Teaser 2820: Three ages
From The Sunday Times, 9th October 2016 [link] [link]
Today is Alan’s, Brian’s and Colin’s birthday. If I write down their ages in a row in that order then I get a six-figure number. If I write down their ages in a row in the reverse order (i.e., Colin’s followed by Brian’s followed by Alan’s) then I get a lower six-figure number. When I divide the difference between these two six-figure numbers by the total of their three ages the answer is Alan’s age multiplied by Colin’s.
What are Alan’s, Brian’s and Colin’s ages?
As published there are 2 possible solutions to this puzzle.
This puzzle was not included in the published collection of puzzles The Sunday Times Brainteasers Book 1 (2019).
[teaser2820]
Jim Randell 10:38 am on 7 September 2021 Permalink |
If we assume the ages have 2 digits each (something not mentioned in the puzzle text), then we can quickly use the [[
SubstitutedExpression]] solver from the enigma.py library to solve this problem.The following run file executes in 187ms.
And this finds two viable solutions.
However it is not a great deal of work to write a program that considers ages that include 1-digit and 3-digit ages (with a suitable upper limit).
The following Python program runs in 179ms, and finds the same two solutions.
Run: [ @replit ]
from enigma import (irange, printf) # permissible ages ages = irange(1, 120) for A in ages: for B in ages: AB = str(A) + str(B) if len(AB) > 5: break for C in ages: ABC = AB + str(C) if len(ABC) > 6: break if len(ABC) < 6: continue d = int(ABC) - int(str(C) + str(B) + str(A)) if A * C * (A + B + C) == d: printf("A={A} B={B} C={C} [A:B:C={A}{B}{C} C:B:A={C}{B}{A}; d={d}]")Solution: The are two possible answers: Alan = 22, Brian = 61, Colin = 18; Alan = 99, Brian = 70, Colin = 33.
These could be reduced to a single solution by adding one of the following conditions:
The published solution is: “22, 61, 18 (or 99, 70, 33)”.
We can run the Python program above to consider ages up to 9999, and we find that without constraining the values of A, B, C there are 5 possible solutions:
LikeLike
GeoffR 12:13 pm on 7 September 2021 Permalink |
# Using 2-digit ages: Alan = ab, Brian = cd, Colin = ef for ab in range(10, 100): for cd in range (10, 100): for ef in range(10, 100): abcdef = 10000*ab + 100*cd + ef # ages in order efcdab = 10000*ef + 100*cd + ab # ages in reverse order if abcdef < efcdab:continue diff = abcdef - efcdab if diff == (ab * ef) * (ab + cd + ef): print(f"Alan = {ab}, Brian = {cd}, Colin = {ef}") # Alan = 22, Brian = 61, Colin = 18 # Alan = 99, Brian = 70, Colin = 33LikeLike
Frits 2:46 pm on 7 September 2021 Permalink |
# Using 2-digit ages: Alan = A, Brian = B, Colin = C # difference <d> does not depend on B for A in range(10, 100): sA = str(A) for C in range(10, A): # use dummy "10" for the value of B d = int(sA + "10" + str(C)) - int(str(C) + "10" + sA) (sumABC, r) = divmod(d, A * C) if r != 0: continue B = sumABC - A - C if not (9 < B < 100): continue print(f"Alan = {A}, Brian = {B}, Colin = {C}")LikeLike
Jim Randell 4:11 pm on 7 September 2021 Permalink |
Or (still assuming 2-digit ages):
from enigma import (irange, subsets, div, printf) # assuming 2 digit ages for (C, A) in subsets(irange(10, 99), size=2): B = div(9999 * (A - C), A * C) if B is None: continue B -= A + C if B < 10 or B > 99: continue printf("A={A} B={B} C={C}")or:
LikeLike
Frits 6:18 pm on 7 September 2021 Permalink |
# Using 2-digit ages: Alan = A, Brian = B, Colin = C # # d = 9999 * (A - C) = (A * C) * (A + B + C) # 9999 = 3 * 3 * 11 * 101 # # as A * C cannot be a multiple of 101 the sum A + B + C must be 101 or 202 # this means that A * C must be a multiple of 99 # and (A * C) / (A - C) is 49.5 or 99 for A in [x for x in range(11, 100) if x % 3 == 0 or x % 11 == 0]: for i, y in enumerate([A + 99, 2 * A + 99]): (C, r) = divmod(99 * A, y) if r > 0 or not (9 < C < 99): continue B = 101 - A - C if i == 0 else 202 - A - C if not (9 < B < 100): continue # probably not needed print(f"Alan = {A}, Brian = {B}, Colin = {C}")LikeLike
Frits 9:22 pm on 7 September 2021 Permalink |
Allowing for ages up to 999, again we don’t need to loop over B.
# allow for high 3-digit ages for A in range(10, 1000): sA = str(A) if A < 100: # see previous posting rangeC = list(range(1, 10)) + \ [int((99 * A) / (A + 99)), int((99 * A) / (2 * A + 99))] else: rangeC = range(1 , A % 100) for C in rangeC: sC = str(C) if sC[0] > sA[0]: continue AC = sA + sC if len(sA + sC) > 5: break prodAC = A * C # allow Brian to be born on 9th October 2016 minB = 10**(5 - len(AC)) if len(AC) != 5 else 0 # how much does d decrement due to one increment of B? if len(sA) > len(sC): difB = int((len(sA) - len(sC)) * "9" + "0") else: difB = 0 # calculate difference for first B entry d = int(sA + str(minB) + sC) - int(sC + str(minB) + sA) # rule: (A * C) * (A + minB + C) + n * (A * C) = d - n * difB (n, r) = divmod(d - prodAC * (A + minB + C), prodAC + difB) if r > 0 or n < 0: continue B = minB + n sB = str(B) ABC = sA + sB + sC if len(ABC) != 6: continue d = int(ABC) - int(sC + sB + sA) if prodAC * (A + B + C) == d: print(f"A={A} B={B} C={C} [A:B:C={A}{B}{C} C:B:A={C}{B}{A}; d={d}]")LikeLike
Jim Randell 10:20 pm on 7 September 2021 Permalink |
Here’s my take on isolating B from the equation, by adjusting the definition of [[
ages()]] you can allow whatever range of ages you want (including up to 9999).from itertools import product from enigma import (irange, div, printf) # decompose t into n numbers, min m def decompose(t, n, m=1, s=[]): if n == 1: if not (t < m): yield s + [t] else: for x in irange(m, t - n + 1): yield from decompose(t - x, n - 1, m, s + [x]) # acceptable k digit ages def ages(k): if k == 3: return irange(100, 120) if k < 3: return irange(10**(k - 1), (10**k) - 1) # consider number of digits a, b, c; a + b + c = 6 for (a, b, c) in decompose(6, 3): # age ranges (rA, rB, rC) = (ages(k) for k in (a, b, c)) if not (rA and rB and rC): continue # multipliers mA = 10**(b + c) - 1 mB = (10**c) - (10**a) mC = 10**(a + b) - 1 # consider values for A and C for (A, C) in product(rA, rC): AC = A * C B = div(A * mA - C * mC - AC * (A + C), AC - mB) if B is not None and B in rB: printf("A={A} B={B} C={C}")LikeLike
Frits 10:53 pm on 7 September 2021 Permalink |
@Jim, very concise.
Almost twice as fast with PyPy (for ages up to 999) although having 4 times as many (innermost) loops (187920 and 44649).
LikeLike
Frits 12:16 pm on 8 September 2021 Permalink |
@Jim,
While trying decompose(11, 3) I got into memory problems (5,6 GB memory used).
Although itertools product is supposed to be a generator the problem disappeared when using separate loops for A and C (25 MB memory used).
LikeLike
Jim Randell 12:41 pm on 8 September 2021 Permalink |
I suspect internally [[
product()]] is remembering all the values of the inner loops, because it doesn’t know thatrangeobjects are restartable iterators. Which will end up using a lot of memory if the ranges are large.So in the general case it would be better to use two
forloops. (Which was how it was originally before I decided to save a line and a level of nesting).LikeLike
Jim Randell 4:00 pm on 9 September 2021 Permalink |
Since I end up writing [[
decompose()]] functions a lot in puzzles, I’ve added a helper function to enigma.py to generate them for you (see [[Decompose()]] and [[decompose()]]).For example, this program becomes:
from enigma import (decompose, irange, div, printf) # acceptable <k> digit ages def ages(k): if k == 3: return irange(100, 120) if k < 3: return irange(10**(k - 1), (10**k) - 1) # consider number of digits (a, b, c), a + b + c = 6 for (a, b, c) in decompose(6, 3, increasing=0, sep=0, min_v=1): # age ranges (rA, rB, rC) = (ages(k) for k in (a, b, c)) if not (rA and rB and rC): continue # multipliers mA = 10**(b + c) - 1 mB = (10**c) - (10**a) mC = 10**(a + b) - 1 # consider values for A and C for A in rA: for C in rC: AC = A * C B = div(A * mA - C * mC - AC * (A + C), AC - mB) if B is not None and B in rB: printf("A={A} B={B} C={C}")LikeLike
Frits 5:52 pm on 9 September 2021 Permalink |
@Jim, Thanks, I will look into it.
Calculating rC after A is known is faster:
LikeLike
Frits 2:07 pm on 10 September 2021 Permalink |
Finding a galactic object for “Brian” 9.5 billion years old.
This program runs in 8 seconds with PyPy.
from enigma import decompose from math import ceil # acceptable k digit ages def ages(k): if k == 11: # universe is approx. 13.8 billion years old return range(10 ** (k - 1), 138 * 10 ** (k - 3)) else: return range(10 ** (k - 1), (10 ** k)) L = 13 # number of digits concatenated A, B and C print("number of digits =", L) cnt = 0 cntsols = 0 # consider number of digits a, b, c; a + b + c = L for (a, b, c) in decompose(L, 3, increasing=0, sep=0, min_v=1): # skip if A * C * (A + B + C) will have too many digits if 2 * a + c - 2 > L or 2 * c + a - 2 > L: continue # group A range by first digit rA = [range(i * 10**(a - 1), i * 10**(a - 1) + 10**(a - 1)) for i in range(1, 10)] rB = ages(b) # multipliers mA = 10 ** (b + c) - 1 mB = (10 ** c) - (10 ** a) mC = 10 ** (a + b) - 1 # consider values for A and C for i, grp in enumerate(rA, 1): rC = range(10 ** (c - 1), (i + 1) * 10 ** (c - 1)) # check first entry in C loop (see below) A = i * 10**(a - 1) C = rC[0] if A * C == mB: C = rC[1] # next entry, we don't want to divide by zero numeratorB = A * mA - C * mC - A * C * (A + C) denominatorB = A * C - mB # numeratorB decreases and denominatorB increases as C becomes higher (B, r) = divmod(numeratorB, denominatorB) d1 = A * mA + B * mB - C * mC if d1 <= 0: # we need a positive distance if numeratorB > 0: # check when numeratorB becomes negative # (mC + A**2 + A * C) * C - A * mA = 0 # quadratic equation: A * C**2 + (mC + A**2) * C - A * mA = 0 newC1 = (-1 * (mC + A**2) + ((mC + A**2)**2 + 4 * A**2 * mA)**.5) newC1 /= (2 * A) newC2 = mB / A # when will denominatorB become positive again? newCs = sorted([newC1, newC2]) low = ceil(newCs[0]) hgh = int(newCs[1]) rC = range(low, hgh + 1) for A in grp: for C in rC: cnt += 1 AC = A * C # difference rule: A * mA + B * mB - C * mC = A * C * (A + B + C) if AC == mB: continue # don't divide by zero (B, r) = divmod(A * mA - C * mC - AC * (A + C), AC - mB) if B < 0: break if r == 0 and B in rB: d1 = int(str(A) + str(B) + str(C)) - int(str(C) + str(B) + str(A)) d2 = AC * (A + B + C) print(f"A={A} B={B} C={C}, AC={AC} diff1={d1} diff2={d2}") cntsols += 1 print("number of solutions", cntsols, "loop count =", cnt)LikeLike
GeoffR 1:49 pm on 12 September 2021 Permalink |
' A Solution in Visual Basic 2019 Module Module1 Public Sub Main() Dim A, B, C As Integer ' for Alan, Brian and Colin Dim AGES, Rev_AGES, DIFF_AGES As Integer For A = 10 To 99 For B = 10 To 99 If B = A Then Continue For End If For C = 10 To 99 If C = B Or C = A Then Continue For End If AGES = 10000 * A + 100 * B + C Rev_AGES = 10000 * C + 100 * B + A If AGES > Rev_AGES Then DIFF_AGES = AGES - Rev_AGES If DIFF_AGES = (A + B + C) * (A * C) Then Console.WriteLine("Alan = {0}, Brian = {1}, Colin = {2}", A, B, C) End If End If Next 'C Next 'B Next 'A Console.ReadKey() 'Freeze console screen End Sub End Module 'Alan = 22, Brian = 61, Colin = 18 'Alan = 99, Brian = 70, Colin = 33LikeLike