Numbers 640, 663, 686, 722, 741, and 792 would presumably yield to a variant of Jim’s analysis as given here. 792 (at least) is flawed because it doesn’t specify that they all arrived together — nor, for that matter, whether riding pillion on the rear carrier is permitted (I know from experience that it seriously upsets the balance and steering!).

LikeLike

]]>This Python program performs an exhaustive search of all possible stacks of blocks.

It runs in 268ms.

**Run:** [ @replit ]

from enigma import (multiset, subsets, csum, irange, printf) # the blocks blocks = multiset.from_dict({8: 4, 4: 2, 3: 3, 1: 2}) assert blocks.sum() == 51 # extract a 3 block blocks.remove(3) # and consider possible arrangements of the remaining blocks for bs in subsets(blocks, size=len, select="mP", fn=list): bs.insert(0, 3) # find what thicknesses can be measured ts = set(b - a for (a, b) in subsets(csum(bs, empty=1), size=2)) # can we measure all values from 1 to 48 if all(x in ts for x in irange(1, 48)): printf("{bs}")

**Solution:** The pile of blocks (bottom to top) is one of the following two sequences:

(3, 4, 3, 8, 8, 8, 8, 4, 1, 1, 3)

(3, 1, 1, 4, 8, 8, 8, 8, 3, 4, 3)

One being the reverse of the other.

LikeLike

]]>Thanks Jim. It was the ‘in both directions’ that I was struggling to get my head around. This week’s puzzle is trivial by comparison!

LikeLike

]]>**Run:** [ @replit ]

from enigma import (nconcat, div, ordered, printf) # adjacency matrix adj = { 1: [2, 4, 5], 2: [1, 3, 4, 5, 6], 3: [2, 5, 6], 4: [1, 2, 5, 7, 8], 5: [1, 2, 3, 4, 6, 7, 8, 9], 6: [2, 3, 5, 8, 9], 7: [4, 5, 8], 8: [4, 5, 6, 7, 9], 9: [5, 6, 8], } # generate k-length numbers with no repeated digits def solve(k, ds): if k == 0: yield ds else: for d in adj[ds[-1]]: if d not in ds: yield from solve(k - 1, ds + [d]) # start at 2, and dial 4 more digits ns = list() for ds in solve(4, [2]): n = nconcat(ds[1:]) # look for previous numbers that pair with this one for m in ns: (x, y) = ordered(m, n) k = div(y, x) if k: printf("{y} = {k} * {x}") # add in the new number ns.append(n)

(Or a more efficient (but longer) variation on this code [@replit]).

**Solution:** *[To Be Revealed]*

LikeLike

]]>@Nigel: The way I thought of it the barman makes a journey towards the stadium for each of the *k* friends. And in between friends he has to make *(k − 1)* return journeys towards the pub.

If he made complete journeys between the pub and the stadium this would be *(2k − 1)* journeys of distance *d* for a total of *(2k − 1)d*.

But if he made complete journeys he would be travelling over the ground that each friend walks, *in both directions*, so this amount is overcounting the barmans distance by *2x* for each of the *k* friends.

So the actual overall distance travelled by the barman is:

(2k − 1)d − 2kx

And this actual distance is travelled at velocity *v*, so we can determine the total time taken for the barman.

LikeLike

]]>Hi Jim. I very much enjoyed this puzzle (more for the logic than the Python content!) but I’m intrigued to know how you got to the term 2kx in your second equation. I got to it indirectly by looking at the vector sum of all the motorbike journeys out: [(k)(d-x)] +back: [(k)(d-x)-d] given that he finishes up a distance d from where he started. That then reduces to:

[(2k-1)d -2kx] but is there a more intuitive way of getting to the second term?

LikeLike

]]>This Python program counts the number of possible triangles and the number of them that are right angled.

It runs in 53ms. (Internal runtime is 1.0ms).

**Run:** [ @replit ]

from enigma import (decompose, fraction, printf) # generate possible triangles triangles = lambda: decompose(180, 3, increasing=1, sep=0, min_v=1, max_v=90) # count: # t = total number of triangles # r = number with a right angle r = t = 0 for (a, b, c) in triangles(): t += 1 if c == 90: r += 1 # output solution (p, q) = fraction(r, t) printf("P(win) = {r}/{t} = {p}/{q}")

**Solution:** The chance of winning is 1/16.

There are 720 triangles, and 45 of them are right angled.

I think it would be annoying to choose a triangle with an 89° angle.

LikeLike

]]>Here is a numerical approach, based on the barman dropping the first friend off after a distance *x* (from the pub) while the remaining friends set off on foot.

The barman then returns to the trailing group and ferries a single friend from the trailing to the leading group until everyone has been transferred to the leading group. And we record the maximal journey time for the friends to give us a total time to get all the friends to the stadium.

We then use the [[ `find_min()`

]] function from the **enigma.py** library to determine at what distance *x* the shortest total journey time occurs.

Unsurprisingly this gives us the same answer as the analytical approach above (but with considerably more effort). But it does show that the logic used in the analysis does indeed produce the minimum time.

**Run:** [ @replit ]

from enigma import (irange, fdiv, find_min, printf) k = 9 # number of people in the group d = 5.8 # distance between pub and stadium w = 2.5 # walking speed v = 30 # motorbike speed # if: A starts at a, velocity v; B starts at b, velocity w # return: (<position>, <time>) of meeting def meet(a, v, b, w, t0=0): t = fdiv(b - a, v - w) return (b + t * w, t0 + t) # run a scenario where the first person is dropped off at distance x # return the time taken for everyone to arrive def run(x): ts = list() # ferry friend 1 to x and let them walk the remaining distance (d - x) d1 = x t1 = fdiv(x, v) # and so the total time for the first friend is ... ts.append(t1 + fdiv(d - d1, w)) # the position of the trailing group at time t is: tr = lambda t: min(t * w, d) # the position of the leading group at time t >= t1 is: ld = lambda t, t1=t1: min(x + (t - t1) * w, d) # ferry the remaining friends ... for _ in irange(2, k): # we return from d1 to the trailing group (d2, t2) = meet(d1, -v, tr(t1), w, t1) # and then back to the leading group (d3, t3) = meet(d2, +v, ld(t2), w, t2) if d3 < d: # they walk the remaining distance ts.append(t3 + fdiv(d - d3, w)) else: # they are dropped off at the stadium (d3, t3) = (d, t2 + fdiv(d - d2, v)) ts.append(t3) (d1, t1) = (d3, t3) # return the maximum time return max(ts) # find the minimal time r = find_min(run, 0, d) (t, x) = (r.fv, r.v) printf("min time = {t:g} hours (= {m:g} min) [drop 1 at {x:g} miles]", m=t * 60)

Here is a graph of the total journey time against the distance *x* that the first friend is taken from the pub. We see that the minimum time is achieved when *x* = 2.6 miles.

LikeLike

]]>`SubstitutedExpression`

]] solver from the The following run file executes in 66ms. (Internal runtime of the generated program is 94µs).

**Run:** [ @replit ]

#! python3 -m enigma -r # assign fence numbers (1-5) to the following groups: # # jockeys = A (Wally), B (Bob), C (Chris), D (Dave), E (Fred) # horses = F (Egg Nog), G (Big Gun), H (Long Gone), I (Nig Nag), J (Dragon) # colours = K (mauve), L (yellow), M (green), N (blue), P (red) SubstitutedExpression --base="6" --digits="1-5" --distinct="ABCDE,FGHIJ,KLMNP" --template="(A B C D E) (F G H I J) (K L M N P)" --solution="" # "Dave (D) riding Egg Nog (F) lasted longer than Bill (B) who fell at the 2nd fence" "D = F" "D > B" "2 = B" # "Big Gun (G) fell at the 3rd" "3 = G" # "mauve (K) lasted the longest" "5 = K" # "Long Gone (H) lasted longer than yellow (L) "H > L" # "Chris's (C's) horse fell one fence later than the horse ridden by green (M)" "M + 1 = C" # "Fred (E) and his friend blue (N) did not fall at adjacent fences" "abs(E - N) > 1" # "Nig Nag (I) was ridden by Wally (A)" "I = A" # "Dragon's (J's) jockey wore red" "J = P"

We can wrap this in Python code to format the solution in a more friendly form:

**Run:** [ @replit ]

from enigma import (SubstitutedExpression, group, printf) # load the run file p = SubstitutedExpression.from_file("teaser2685.run") # map symbols to jockey, horse, colour names name = dict( A="Wally", B="Bob", C="Chris", D="Dave", E="Fred", F="Egg Nog", G="Big Gun", H="Long Gone", I="Nig Nag", J="Dragon", K="mauve", L="yellow", M="green", N="blue", P="red", ) # solve the puzzle for s in p.solve(verbose=0): # group symbols by value d = group(s.keys(), by=s.get) # output solution for each fence for k in sorted(d.keys()): # extract jockey, horse, colour (j, h, c) = (name[s] for s in sorted(d[k])) printf("{k}: {j} ({c}) on {h}") printf()

**Solution:** Fred was in yellow, riding Big Gun.

The full solution is:

1st fence:Wally (blue) on Nig Nag

2nd fence:Bob (red) on Dragon

3rd fence:Fred (yellow) on Big Gun

4th fence:Dave (green) on Egg Nog

5th fence:Chris (mauve) on Long Gone

LikeLike

]]>Joined together along the line of their common height h, of course.

A base a = 21 also allows eight resultant triangles, including reflections.

LikeLike

]]>If their bases are d and e, then d² = b² – h² and e² = c² – h².

d + e, or |d – e| in the case of the obtuse-angled triangles, must equal a = 28.

b must not equal c, for then the overall triangle would be isosceles

(that is the case when h = 48, b = c = 50, and d = e = 14).

LikeLike

]]>s = (a + b + c) / 2

A = √(s(s − a)(s − b)(s − c))

And if the height (from side *a*) is *h*, then we also have:

A = ha / 2

Hence the height *h* can be determined from the sides *a, b, c*:

h = 2A / a

h = (2/a)√(s(s − a)(s − b)(s − c))

This Python program considers possible values for the 2nd and 3rd sides of the triangle, and then looks for values where the height is an integer.

It runs in 53ms. (Internal runtime is 1.8ms).

**Run:** [ @replit ]

from enigma import (irange, div, is_square, subsets, sq, printf) # check for a viable triangle def is_triangle(a, b, c): # check triangle inequalities if not (a + b > c and a + c > b and b + c > a): return # no triangle is isosceles if len({a, b, c}) < 3: return # no triangle is right angled sqs = (sq(a), sq(b), sq(c)) if sum(sqs) == 2 * max(sqs): return # looks OK return (a, b, c) # calculate integer height from side a def height(a, b, c): p = a + b + c x = div(p * (p - 2 * a) * (p - 2 * b) * (p - 2 * c), 4) if not x: return x = is_square(x) if not x: return h = div(x, a) return h # base a = 28 # consider possible integer length for the remaining sides, b, c for (b, c) in subsets(irange(1, 60), size=2, select="R"): if not is_triangle(a, b, c): continue # calculate the height of the triangle h = height(a, b, c) if not h: continue # output viable triangles printf("a={a} b={b} c={c} -> h={h}")

**Solution:** The heights of the triangles are 9″ (2 triangles), 15″ (4 triangles), 24″ (2 triangles).

The four triangles found by the program are shown below. And they can be mirrored to produce the remaining four triangles.

LikeLike

]]>If the friends just walked from pub to the stadium it would take 2.32 hours.

If the barman ferried each of them individually between the pub and the stadium it would take 3.29 hours.

But we can do better.

If the barman takes the first friend on his motorbike, and the rest of the friends start walking towards the stadium. Then the barman drops the first friend off to walk the remainder of the distance to the stadium and returns to the group (now a short distance from the pub) and ferries the next friend to join the first friend, and so on until he collects the final friend and drops him off at the stadium at the same time that all the other friends arrive, then we can achieve a minimum overall time.

The only thing to work out is how far before the stadium to drop the first friend, then it is just a matter of ferrying friends from the trailing to the leading group.

If each of the *k* friends walks a distance *x* at velocity *w*, and travels by motorbike a distance *(d − x)* at a velocity *v*, then each journey takes:

t = x/w + (d − x)/v

And the total time taken for the barman to ferry them all is:

t = [(2k − 1)d − 2kx]/v

Everyone arrives at the stadium at the same time, so:

x/w + (d − x)/v = [(2k − 1)d − 2kx]/v

vx + (d − x)w = [(2k − 1)d − 2kx]w

vx + dw − xw = (2k − 1)dw − 2kxw

x(v + w(2k − 1)) = dw(2k − 2)

x = 2dw(k − 1) / (v + w(2k − 1))

or:

n = 2k − 1

x = dw(n − 1) / (v + wn)

t = d(w + vn) / v(v + wn)

The answer can then be calculated directly.

**Run:** [ @replit ]

from enigma import (fdiv, printf) k = 9 # number of people in the group d = 5.8 # distance between pub and stadium w = 2.5 # walking speed v = 30 # motorbike speed # calculate amount of walking per person n = 2 * k - 1 x = fdiv(d * w * (n - 1), v + w * n) # calculate time taken t = fdiv(x, w) + fdiv(d - x, v) # output solution printf("t={t:g} hours (= {m:g} min) [x={x:g} miles]", m=t * 60)

**Solution:** The minimum travelling time is 82 minutes.

We calculate:

x = 16/5 = 32/10

t = 32/25 + 13/150 = 41/30 = 82/60

So the first friend is dropped off 3.2 miles from the stadium (and walks the remainder of the way).

Each friend walks for 76.8 minutes and is on the motorbike for 5.2 minutes. Giving each a total journey time of 82 minutes.

LikeLike

]]>D < F

C = (E − 2) + 2 ⇒ C = E

B < E

A = (C + 2) + 2 ⇒ A = C + 4 (and A was 3rd)

From which we get 2 chains (shortest to longest time):

B < C,E < A

D < F

However we are told that A was in 3rd place, but we see there are at least 3 competitors (B, C, E) who finished in a shorter time, which would imply the best A could have placed was 4th.

So, assuming positions are ranked according to time, either the puzzle is flawed, or the goal of the race is to achieve the longest time, not the shortest.

In this latter case the finishing positions (longest to shortest time) are:

1st:Fred

2nd:Dave

3rd:Alf

4th:Colin, Ed (joint)

6th:Bob

**Solution:** Dave won second prize.

LikeLike

]]>But if we require that the 6-digit numbers formed from the date *cannot* have a leading zero, then this narrows down the solution space considerably (and essentially restricts the three dates to the form *10xxxx, 20yyyy, 30zzzz*, and the multiples being 1, 2, 3).

This Python program runs in 129ms.

**Run:** [ @replit ]

from datetime import (date, timedelta) from enigma import ( fdiv, inc, repeat, nconcat, nsplit, catch, subsets, append, tuples, union, join, printf ) # extract date as a 6-digit number def number(d): if d.day < 10: return None return nconcat(d.day, d.month, d.year % 100, base=100) # check viable generation gap, a -> b def gap(a, b): y = fdiv((b - a).days, 365.2422) return not (y < 16 or y > 50) # consider dates for the setters birthdate for d in repeat(inc(timedelta(days=-1)), date(2014, 3, 30)): if d.year < 1922: break # construct the number "ddmmyy" n = number(d) if n is None: continue # look for proper multiples that also give a valid date mn = n ds = list() while True: mn += n if mn > 311299: break (dd, mm, yy) = nsplit(mn, base=100) for y in (1900 + yy, 1800 + yy): if y < 1892: continue md = catch(date, y, mm, dd) if md is None or number(d) is None: continue ds.append(md) # look for a set of 3 plausible ages for ss in subsets(sorted(ds), size=2): ss = append(ss, d) if not all(gap(a, b) for (a, b) in tuples(ss, 2)): continue # check all digits are used if len(union(nsplit(number(d)) for d in ss)) < 10: continue # output dates printf("{ss}", ss=join(ss, sep=" -> "))

The program works backwards from 2014 to 1922 looking for sets of 3 dates that make a plausible set of birthdates for the three generations.

It finds 2 possible situations, as below (ages shown are on the date the puzzle was published (2014-03-30)):

Grandmother = 1919-08-30 (age = 94.6y)

Mother = 1946-05-20 (age = 67.9y)

Setter = 1973-02-10 (age = 41.1y)

gap = 26.7y

100273 × 2 = 200546

100273 × 3 = 300819

Grandmother = 1892-07-30 (age = 121.7y)

Mother = 1928-05-20 (age = 85.9y)

Setter = 1964-02-10 (age = 50.1y)

gap = 35.7y

100264 × 2 = 200528

100264 × 3 = 300792

The second of these is only just plausible, so presumably the first provides the required answer. (I would have preferred the puzzle eliminated one of these solutions by an explicit condition).

**Solution:** The mother’s date of birth is: 200546 (i.e. 20th May 1946).

To see solutions where the 6-digit number formed from the date *is* permitted to have a leading zero, the check at line 9 can be removed. In this case the program finds 115 solutions.

LikeLike

]]>

[1]

(maurice is not to sing at my funeral service)

(stephen receives £3000) → (alec is to have £5000)

(thomas receives less than maurice) → (alec is to have exactly twice what nigel has)

(thomas does not receive £1000) → (stephen is to have exactly £4000)

[2]

(maurice is not to sing at my funeral service) ← (stephen receives £3000)

(alec is to have £5000)

(thomas receives less than maurice) → (alec is to have exactly twice what nigel has)

(thomas does not receive £1000) → (stephen is to have exactly £4000)

[3]

(maurice is not to sing at my funeral service) ← (stephen receives £3000)

(alec is to have £5000) ← (thomas receives less than maurice)

(alec is to have exactly twice what nigel has)

(thomas does not receive £1000) → (stephen is to have exactly £4000)

[4]

(maurice is not to sing at my funeral service) ← (stephen receives £3000)

(alec is to have £5000) ← (thomas receives less than maurice)

(alec is to have exactly twice what nigel has) ← (thomas does not receive £1000)

(stephen is to have exactly £4000)

This Python program investigates each interpretation, rejecting those that do not have a single way of distributing the money.

It runs in 54ms. (Internal runtime is 866µs).

**Run:** [ @replit ]

from enigma import (defaultdict, cproduct, subsets, implies, singleton, join, printf) # evaluate the clauses under interpretation <i> def check(i, cs): (a, b, c, d, e, f, g) = cs # [1] (a, b -> c, d -> e, f -> g) if i == 1: return all([a, implies(b, c), implies(d, e), implies(f, g)]) # [2] (a <- b, c, d -> e, f -> g) if i == 2: return all([implies(b, a), c, implies(d, e), implies(f, g)]) # [3] (a <- b, c <- d, e, f -> g) if i == 3: return all([implies(b, a), implies(d, c), e, implies(f, g)]) # [4] (a <- b, c <- d, e <- f, g) if i == 4: return all([implies(b, a), implies(d, c), implies(f, e), g]) # the amounts in the envelopes money = [5000, 4000, 3000, 2000, 1000] # choose an interpretation of the will (1-4) for i in (1, 2, 3, 4): # record outcomes: (A, M, N, S, T) -> sing r = defaultdict(list) # allocate the envelopes, and whether Maurice can sing for (k, sing) in cproduct([subsets(money, size=len, select="P"), [0, 1]]): (A, M, N, S, T) = k # the clauses: cs = [ # (a) "M is not to sing ..." (not sing), # (b) "S gets 3000" (S == 3000), # (c) "A gets 5000" (A == 5000), # (d) "T gets less than M" (T < M), # (e) "A gets twice N" (A == 2 * N), # (f) "T does not get 1000" (T != 1000), # (g) "S gets 4000" (S == 4000), ] # is this is a valid allocation? if check(i, cs): r[k].append(sing) # do we need to proceed further? if len(r.keys()) > 1: break # there is only one way to distribute the money? k = singleton(r.keys()) if k: # output solution (A, M, N, S, T) = k printf("[{i}] A={A} M={M} N={N} S={S} T={T}; sing={sing}", sing=join(sorted(r[k]), sep=", ", enc="{}"))

**Solution:** The money is distributed as follows:

Alec:£2000

Maurice:£3000

Nigel:£1000

Stephen:£4000

Thomas:£5000

The solution is provided by interpretation [3] of the will.

And as Stephen does not get £3000, Maurice is not prohibited from singing at the funeral.

LikeLike

]]>LikeLike

]]>LikeLike

]]>We can use some analysis to simplify the puzzle to two cases, one of which has no solution for positive integers, and the other that gives us the required solution.

However, here is a constructive Python program that considers the total numbers of players *t* (up to 2000), and generates the possible *(n, k, m)* values for each candidate *t* value. It then looks for two *t* values that differ by 2, and give *n* and *k* values that differ by 1, and *m* values that differ by 63.

This Python program runs in 85ms.

**Run:** [ @replit ]

from enigma import (irange, divisors_pairs, tri, cproduct, tuples, printf) # generate (k, n, m) values for t players def generate(t): # divide them into k sections of n players each for (k, n) in divisors_pairs(t, every=1): if n < 2: continue # calculate the total number of matches m = k * tri(n - 1) yield (t, k, n, m) # solve for numbers of players that differ by 2 def solve(N): for (zs, ys, xs) in tuples((list(generate(t)) for t in irange(1, N)), 3): for ((t, k, n, m), (t_, k_, n_, m_)) in cproduct([xs, zs]): # check the differences in the corresponding k, n, m values if abs(k - k_) == 1 and abs(n - n_) == 1 and m - m_ == 63: # output solution printf("{k} sections of {n} players (= {m} matches) -> {k_} sections of {n_} players (= {m_} matches)") # solve the puzzle (up to 2000 players) solve(2000)

**Solution:** There are 10 players in each section.

The club starts with 110 players, formed into 11 sections of 10 players each. There are 45 matches among the players in each section, and 495 matches in total.

When 2 players leave there are 108 players remaining. These are formed into 12 sections of 9 players each. There are 36 matches among the players in each section, and 432 matches in total. This is 63 fewer matches.

Manually from:

(n ± 1)(k ± 1) = nk − 2

we can see that (for *n* + *k* > 3) there are 2 cases:

**[Case 1]** *(n, k)* → *(n + 1, k − 1)* ⇒ *k* = *n* − 1.

k C(n, 2) − (k − 1) C(n + 1, 2) = 63

n[(n − 1)(n − 1) − (n − 2)(n + 1)] = 126

n(3 − n) = 126

n² − 3n + 126 = 0

Which has no solution in positive integers.

**[Case 2]** *(n, k)* → *(n − 1, k + 1)* ⇒ *k* = *n* + 1.

k C(n, 2) − (k + 1) C(n − 1, 2) = 63

(n − 1)[(n + 1)n − (n + 2)(n − 2)] = 126

(n − 1)(n + 4) = 126

n² + 3n − 130 = 0

(n − 10)(n + 13) = 0

n = 10

k = 11

And so there are 11 sections, each with 10 players.

LikeLike

]]>each button.

Digits used with each button as follows:

[A,D,E,I,M,N,R,S,T,U,Y =

[8,1,8,1,2,6,5,5,2,2,5]

LikeLike

]]>LikeLike

]]>`SubstitutedExpression`

]] solver from the The following run file executes in 67ms. (Internal runtime of the generated program is 1.8ms).

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression --digits="1-9" --distinct="" # SUNDAY, TIMES, TEASER are all multiples of 495 "SUNDAY % 495 = 0" "TIMES % 495 = 0" "TEASER % 495 = 0" # M, T, U have the same value "M = T" "T = U" # no digit appears more than 3 times "all(v < 4 for v in multiset.from_seq([A, D, E, I, M, N, R, S, T, U, Y]).values())" --answer="SATURDAY" --template="SUNDAY TIMES TEASER SATURDAY"

**Solution:** To type **SATURDAY** use the following keys: **58225185**.

The letters we are given are assigned to the following keys:

1:D I

2:M T U

5:R S Y

6:N

8:A E

So we have:

SUNDAY= 526185 = 495 × 1063

TIMES= 21285 = 495 × 43

TEASER= 288585 = 495 × 583

LikeLike

]]>Alas I’m thwarted!

Thanks for this and all your efforts.

They are all appreciated.

Regards Paul

LikeLike

]]>@Paul: My program performs an exhaustive search, so there is only one solution to the puzzle.

If we had:

CARY = 24 [576]

JAMES = 28 [784]

STEVE = 29 [841]

Then {LUCY, NICK, RICKY} must correspond to {16 [256], 23 [529], 25 [625]} in some order.

LUCY is the youngest, so we have:

LUCY = 16 [256]

But then ALAN has to have digits in common with CARY [576], LUCY [256], JAMES [784], but not STEVE [841]

Which means for ALAN we need to find a square with a 7 and a 5 or a 6. The only candidate is 24 [576], but that is already used by CARY, so it is not possible to find a value for ALAN in this scenario.

LikeLike

]]>Forgive me I should’ve made myself clearer.

If Cary becomes 576, and James 784, and Steve 841, can it then work as an alternative?

LikeLike

]]>And from (ii) two of the brothers totals are equal to the sum of the totals of two of the remaining three brothers. These two must be A and B, and so B = C + D, and A = C + 2D or 2C + D.

This Python program starts by finding possible A, B, C, D values, and then tries to split these totals into numbers of cattle and sheep such that the remaining conditions hold.

It runs in 62ms. (Internal runtime is 11.2ms).

**Run:** [ @replit ]

from enigma import (irange, subsets, ediv, as_int, printf) # split total <t> into (<cattle>, <sheep>) with value <v> def split(t, v): c = ediv(v - t, 3) return (as_int(c, "0+"), as_int(t - c, "0+")) # solve puzzle for given totals: A, B, C, D def solve(A, B, C, D): # consider number of cattle for A for Ac in irange(0, A): As = A - Ac # total value for A (cattle = 4, sheep = 1) v = 4 * Ac + As # find splits for B, C, D try: (Bc, Bs) = split(B, v) (Cc, Cs) = split(C, v) (Dc, Ds) = split(D, v) except ValueError: continue # total number of cattle is not divisible by 4 if (Ac + Bc + Cc + Dc) % 4 == 0: continue # someone has twice as many of one type of animal as the other if all(2 * x != y and x != 2 * y for (x, y) in [(Ac, As), (Bc, Bs), (Cc, Cs), (Dc, Ds)]): continue # output scenario printf("[v={v}] A: {Ac}c+{As}s = {A}; B: {Bc}c+{Bs}s = {B}; C: {Cc}c+{Cs}s = {C}; D: {Dc}c+{Ds}s = {D}") # total number of animals for C (not more than 100) for C in irange(1, 100): # total number of animals for D (not more than C) for D in irange(0, C): # B = C + D (not more than 100) B = C + D if B > 100: break # A = C + 2D or 2C + D (more than 100) for A in (B + D, B + C): if not (A > 100): continue # one of these values must be 1.8x a lower value if not any(5 * x == 9 * y for (x, y) in subsets((A, B, C, D), size=2)): continue # solve the puzzle solve(A, B, C, D)

**Solution:** The son receiving over 100 animals received 6 cattle.

The full solution is:

A:6 cattle + 111 sheep = 117 animals; 117 = 81 (B) + 36 (D)

B:18 cattle + 63 sheep = 81 animals; 81 = 45 (C) + 36 (D) = 1.8 × 45 (C)

C:30 cattle + 15 sheep = 45 animals; 30 is twice 15

D:33 cattle + 3 sheep = 36 animals

Assigning values of cattle = 4, sheep = 1, each brother receives animals to a value of 135.

In total there are 279 animals = 87 cattle (not a multiple of 4) + 192 sheep.

LikeLike

]]>@Paul: Thanks for the feedback.

We can’t swap CARY for 24 in the solution I give above, as 24² = 576, and CARY and JAMES share the letter A, so their squares need to share a digit. But 576 and 841 (= 29²) don’t have any digits in common.

LikeLike

]]>Thanks for all the good work on these Teasers.

Love the simplicity of your website and your solutions.

Re 3137 Teaser

Is 24 instead of 31 also a correct answer? Keep up the good work!

LikeLike

]]>@Frits: I did wonder about that. I check the final PM of the 20th century does not have a term that is longer than 30 years (line 12), and when I saw that there was only a single possible solution I didn’t worry about the actual length of the term, as it starts very close to the end of the century, and there are 2 unused primes available.

Here is the code with an extra check to ensure the final term length is possible. (I also include code to specify the first and last years of the 20th century. I am using the “strict” definition 1901-2000, but you get the same answer with the “popular” usage of 1900-1999).

from enigma import (primes, append, tuples, printf) # first/last years for 20th century (first, last) = (1901, 2000) # possible term lengths terms = set(primes.between(0, 30)) # select <k> years from <years> # return (<start-years>, <term-lengths>) def solve(k, years, ys=[], ts=[]): # are we done? if k == 0: yield (ys, ts) elif not k > len(years): # choose the next year for (i, y) in enumerate(years): # consider possible term lengths g = y - ys[-1] for t in terms.intersection({g, g - 1}): if t in ts: continue # solve for the remaining years yield from solve(k - 1, years[i + 1:], append(ys, y), append(ts, t)) # consider the start year of the incumbent at the start of the century for y0 in primes.between(first - 31, first): # fill out the 8 starting years in the 20th century years = list(primes.between(max(y0 + 1, first), last)) years.remove(1973) # but not 1973 for (ys, ts) in solve(8, years, [y0]): # check possible term lengths for the incumbent at the end of the century fs = sorted(t for t in terms.difference(ts) if ys[-1] + t > last) if not fs: continue # output solution for ((y1, y2), t) in zip(tuples(ys, 2), ts): printf("{y1} - {y2} = {t} years") printf("{y2} - .... = {fs} years") printf()

But it seems that it may be possible that the final PM is still in office at the time the puzzle was set (having served less than 30 years so far), so we can’t say what the length of the term will be yet.

LikeLike

]]>Frits, the wording of the puzzle is vague on that score, but I think if we do allow the last term of office to extend beyond the end of the century then we get multiple solutions. Jim (line 25) would seem to agree with me that we need to start looking before the beginning of the century.

LikeLike

]]>@Jim, as I don’t have access to a PC I can’t publish/test a program myself.

I think a check is needed to see if the last Prime Minister can serve a prime number of years and which ends after the 20th century (if high prime numbers already have been used this may not be possible anymore).

LikeLike

]]>**Run:** [ @replit ]

from enigma import (primes, append, tuples, printf) # possible term lengths terms = set(primes.between(0, 30)) # select <k> years from <years> # return (<start-years>, <term-lengths>) def solve(k, years, ys=[], ts=[]): # are we done? if k == 0: # no-one served more than 30 years if ys and ys[-1] > 1968: yield (ys, ts) elif not k > len(years): # choose the next year for (i, y) in enumerate(years): # consider possible term lengths g = y - ys[-1] for t in terms.intersection({g, g - 1}): if t in ts: continue # solve for the remaining years yield from solve(k - 1, years[i + 1:], append(ys, y), append(ts, t)) # consider the start year of the incumbent at the start of the century for y0 in primes.between(1870, 1901): # fill out the 8 starting years in the 20th century years = list(primes.between(max(y0 + 1, 1901), 2000)) years.remove(1973) # but not 1973 for (ys, ts) in solve(8, years, [y0]): # output solution for ((y1, y2), t) in zip(tuples(ys, 2), ts): printf("{y1} - {y2} = {t} years") printf("{y2} - .... = ? years") printf()

**Solution:** The years are: 1901, 1907, 1931, 1949, 1979, 1993, 1997, 1999.

The incumbent at the start of the 20th century began office in 1889, so we have:

1. 1889 – 1901 = 11 years

2. 1901 – 1907 = 5 years

3. 1907 – 1931 = 23 years

4. 1931 – 1949 = 17 years

5. 1949 – 1979 = 29 years

6. 1979 – 1993 = 13 years

7. 1993 – 1997 = 3 years

8. 1997 – 1999 = 2 years

9. 1999 – …. = (7 or 19 years)

There are two primes left that could correspond to the length of the term of the incumbent at the end of the 20th century. Note that it is not required that the term of the successor (if any) starts in a prime year.

LikeLike

]]>LikeLike

]]>LikeLike

]]>@Hugh: My manual solution proceeded as follows:

Armed with the prime factorisations of the numbers between 1817 and 1910 (which I did not compute manually), we quickly find 1849, 1892, 1904 are on the list.

Then we need more factors of 17, and there are only 2 candidates: 1836 and 1870. Both of which must be included. So we now have 5 of the 8 numbers on the list.

We can eliminate numbers with a factor of 29 (1827, 1856, 1885).

Considering numbers with factors of 13 (1820, 1859, 1872), if any of these are included it must be 1859 and just one of the others.

Adding 1859 and 1820 to the list leaves factors of (2, 5, 7) to find, and the only candidate is 1890, and this does indeed give a viable list.

And I didn’t look for further solutions.

LikeLike

]]>`SubstitutedExpression.split_sum()`

]] solver from the The following run file executes in 58ms. (Internal runtime is 4.74ms).

**Run:** [ @replit ]

from enigma import (SubstitutedExpression, irange, diff, join, printf) # construct the alphametic sum p = SubstitutedExpression.split_sum( ["THIRTY"] * 2 + ["TEN"] * 4, "HUNDRED", template="(2 * {THIRTY} + 4 * {TEN} = {HUNDRED})", ) # solve the puzzle, and output unused digit(s) for s in p.solve(): ds = diff(irange(0, 9), s.values()) # output solution printf("-> unused digits = {ds}", ds=join(ds, sep=", "))

**Solution:** The missing digit is 7.

LikeLike

]]>The only integer in the range 1902 to 1910 with a small enough largest prime factor to allow us to find two more is 1904 = 2^4 × 7 × 17.

After that I think trial and error (i.e. by computer) is needed.

LikeLike

]]>And the only square in that range is 1849 (= 43²), so that must be one of the 8 values.

And the largest value is in the interval [1902, 1910].

This Python program finds the prime factors for numbers in the required range, and then constructs sets of 8 values whose prime factors, when combined, all appear a multiple of 3 times, and so the product of the values is a cube.

It runs in 164ms.

**Run:** [ @replit ]

from enigma import (irange, prime_factor, multiset, delete, append, printf) # find values whose product is a cube # k = remaining values for find (int) # vs = values so far (list) # fs = prime factors so far (multiset) # d = map of value -> prime factors (multiset) # ks = candidate keys in d (list) def solve(n, vs, fs, d, ks): if n == 0: # check all prime exponents are multiples of 3 if all(x % 3 == 0 for x in fs.values()): yield vs elif not len(ks) < n: # add in a new candidate for (i, k) in enumerate(ks): # and solve for the remaining candidates yield from solve(n - 1, append(vs, k), fs.combine(d[k]), d, ks[i + 1:]) # collect candidate numbers and their prime factors (as a multiset) d = dict((n, multiset.from_pairs(prime_factor(n))) for n in irange(1817, 1910)) # find factors that appear fewer than 3 times in total while True: # count the factors (by combining values from each of the numbers) m = multiset.from_dict(*d.values()) fs = set(k for (k, v) in m.items() if v < 3) if not fs: break # and remove any numbers which involve any of these factors d = delete(d, (k for (k, v) in d.items() if any(p in fs for p in v))) # 1849 is one of the values in the set (vs1, fs1) = ([1849], d[1849]) # and the max value is between 1902 and 1910 for v in irange(1902, 1910): if v not in d: continue vs2 = append(vs1, v) fs2 = fs1.union(d[v]) # solve for the remaining 6 values ks = sorted(k for k in d.keys() if k < v and k not in vs2) for vs in solve(6, vs2, fs2, d, ks): # output solution printf("years = {vs}", vs=sorted(vs))

**Solution:** The eight years are: 1820, 1836, 1849, 1859, 1870, 1890, 1892, 1904.

So we have:

numbers = (1820, 1836, 1849, 1859, 1870, 1890, 1892, 1904)

factors = (2^12, 3^6, 5^3, 7^3, 11^3, 13^3, 17^3, 43^3)

factors = (2^4, 3^2, 5, 7, 11, 13, 17, 43)^3

And the product of the numbers is: (2^4, 3^2, 5, 7, 11, 13, 17, 43)^3 = 526846320^3.

LikeLike

]]>Then the ratio of the weights W = (Elbo/Solbo) is (according to to the two correspondents):

W = (2E + 4) / (4E + 2)

W = (S + 6) / (2S + 6)

⇒ 4ES + 12E + 8S + 24 = 4ES + 24E + 2S + 12

⇒ E = S/2 + 1

And we see from the digits used, E > 4 and S > 6, and S must be even, so:

S = 8; E = 5

We can then translate the correspondents statements into decimal to get:

Eu:“There are 22 Elbos to 14 of their Solbos [W = 7/11]. The common frontier is 11 Emils”.

So:“14 Solbos weigh the same as 22 Elbos [W = 7/11]. The common frontier is 17 Somils”.

And:

“104 Somils/hour” [base 8]

= 68 Somils/hour [base 10]

= 4× (17 Somils)/hour [base 10]

= 4× (11 Emils)/hour [base 10]

= 44 Emils/hour [base 10]

= “134 Emils/hour” [base 5]

**Solution:** The Europhalian speed-limit would be expressed as: “134 Emils per hour”.

LikeLike

]]>**Run:** [ @replit ]

from enigma import (irange, sq, nsplit, diff, intersect, append, delete, subsets, printf) # model a symmetric relation class Relation(set): # check if x is related to y def __call__(self, x, y): return (x, y) in self or (y, x) in self # names names = "ALAN CARY JAMES LUCY NICK RICKY STEVE VICTOR".split() # names are related when they share a letter N = Relation((x, y) for (x, y) in subsets(names, size=2) if intersect([x, y])) # find 3-digits squares with no repeated digits sqs = dict() for i in irange(10, 31): ds = set(nsplit(sq(i))) if len(ds) == 3: sqs[i] = ds # values are related when their squares share a digit V = Relation((x, y) for ((x, sx), (y, sy)) in subsets(sqs.items(), size=2) if intersect([sx, sy])) # assign values to remaining names # names = remaining names # ss = used (name, value) pairs # vs = remaining values def solve(names, ss, vs): if not names: yield ss elif not len(names) > len(vs): # choose a value for the next name n = names[0] for (i, v) in enumerate(vs): # check values have digits in common when names have letters in common if all(N(n, n_) == V(v, v_) for (n_, v_) in ss): # solve for the remaining names yield from solve(names[1:], append(ss, (n, v)), delete(vs, [i])) # choose an age for LUCY (the youngest) n0 = "LUCY" ks = sorted(sqs.keys()) for (i, k) in enumerate(ks): # solve for the remaining names for ss in solve(diff(names, {n0}), [(n0, k)], ks[i + 1:]): # output solution for (n, v) in sorted(ss): printf("{n} -> {v} [{s}]", s=sq(v)) printf()

**Solution:** The ages are: 19, 31, 29, 16, 25, 23, 28, 27.

With squares in square brackets:

ALAN = 19 [361]

CARY = 31 [961]

JAMES = 29 [841]

LUCY = 16 [256]

NICK = 25 [625]

RICKY = 23 [529]

STEVE = 28 [784]

VICTOR = 27 [729]

LikeLike

]]>LikeLike

]]>There are many solutions without the restricted psalm and hymn number ranges.

LikeLike

]]>We can solve this puzzle using the [[ `SubstitutedExpression`

]] solver from the **enigma.py** library.

The following run file executes in 63ms. (Internal runtime of the generated program is 1.2ms).

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression --digits="1-9" # the psalm "is_prime(ABC)" "ABC < 151" # the hymns "is_prime(DEF)" "is_prime(GHI)" "DEF < GHI < 667" --answer="(ABC, DEF, GHI)"

**Solution:** Psalm 149. Hymn 263. Hymn 587.

LikeLike

]]>LikeLike

]]>In order for 3 of them to use all the digits from 1-9 then each digit can only appear in one prime, and **X**, **Y**, **Z** must be different digits.

The setter has used 3, 9, 2, so we need to find 2 primes that use the digits 1, 4, 5, 6, 7, 8, between them.

This Python program runs in 59ms. (Internal runtime is 370µs).

**Run:** [ @replit ]

from enigma import ( irange, nconcat, diff, is_prime, subsets, partitions, cproduct, unpack, printf ) # construct a 5-digit palindrome from digits X, Y, Z pal5 = unpack(lambda X, Y, Z: nconcat(X, Y, Z, Y, X)) # construct palindromic primes from digits <ds> def primes(ds): for ss in subsets(ds, size=len, select="P"): if is_prime(pal5(ss)): yield ss # digits used in the first prime p0 = (3, 9, 2) assert is_prime(pal5(p0)) # split the remaining digits for the other 2 primes for dss in partitions(diff(irange(1, 9), p0), 3): for (p1, p2) in cproduct(primes(ds) for ds in dss): printf("{p0} {p1} {p2}", p0=pal5(p0), p1=pal5(p1), p2=pal5(p2))

**Solution:** The other palprimes are: 16561 and 78487.

In fact as the palprimes must end (and begin) with one of 1, 3, 7, 9, we can see that the two numbers we have to find must be of the form 1???1 and 7???7. And we just need to work out how to arrange the digits 4, 5, 6, 8 in the middle of them.

And we can make a slightly shorter program based on this observation:

from enigma import (nconcat, is_prime, subsets, printf) # construct a 5-digit palindrome from digits X, Y, Z pal5 = lambda X, Y, Z: nconcat(X, Y, Z, Y, X) # digits used in the first prime p0 = pal5(3, 9, 2) assert is_prime(p0) # insert digits 4, 5, 6, 8 in 1???1, 7???7 for (A, B, C, D) in subsets((4, 5, 6, 8), size=len, select="P"): (p1, p2) = (pal5(1, A, B), pal5(7, C, D)) if is_prime(p1) and is_prime(p2): printf("{p0} {p1} {p2}")

LikeLike

]]>This Python program looks for the longest gap between consecutive dates that both occur on Tuesday and the same day of the month (but different months). It runs in 60ms.

**Run:** [ @replit ]

from datetime import (date, timedelta) from enigma import (inc, repeat, group, tuples, Accumulator, printf) # generate possible birthdates def generate(): # start from the first Tuesday in 1854 for d in repeat(inc(timedelta(days=7)), date(1854, 1, 3)): # calculate age in 1976 a = 1976 - d.year if a < 65: break yield d # group candidate birthdates by day of month g = group(generate(), by=(lambda d: d.day)) # find maximal distance dates r = Accumulator(fn=max, collect=1) # consider possible days of month for (k, ds) in g.items(): # and possible adjacent dates for (A, D) in tuples(ds, 2): t = D - A r.accumulate_data(t, (A, D)) t = r.value printf("max gap = {t} days", t=t.days) for (A, D) in r.data: # A was born in the winter, say Dec, Jan, Feb if A.month in {12, 1, 2} and A.month != D.month: printf("-> A={A}; D={D}")

**Solution:** David was born on Tuesday, 31st August 1880.

So David is 96 years old at the time the puzzle was set.

Andrew was born on Tuesday, 31st December 1878.

So he is 97 years old at the time the puzzle was set.

The longest possible gap between the birthdates is 609 days, which gives the following candidates:

A=1855-07-31; D=1857-03-31; ages = 121 & 119

A=1878-12-31; D=1880-08-31; ages = 97 & 96

A=1883-07-31; D=1885-03-31; ages = 93 & 91

But as A is born in the winter only the second of these candidates is viable.

Other candidates outside the age range considered are:

A=1850-12-31; D=1852-08-31; ages = 125 & 124

A=1918-12-31; D=1920-08-31; ages = 57 & 56

LikeLike

]]>Since Watson says “Not square” I am assuming we want the scenario where the lowest number in the grid (A) is not a square (i.e. it is a cube of a non-square).

The following Python program runs in 81ms. (Internal runtime is 29ms).

**Run:** [ @replit ]

from enigma import ( defaultdict, irange, sq, cb, is_square, cproduct, union, diff, subsets, update, peek, intersect, join, printf ) # we have 3 disjoint categories of 4-digit numbers: # cat1 = "square of a square" (i.e. a power of 4) cat1 = list(str(sq(sq(i))) for i in irange(6, 9)) # cat2 = "cube of a non-square" cat2 = list(str(cb(i)) for i in irange(10, 21) if not is_square(i)) # cat3 = "square of a non-square" cat3 = list(str(sq(i)) for i in irange(32, 99) if not is_square(i)) # sets of isolated cells iso0 = [(0, 2, 6), (0, 2, 7), (0, 2, 8), (0, 5, 6), (0, 6, 8)] # with cell 0 isox = [(1, 6, 8), (2, 3, 8), (2, 6, 8)] # without cell 0 # collect candidate grids g = defaultdict(list) # fill out cells <cs> in grid <ns> with values from <cat> # making sure digit content (including <ds>) does not exceed 9 def fill(ns, cs, cat, ds): # are we done? if not cs: yield (ns, ds) else: # fill out the next cell i = cs[0] lo = peek((ns[j] for j in irange(i - 1, 0, step=-1) if ns[j]), default='0000') hi = peek((ns[j] for j in irange(i + 1, 8) if ns[j]), default='AAAA') for n in cat: if not (n > lo): continue if not (n < hi): break ds_ = ds.union(n) if not (len(ds_) > 9): yield from fill(update(ns, [i], [n]), cs[1:], cat, ds_) # choose cat1, cat2 cells; cell 0 (A) is not a square for (cs1, cs2) in cproduct([isox, iso0]): # remaining cells are cat3 cs = union([cs1, cs2]) if len(cs) != 6: continue cs3 = diff(irange(0, 8), cs) # fill out the cells ns0 = [None] * 9 # choose cat1 values for vs1 in subsets(cat1, size=3): ds1 = union(vs1) if len(ds1) > 9: continue ns1 = update(ns0, cs1, vs1) # fill out cat2 and cat3 values for (ns2, ds2) in fill(ns1, cs2, cat2, ds1): for (ns3, ds3) in fill(ns2, cs3, cat3, ds2): if len(ds3) == 9: # group grids by (<cell 0>, <cat1 positions>) g[(ns3[0], cs1)].append(ns3) # consider the groups for (k, vs) in g.items(): # can we determine at least 5 additional values? xs = intersect(vs) if len(xs) < 6: continue # output solution (A, cs1) = (k[0], join("ABCDEFGHI"[x] for x in k[1])) printf("(A={A}, cat1={cs1}) -> {xs}", xs=join(sorted(xs), sep=", ", enc="()"))

**Solution:** Watson knew for certain the grid contained: 1331, 2401, 4096, 4913, 5832, 6561.

Watson was told that the lowest number was 1331, and the “squares of squares” were in positions: C, D, I.

As 1331 = 11³, Watson can deduce that the cubes are in positions: A, F, G. And the squares of non-squares are in positions: B, E, H.

And so he can deduce the cubes are: A=1331, F=4913, G=5832. And the powers of 4 are: C=2401, D=4096, I=6561.

Together these 6 values use all the digits except 7.

So the grid can be completed with any squares of non-squares that don’t use the digit 7, and fit in the appropriate position.

The candidates are:

B = 1369, 1444, 1521, 1600, 1681, 1849, 1936, 2025, 2116, 2209, 2304

E = 4225, 4356, 4489, 4624, 4900

H = 5929, 6084, 6241, 6400

So there are 220 possible grids.

Here is one possibility:

red = squares of squares

blue = cubes of non-squares

yellow = squares of non-squares

LikeLike

]]>In my program above, in order to get a total distance that is a whole number we require the individual segments between consecutive pins in the stringing to be a whole number.

This is a reasonable assumption. As we can show that if we have a collection of positive integers, and at least one of them has a square root that is irrational, then the sum of the square roots of the entire collection is irrational. (See below [*]).

However, this does not mean we cannot construct stringings that are very close to an integer value using segments that are not themselves integers. And this lets us find solutions that are much longer than the published solution.

For example here is a stringing on the 12cm × 8 cm grid that has a total length of 440.999925 cm (i.e. 750 *nanometres* short of 441 cm). It would be practically impossible to tell this apart from a 441 cm length of wire.

Note that in this construction every pin is visited.

If we want more accuracy we can get within 6nm of 438 cm.

[*] The core of the proof is:

Suppose *x, y* are integers, such that at least one of *√x* and *√y* are irrational.

Then *(x − y)* is also an integer, and:

(x − y) = (√x + √y)(√x − √y)

If *(√x + √y)* is rational (= *p*), then:

(√x − √y) = (x − y)/(√x + √y) is also rational (= q)

But then:

√x = (p + q)/2 is rational; and

√y = (p − q)/2 is rational

Which is a contradiction, hence the sum *(√x + √y)* is irrational.

LikeLike

]]>LikeLike

]]>`SubstitutedExpression.split_sum()`

]] solve from the It runs in 57ms. (Internal runtime is 5.8ms).

**Run:** [ @replit ]

from enigma import (SubstitutedExpression, nsplit, substitute, join, map2str, printf) # create the alphametic puzzle p = SubstitutedExpression.split_sum("{GIRL} + {BOY} = {LOVE}", answer="{GIRL} - {BOY}") # solve the puzzle for (s, ans) in p.solve(verbose=0): # answer should be 3 digits ds = nsplit(ans) if len(ds) != 3: continue # map digits to symbols r = dict((v, k) for (k, v) in s.items() if k in p.symbols) # find the answer as a word w = join(r.get(d, '?') for d in ds) # output potential answers LOVER = substitute(s, "LOVER") printf("{ans}={w} -> LOVER={LOVER} / {r}", r=map2str(r, sep=" ", enc=""))

From the output we find there are 4 candidate solutions:

answer = 586 →

IEY

answer = 715 →YGI

answer = 879 →BI?

answer = 914 →VG?

Only the third of these can form a normal word.

By assigning an unused letter to 9, we can make 879 read as **BID**, **BIN**, **BIT**, **BIZ**. (None of which are particularly appropriate).

**Solution:** **LOVER** = 26054.

LikeLike

]]>rhy = [[1, 4, 6, 7, 8, 10, 11],[1, 2, 8],[1, 2, 5, 6,10]] patts=('DO','KL','OM') ntotxt = lambda k : [[patts[j[0]],j[1]] for j in k] #makes print out more intelligible using labels score = lambda k : sorted([y+x[1]-1 for x in k for y in rhy[x[0]]]) # creates list of beat numbers filled gap = lambda k : sorted([x for x in range(1,len(k)+1) if x not in k]) # identifies gaps in beat sequence for n in range(2,8): # iterate for missing value in KL rhy[1][1] = n seq=[[0,1]] #Initialise seq. Entries in seq are in format:[pattern no.,starting beat] while seq: beats=score(seq) if dup:=len(beats)-len(set(beats)): #if overlap of beats found, go back in sequence while seq and seq[-1][0]==2: seq.pop() if not seq:break seq[-1][0]+=1 #try next pattern in exisiting sequence else: #no overlaps in current sequence if g:=gap(beats): # start next pattern at first gap in sequence seq.append([0,g[0]]) else: # if no gap and no overlaps, solution has been found print('solution found: ',ntotxt(seq), ' n=',n) exit() print('options exhausted for n=' ,n)

LikeLike

]]>2

L+ 4F+ 8X= 50

And if he started out by buying a total of **N** packs, and selling some (at 25% markup) to cover the cost of the initial purchase, then we have:

(5/4)(

N− (X+F+L)) =N

⇒N= 5(X+F+L)

And if he initially purchased **n** plants of each type we have:

N=n/8 +n/4 +n/2 = (7/8)n

This Python program runs in 56ms. (Internal runtime is 210µs).

**Run:** [ @replit ]

from enigma import (express, div, printf) # consider the make up of the 50 remaining plants # L = lily packs, F = floating packs; X = oxygenating packs # 2L + 4F + 8X = 50 for (L, F, X) in express(50, (2, 4, 8)): # there are fewer lilies than any other type of plant if not (2 * L < min(4 * F, 8 * X)): continue # calculate the initial number of packs bought N = 5 * (L + F + X) # calculate the initial number of plants of each type bought n = div(8 * N, 7) if n is None: continue # output solution (final number of lily plants) printf("{x} lily plants [L={L} F={F} X={X} -> N={N} n={n}]", x=2 * L)

**Solution:** 14 of the remaining 50 plants were lilies.

Initially John bought 80 of each type of plant (240 plants in total) = 10 packs of oxygenators + 20 packs of floating plants + 40 packs of lilies.

Of these 70 packs he sold 8 packs of oxygenators + 15 packs of floating plants + 33 packs of lilies. Leaving him with 14 packs.

The sale of these 56 packs at a 25% markup exactly met the initial cost of the 70 packs.

Leaving John with 2 packs of oxygenators (= 16 plants) + 5 packs of floating plants (= 20 plants) + 7 packs of lilies (= 14 plants). A total of 50 plants.

LikeLike

]]>@Hugh: I thought originally that everyone had a “before” and “after” value. But unless you have a “future” value for D (that comes after “after”), then you don’t get the published solution.

LikeLike

]]>I think we have to assume that what the doorknob polisher “used to get” is what he currently gets, and that all the new wages come into effect next month.

LikeLike

]]>I think the wording in this puzzle was slightly confusing.

I am assuming that an unqualified wage referred to on the notice are the new wage that each employee is to receive immediately.

However there is also a future wage referred to (the Bottle Washer will receive a 10% rise next month), and a previous wage (the Doorknob polisher receives 30% more than he used to).

This time I used constraint satisfaction solver, so here is a *MiniZinc* model to solve the puzzle. It uses my **minizinc.py** library to provide a shortcut for allowing multiple variables with the same domain to be declared in one statement.

%#! python3 -m minizinc use_embed=1 include "globals.mzn"; % wages by name: A, B, C, D, E {var("200..600", "ABCDE")}; % wages by job: DK, DP, BW, WO, WK {var("200..600", ["DK", "DP", "BW", "WO", "WK"])}; % the sets of values are the same constraint sort([A, B, C, D, E]) = sort([DK, DP, BW, WO, WK]); % 1. A gets more than D constraint A > D; % 2. In the future BW is to get a 10% increase ... constraint (BW * 110) mod 100 = 0; % ... and E (now) gets 12% more than this future amount constraint (BW * 112 * 110) mod (100 * 100) = 0; constraint E = (BW * 112 * 110) div (100 * 100); % 3. DP gets a 30% increase constraint (DP * 100) mod 130 = 0; % 4. C is to get 12 less than 20% more than WO constraint (WO * 120) mod 100 = 0; constraint C = (WO * 120) div 100 - 12; % 6. DK is to get 5% more than 10% less than B constraint (B * 90 * 105) mod (100 * 100) = 0; constraint DK = (B * 90 * 105) div (100 * 100); solve satisfy;

And you can then run it like this:

% python3 minizinc.py use_embed=1 teaser177.mzn A=378 B=400 C=468 D=250 E=308 DK=378 DP=468 BW=250 WO=400 WK=308

**Solution:** The jobs and wages are as follows:

Alf:Doorkeeper; £378

Bert:Welfare Officer; £400

Charlie:Doorknob Polisher; £468 (was £360)

Duggie:Bottle Washer; £250 (future £275)

Ernie:Worker; £308

The originally published puzzle was pre-decimalisation, and gave the salaries in shillings per week, rather than pounds per year, but used the same values.

LikeLike

]]>We can now call the [[ `SubstitutedExpression.split_sum`

]] solver directly from a run file. Giving us a solution that is both short and fast.

The following run file executes in 68ms, and the internal runtime of the generated program is just 139µs.

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression.split_sum --invalid="0,ADEGKLNRS" # none of the digits are 0 --answer="ALE" "{DRANK} + {GLASS} = {LAGER}" --extra="{G} > {D}"

LikeLike

]]>It runs in 54ms. (Internal runtime is 372µs).

**Run:** [ @replit ]

from enigma import (irange, inf, update, peek, printf) # the rhythm patterns D = [1, 4, 6, 7, 8, 10, 11] K = [1, None, 8] # a beat in the range [2, 7] is missing O = [1, 2, 5, 6, 10] # disjoint union of two sets (or None) def disjoint_union(ss, xs): ss = set(ss) for x in xs: if x in ss: return ss.add(x) return ss # fill out patterns from <ps> with an accent on every beat # starting from <s> def solve(ps, s=1, vs=[], bs=set()): # we are done if there are no missing beats if bs and len(bs) == max(bs): yield vs else: # find the first missing beat b = peek(k for k in irange(s, inf) if k not in bs) # and choose a pattern to start here for (k, pattern) in ps.items(): bs_ = disjoint_union(bs, (x + b - 1 for x in pattern)) if bs_ is not None: yield from solve(ps, s + 1, vs + [(k, b)], bs_) # consider the missing beat in pattern K for k in irange(2, 7): for vs in solve(dict(D=D, K=update(K, [1], [k]), O=O)): printf("k={k} vs={vs}")

**Solution:** Kluc has an additional accent on beat 5. The order of the patterns as: **OOKKDDK**.

The composition lasts for 33 beats (or a multiple of 33 beats). The patterns being:

O@ 1: (1, 2, 5, 6, 10)

O@ 3: (3, 4, 7, 8, 12)

K@ 9: (9, 13, 16)

K@ 11: (11, 15, 18)

D@ 14: (14, 17, 19, 20, 21, 23, 24)

D@ 22: (22, 25, 27, 28, 29, 31, 32)

K@ 26: (26, 30, 33)

Which covers all beats from 1 – 33.

In order for the composition to end after 33 beats with each beat being accented by exactly one of the instruments, we need pattern **K** to end after the last accented beat. Pattern **D** can have 0 or 1 non-accented beats after the last accented beat. And pattern **O** could have up to 21 non-accented beats after the last accented beat.

LikeLike

]]>This puzzle was also published in *Significance* magazine (from The Royal Statistical Society) in the December 2021 issue [@link] (with the solution appearing in the February 2022 issue).

I was able to get access to the online version of the magazine via my local library.

Extract from *Significance*, February 2022, p8:

The answer we were looking for is 4, 4, 5, 5, 6. We’ll hand over to reader Richard Jenkins to explain the solution.

“Each time the dice are rolled, there are five possible outcomes (sums) with the following probabilities:

P(sum is 2) = 1/36

P(sum is 3) = 1/9

P(sum is 4) = 5/18

P(sum is 5) = 1/3

P(sum is 6) = 1/4.Even though a sum of 5 has the highest probability on any single roll of the dice, it is very unlikely that a sum of 5 will be rolled each time when the dice are rolled multiple times. It is more likely that a variety of sums will appear”.

This is an important point. Many readers wrote in to suggest a bingo card of 5, 5, 5, 5, 5. But, as Kyla E. Chasalow and Scott D. Chasalow explain in their submission – a detailed, eight-page

tour de forceof a solution – choosing all 5s “fails to take into account that bingo cards are unordered”. Roll the dice five times and there are 120 different roll sequences that will yield a win for the card 2, 3, 4, 5, 6. But there is only one way to win with a card of 5s.Back to Jenkins: “By reviewing the various combinations, we see there are 126 possible bingo cards (since the order of the numbers on the card does not matter). But which bingo card is the most likely to match all numbers in exactly five rolls? By crunching the numbers, I found that the combination of 4, 4, 5, 5, 6 has the highest probability of success”.

Jenkins gives the four bingo cards with the highest probabilities of winning in five rolls as:

P(4, 4, 5, 5, 6) = 0.064300412

P(4, 5, 5, 6, 6) = 0.05787037

P(3, 4, 5, 5, 6) = 0.051440329

P(4, 5, 5, 5, 6) = 0.051440329Chasalow and Chasalow also identify 4, 4, 5, 5, 6 as the bingo card with the highest overall probability of winning in five rolls, explaining that: “This card reflects a trade-off between choosing high-probability outcomes and choosing different outcomes so that there are more ways to win”.

— The Royal Statistical Society 2022

LikeLike

]]>LikeLike

]]>`SubstitutedExpression`

]] solver from the The following run file executes in 63ms. (The internal runtime of the generated program is 424µs).

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression --distinct="" --answer="ABCDE" "cb(A) + cb(B) + cb(C) = ABC" "factorial(C) + factorial(D) + factorial(E) = CDE"

**Solution:** The code is: 37145.

And we have:

3³ + 7³ + 1³ = 371

1! + 4! + 5! = 145

Although not a condition of the puzzle, it turns out that the digits in the code are all different.

LikeLike

]]>LikeLike

]]>Yes. I’ve updated my program to allow you to specify the number of faces to be coloured:

0 (or 8) → 1 octahedron

1 (or 7) → 1 octahedron

2 (or 6) → 3 octahedra

3 (or 5) → 3 octahedra

4 → 7 octahedra

Which gives the 23 essentially different colourings.

LikeLike

]]>LikeLike

]]>The seven variants are then, for example:

1. All four upper vertices.

2 to 5. Three upper vertices and each in turn of the lower ones.

6. Two adjacent upper vertices and their diametric opposites.

7. Two opposite upper vertices and their diametric opposites.

Any further patterns turn out to be repeats, rotated or reflected in one way or another (a cube has many axes of symmetry).

If we allow ourselves any number of gold faces, from 0 to 8, then I think there are 23 variants in all.

LikeLike

]]>I already have some code that deals with the rotations of the faces of the cube (see: **Teaser 2835**), so I used that to generate the rotations of an octahedron.

We then consider all possible octahedra with 4 faces coloured gold and 4 coloured silver, and then remove those that are equivalent by rotation.

The following Python program runs in 53ms. (Internal run time is 2.0ms).

**Run:** [ @replit ]

from enigma import (irange, subsets, arg, printf) from cube import Cube # make a cube with faces labelled in powers of 2 fs = list(1 << i for i in irange(0, 5)) cube = Cube(faces=fs) # extract the vertices from a cube def vertices(cube): (U, D, F, B, L, R) = cube.faces return ( U + F + L, U + F + R, U + B + R, U + B + L, D + F + L, D + F + R, D + B + R, D + B + L, ) # map vertex values to indices m = dict((v, k) for (k, v) in enumerate(vertices(cube))) # construct the rotations of the faces of an octahedron rot8s = list() for x in cube.rotations(): rot8s.append(list(m[v] for v in vertices(x))) # now on with the puzzle ... # canonical form of a colouring of the faces of the octahedron def canonical(fs): return min(tuple(fs[r[i]] for (i, _) in enumerate(fs)) for r in rot8s) # colour k of the faces k = arg(4, 0, int) # initial colouring fs = list(int(i < k) for i in irange(0, 7)) # collect all possible permutations of the faces rs = set(canonical(s) for s in subsets(fs, size=len, select="mP")) printf("{k} faces -> {n} different octahedra", n=len(rs))

**Solution:** There are 7 different octahedra.

LikeLike

]]>@Hugh: Yes for the original wording I found a maximal solution with 24 links. For this revised version there are only 10 links. The rectangle was the same in both cases.

LikeLike

]]>The puzzle does not seem to be well thought out. That the nails are thin clearly means that we are to ignore their diameter (and the thickness of the wire). But with centimetres as the unit they are not nails at all but pins, and it’s fine fuse wire. Inches would have been somewhat less unrealistic.

I also feel that if the “artist” had allowed himself more nails, the resulting pattern could have been more interesting. For example, 12 occurs in the Pythagorean triangles (9, 12, 15) and (12, 16, 20) as well as (5, 12, 13), which would presumably give more scope. But the length + width of the rectangle must not exceed 20, so we are much restricted.

LikeLike

]]>For this version I am assuming we want to find the maximum achievable total length of wire between the nails that can be made using a rectangle constructed from up to 40 nails, with the wire strung between nails where no nail is used more than once and consecutive nails are on different edges of the rectangle (i.e. each linear section of wire must cross part of the interior of the rectangle, and be a whole number of centimetres long), *and* no linear section of wire is parallel to the edges of the rectangle.

You can think of the wire being available in various stiff lengths with looped ends, but these individual links can only ever fit between nails that are a whole number of centimetres apart. We wish to make a chain of these wire links, where each link goes from the end of the previous link to a currently unoccupied nail. What is the longest possible chain that can be made, on a rectangle whose perimeter is constructed from no more than 40 nails spaced 1 cm apart, where each link must cross the interior of the rectangle *not* parallel to the edges of the rectangle, and the start and end of the complete chain are on different nails.

This Python program runs in 104ms.

**Run:** [ @replit ]

from enigma import (irange, ihypot, chain, Accumulator, printf) # generate possible <x> by <y> rectangles, using (up to) <n> nails def generate(n): for y in irange(2, n // 4): for x in irange(2, (n - 2 * y) // 2): yield (x, y) # find paths by adding nails to <vs> def solve(x, y, nails, vs, d=0): # return the current path yield (d, vs) # choose the next nail (x1, y1) = vs[-1] for v in nails: (x2, y2) = v (dx, dy) = (x2 - x1, y2 - y1) if dx == 0 or dy == 0: continue dd = ihypot(dx, dy) if dd is not None: yield from solve(x, y, nails.difference([v]), vs + [v], d + dd) # collect maximal distance paths r = Accumulator(fn=max, collect=1) # consider possible rectangles for (x, y) in generate(40): # collect possible edge nails nails = set() for i in irange(0, x - 1): nails.update([(i, 0), (x - i, y)]) for i in irange(0, y - 1): nails.update([(0, y - i), (x, i)]) # start in the bottom/left quadrant botm = ((i, 0) for i in irange(0, x // 2)) left = ((0, i) for i in irange(1, y // 2)) for v in chain(botm, left): for (d, vs) in solve(x, y, nails.difference([v]), [v]): r.accumulate_data(d, (x, y, vs)) # output solution(s) printf("max wire length = {r.value} cm") for (x, y, vs) in r.data: printf("-> {x} cm by {y} cm grid {vs}")

**Solution:** The rectangle was 12 cm × 8 cm. The total length of wire used was 102 cm.

Here is a diagram of the maximal length layout:

LikeLike

]]>Using a different (and more likely) interpretation of the problem (see below), where each edge of the rectangle may be visited many times (and allowing the corner nails to be used), but each linear section of wire is a whole number of centimetres, we can get a much longer wire.

To be clear: I am assuming we want to find the maximum achievable total length of wire between the nails that can be made using a rectangle constructed from up to 40 nails, with the wire strung between nails where no nail is used more than once and consecutive nails are on different edges of the rectangle (i.e. each linear section of wire must cross part of the interior of the rectangle, and be a whole number of centimetres long).

Here is a quick program to explore that (although there may be some duplication in the patterns found).

It runs in 942ms.

**Run:** [ @replit ]

from enigma import (irange, ihypot, chain, Accumulator, printf) # generate possible <x> by <y> rectangles, using (up to) <n> nails def generate(n): for y in irange(2, n // 4): for x in irange(2, (n - 2 * y) // 2): yield (x, y) # find paths by adding nails to <vs> def solve(x, y, nails, vs, d=0): # return the current path yield (d, vs) # choose the next nail (x1, y1) = vs[-1] for v in nails: (x2, y2) = v (dx, dy) = (x2 - x1, y2 - y1) if dx == 0 and (x1 == 0 or x1 == x): continue if dy == 0 and (y1 == 0 or y1 == y): continue dd = ihypot(dx, dy) if dd is not None: yield from solve(x, y, nails.difference([v]), vs + [v], d + dd) # collect maximal distance paths r = Accumulator(fn=max, collect=1) # consider possible rectangles for (x, y) in generate(40): # collect possible edge nails nails = set() for i in irange(0, x - 1): nails.update([(i, 0), (x - i, y)]) for i in irange(0, y - 1): nails.update([(0, y - i), (x, i)]) # start in the bottom/left quadrant botm = ((i, 0) for i in irange(0, x // 2)) left = ((0, i) for i in irange(1, y // 2)) for v in chain(botm, left): for (d, vs) in solve(x, y, nails.difference([v]), [v]): r.accumulate_data(d, (x, y, vs)) # output solution(s) printf("max wire length = {r.value} cm") for (x, y, vs) in r.data: printf("-> {x} cm by {y} cm grid {vs}")

**Solution:** The rectangle was 12 cm × 8 cm. The total length of wire used was 258 cm.

Here is a diagram of one maximal length arrangement (there are several):

LikeLike

]]>@Brian: I was assuming that each edge of the rectangle was only used once (and the corners not at all). But the puzzle text only says *consecutive* nails are on different edges, so if we can visit edges more than once then we can use much more wire. (And get a more artful display into the bargain).

In this case I assumed distance between each consecutive pair of nails was an exact whole number of cm, so that the total length of the wire is as well.

(Although it still seems like we would need to know the size of the rectangle the artist had constructed before we could find the maximum length of wire he could use).

LikeLike

]]>LikeLike

]]>It seems we would need to know the dimensions of the rectangle before we determined the maximum length of wire. I considered all rectangles that can be constructed using 40 nails, and found the maximum possible length of wire that touches each edge of the rectangle no more than once.

This Python program tries all possible rectangles, and all possible stringings.

It runs in 275ms.

**Run:** [ @replit ]

from enigma import (irange, inf, subsets, tuples, hypot, intr, Accumulator, printf) # generate possible <x> by <y> rectangles, using <n> nails def generate(n): for y in irange(3, inf): x = (n - 2 * y) // 2 if x < 3: break yield (x, y) # calculate length of wire required between points <vs> def dist(vs): d = 0 for ((x1, y1), (x2, y2)) in tuples(vs, 2): d += hypot(x2 - x1, y2 - y1) if abs(d - intr(d)) < 1e-6: return d # collect maximal stringings r = Accumulator(fn=max, collect=1) # consider possible rectangles for (x, y) in generate(40): # choose top, bottom nails for (x1, x2) in subsets(irange(1, x - 1), size=2, select="R"): (T, B) = ((x1, y), (x2, 0)) # choose left, right nails for (y1, y2) in subsets(irange(1, y - 1), size=2, select="P"): (L, R) = ((0, y1), (x, y2)) # calculate possible lengths for vs in [(T, R, B, L), (T, R, L, B), (T, B, R, L)]: d = dist(vs) if d is None: continue printf("[({x}, {y}) {d:g} = {vs}]") r.accumulate_data(d, (x, y, vs)) printf("max wire length = {r.value:g} cm") for (x, y, vs) in r.data: printf("-> {x} cm by {y} cm grid {vs}")

**Solution:** The rectangle was 6 cm × 14 cm, and the wire was 37 cm long.

This program finds what I assume is the required answer (where each section of the wire is an integer length). But by changing the tolerance at line 15 we can find a longer wire that is sufficiently close to an exact whole number of centimetres that it would be physically impossible to determine that it wasn’t.

So we can create a stringing on a 6 cm × 14 cm rectangle that uses 40.0007 cm of wire.

And also a stringing on a 4 cm × 16 cm rectangle that uses 44.99 cm of wire.

LikeLike

]]>LikeLike

]]>We can use the [[ `SubstitutedExpression`

]] solver from the **enigma.py** library to solve this puzzle.

The following run file executes in 65ms. (Internal runtime of the generated code is 1.1ms)

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression --distinct="" --digits="1-9" --answer="ABCD" # The following multiplied together give the value of the PIN # (i) the sum of the digits; # (ii) the mean of the digits (= (i) / 4); # (iii) the product of the digits "sq(A + B + C + D) * (A * B * C * D) == 4 * ABCD" # the PIN is odd "D % 2 = 1"

**Solution:** The PIN is: 1715.

LikeLike

]]>1 × 44 or 4 × 26 or 8 × 17.

However, I maintain that those are the sizes of the arrays of square cells;

the grids (of lines) are 2 × 45, 5 × 27, and 9 × 18.

LikeLike

]]>1 pair that are distance

xapart

2 pairs that are distance(x − 1)apart

3 pairs that are distance(x − 2)apart

…

xpairs that are distance 1 apart

Giving a total of *tri(x)* pairs on that line, and there are *(y + 1)* horizontal lines.

So the total number of horizontal pairs is:

h(x, y) = (y + 1) tri(x)

Similarly the total number of vertical pairs is:

v(x, y) = (x + 1) tri(y)

And so the total number of pairs is:

pairs(x, y) = h(x, y) + v(x, y)

pairs(x, y) = (y + 1)x(x + 1)/2 + (x + 1)y(y + 1)

pairs(x, y) = (x + 1)(y + 1)(x + y)/2

This Python program runs in 54ms. (Internal runtime is 488µs).

**Run:** [ @replit ]

from enigma import (irange, inf, div, decompose, printf) # count pairs on an x by y grid pairs = lambda x, y: div((x + 1) * (y + 1) * (x + y), 2) # check the 3x2 grid gives 30 pairs assert pairs(3, 2) == 30 # generate (x, y, pairs(x, y)) values def generate(): # consider increasing x + y values for t in irange(2, inf): for (x, y) in decompose(t, 2, increasing=1, sep=0, min_v=1): yield (x, y, pairs(x, y)) # find the answers a = b = 0 for (x, y, n) in generate(): # (a) look for grids with 1998 pairs if n == 1998: printf("(a) {x} x {y} -> {n}") a = 1 # (b) looks for grid in the range [2015, 2024] if 2014 < n < 2025: printf("(b) {x} x {y} -> {n}") b = 1 # are we done? if a and b: break

**Solution:** (a) The 1998 diary had a 2 × 35 grid; (b) The next special diary is for 2016.

We have:

1998 = pairs(2, 35)

2016 = pairs(5, 23) = pairs(11, 13)

Alternatively we can start with the required number of pairs *n* and produce possible *(x, y)* grids, by considering the divisors of *n*.

Which gives us a shorter program.

This Python program runs in 56ms. (Internal runtime is 622µs).

**Run:** [ @replit ]

from enigma import (divisors_pairs, irange, printf) def solve(n): for (a, b) in divisors_pairs(2 * n): for (x, y) in divisors_pairs(b - a - 1): if x + y == a: yield (x, y) # years to investigate qs = { 'a': [1998], 'b': irange(2015, 2024), } # solve the puzzle for k in sorted(qs.keys()): for n in qs[k]: for (x, y) in solve(n): printf("({k}) {x} x {y} -> {n}")

LikeLike

]]>from enigma import nsplit, is_prime # form lists of 2, 3 and 4 digit primes # with non-repeating digits to check digit splits pr2 = [n for n in range(11, 99) if is_prime(n) \ and len(set(str(n))) == 2] pr3 = [n for n in range(100, 999) if is_prime(n) \ and len(set(str(n))) == 3] pr4 = [n for n in range(1000, 9999) if is_prime(n) \ and len(set(str(n))) == 4 ] # check if (town/area) digits split is (3, 3) for town in pr3: A, B, C = nsplit(town) for area in pr3: if area == town:continue D, E, F = nsplit(area) if len(set((A, B, C, D, E, F))) != 6:continue roads = town + area - 1 if roads in pr4: continue if roads < 1000 or roads > 9999:continue R1, R2, R3, R4 = nsplit(roads) if len(set((A,B,C,D,E,F,R1,R2,R3,R4))) == 10: if town < area: print(f"1.The three numbers were {town}, {area} and {roads}.") # check if (town/area) digits split is (2,4) for town2 in pr2: a, b = nsplit(town2) for area2 in pr4: c, d, e, f = nsplit(area2) if len(set((a, b, c, d, e, f))) == 6: roads2 = town2 + area2 - 1 if roads2 < 1000 or roads2 > 9999: continue R5, R6, R7, R8 = nsplit(roads2) if len(set((a,b,c,d,e,f,R5,R6,R7,R8))) == 10: print(f"2.The three numbers were {town2}, {area2} and {roads2}.")

LikeLike

]]>It runs in 55ms. (Internal runtime is 790µs).

**Run:** [ @replit ]

from enigma import (subsets, printf) # Belfast, Cardiff, Edinburgh, London cities = "BCEL" # assign North and East values (approximate latitude and longitude) N = dict(C=51.5, L=51.5, B=54.6, E=56.0) E = dict(B=-5.9, C=-3.2, E=-3.2, L=-0.1) # choose cities of birth for the men for (mA, mB, mC, mD) in subsets(cities, size=len, select="P"): # choose cities of birth for the women for (fA, fB, fC, fD) in subsets(cities, size=len, select="P"): # "Mr C and Mrs D were twins" (presumably born in the same place) if not (mC == fD): continue # "Mr B was the only man to marry a woman born N of him" if not (N[fB] > N[mB]): continue if any(N[f] > N[m] for (m, f) in zip((mA, mC, mD), (fA, fC, fD))): continue # choose cities for marriages for (wA, wB, wC, wD) in subsets(cities, size=len, select="P"): # no-one was married in the city of their birth if any(w == m or w == f for (m, f, w) in zip((mA, mB, mC, mD), (fA, fB, fC, fD), (wA, wB, wC, wD))): continue # "Mrs A is the only woman married east of her birth" if not (E[wA] > E[fA]): continue if any(E[w] > E[f] for (f, w) in zip((fB, fC, fD), (wB, wC, wD))): continue # "Mr A is the only man married south of his birth" if not (N[mA] > N[wA]): continue if any(N[m] > N[w] for (m, w) in zip((mB, mC, mD), (wB, wC, wD))): continue printf(" A B C D") printf("husband = {mA} {mB} {mC} {mD}") printf("wife = {fA} {fB} {fC} {fD}") printf("wedding = {wA} {wB} {wC} {wD}") printf()

**Solution:** The Carters were married in Belfast.

The full solution is:

Archer:husband = Edinburgh; wife = Belfast; married = London

Brewer:husband = London; wife = Edinburgh; married = Cardiff

Carter:husband = Cardiff; wife = London; married = Belfast

Drew:husband = Belfast; wife = Cardiff; married = Edinburgh

LikeLike

]]>Most unlikely that there would be a four-digit number of towns and only a two-digit number of regions between the roads, or vice versa. In any case, three plus three quickly yields a solution.

I’ll spare you my program code, but Basic’s ability to handle strings was useful in determining whether all ten digits are present.

LikeLike

]]>Using the formula:

E = p + q − 1

where *p* and *q* are primes, and *(E, p, q)* is pandigital, we see that *E* must be 4-digits, so *p* and *q* can only be (2, 4) or (3, 3) digits.

And without further planarity checks there is only one candidate solution, which we can find using the [[ `SubstitutedExpression()`

]] solver from the **enigma.py** library.

The following Python program runs in 83ms. (Internal runtime is 28.4ms)

**Run:** [ @replit ]

from enigma import (SubstitutedExpression, sprintf as f, printf) # symbols for the digits (terms, result) = ("ABCDEF", "GHIJ") # consider (2,4) and (3, 3) digit terms for i in (2, 3): (x, y) = (terms[:i], terms[i:]) exprs = [ f("is_prime({x})"), f("is_prime({y})"), f("{x} + {y} - 1 = {result}") ] if len(x) == len(y): exprs.append(f("{x} < {y}", x=x[0], y=y[0])) p = SubstitutedExpression(exprs, answer=f("({x}, {y}, {result})")) for (s, ans) in p.solve(verbose=0): printf("{ans}")

LikeLike

]]>I stopped programming when the run time got under 10ms.

NB The final P4 list is always logically empty, I didn’t remove the code to process 4-digit number of towns .

from itertools import combinations # formula E = V + A - 1 # V = the number of towns # A = the number of enclosed areas # E = the number of roads sols = set() P = [3, 5, 7] P += [x for x in range(11, 33, 2) if all(x % p for p in P)] P += [x for x in range(33, 1000, 2) if all(x % p for p in P)] # if V has 3 digits then E must be 1xxx # E must have at least 4 digits so V + 2V - 5 - 1 > 999 --> 3V > 1005 # 3-digit primes P3 = [p for p in P if p > 335 and len(s := str(p)) == len(set(s)) and '1' not in s] # first process 3-digit number of towns for V, A in combinations(P3, 2): if V + A < 1024: continue E = V + A - 1 if len(set(str(1000000 * E + 1000 * A + V))) != 10: continue sols.add(tuple(sorted([V, A, E]))) P = [3, 5, 7] P += [x for x in range(11, 100, 2) if all(x % p for p in P)] # if V has 4 digits then the 2nd digit of V, E must be resp 9, 0 # if A ends in 1 then V and E will end in the same digit # 2-digit primes P2 = [p for p in P if p > 9 and all(y not in (s := str(p)) for y in "019") and len(s) == len(set(s))] # 4-digit primes P4 = [x for x in range(1001, 10000, 2) if all(x % p for p in P)] # if V has 4 digits the 2nd digit must be a nine (and may not use a zero) # otherwise E will use some of the same digits # if V ends in 1 then A and E will end in the same digit P4 = [p for p in P4 if (s := str(p))[1] == '9' and '0' not in s and len(s) == len(set(s)) and s[-1] != '1'] # numbers in P2 and P4 always end in 3 or 7 (1, 5, and 9 are not allowed) # so E must end in 9 P2 = [p for p in P2 if '9' not in (s := str(p)) and p not in {37, 73}] P4 = [p for p in P4 if '9' not in (s := str(p)) and all(y not in s[:-1] for y in "37")] # process 4-digit number of towns for V in P4: # if V has 4 digits (x9yz) then A must be at least 101 - yz for A in [p for p in P2 if p >= 101 - V % 100]: E = V + A - 1 if len(set(str(1000000 * E + 1000 * A + V))) != 10: continue sols.add(tuple(sorted([V, A, E]))) # print solutions for s in sols: print(s)

LikeLike

]]>We can use Euler’s formula:

V – E + F = 2

where:

V = the number of vertices in graph (= the number of towns on the map)

E = the number of edges in the graph (= the number of roads on the map)

F = the number of enclosed regions in the graph (including the region around the graph)

F = (the number of enclosed/coloured regions on the map) + 1

This Python program runs in 93ms. (Internal run time is 38.6ms).

**Run:** [ @replit ]

from enigma import (primes, nsplit, seq_all_different as all_different, ordered, printf) # consider V = the number of towns, it is prime for V in primes.irange(3, 1000): dsV = nsplit(V) if not all_different(dsV): continue # consider A = the number of enclosed areas, it is also prime for A in primes.irange(5, 2 * V - 5): dsA = nsplit(A) if not all_different(dsV + dsA): continue # calculate E = the number of roads E = V + A - 1 if E > 3 * V - 6: continue dsE = nsplit(E) ds = dsV + dsA + dsE if not (len(ds) == 10 and all_different(ds)): continue # output solution ns = ordered(V, A, E) printf("{ns} [V={V} A={A} E={E}]")

**Solution:** The three numbers are: 607, 829, 1435.

We can construct a viable map as follows:

Consider the following diagram:

If the central area is an odd *n*-gon, coloured with the first colour, then it is surrounded by *n* regions, and as *n* is odd we require an additional 3 colours to colour these. So at least 4 colours are required, and the 4-colour theorem tells that we don’t need more than 4 colours.

And together the central and surrounding areas contribute:

2n vertices

3n edges

(n + 1) areas

And adding *p* petals (we may add multiple layers of elongated petals if necessary), adds:

0 vertices

p edges

p areas

And a stalk of length *s* adds:

s vertices

s edges

0 areas

So using: *n* = 413, *p* = 193, *s* = 3, gives a graph with:

826 + 0 + 3 = 829 vertices

1239 + 193 + 3 = 1435 edges

414 + 193 + 0 = 607 areas

Which provides a viable map.

LikeLike

]]>This Python program considers dates in 1974 up to 6th June, if the date includes 8 different digits, then the remaining 2 digits indicate Granny’s age (in some order), and we can then look for other years.

The program runs in 59ms. (Internal runtime is 4.3ms)

**Run:** [ @replit ]

from datetime import (date, timedelta) from enigma import (irange, repeat, nsplit, union, subsets, nconcat, printf) digits = set(irange(0, 9)) # consider dates earlier than this end = date(1974, 6, 7) # consider dates in 1974 inc = lambda x, i=timedelta(days=1): x + i for d in repeat(inc, date(1974, 1, 1)): if not (d < end): break # remaining digits ds4 = union([nsplit(d.month, 2), nsplit(d.day, 2)]) ds = digits.difference(nsplit(d.year, 4), ds4) if len(ds) != 2: continue # construct possible ages for ss in subsets(ds, size=2, select='P'): age = nconcat(ss) year = 1974 - age # collect "special" years years = list() for bd in irange(10, 99): y = year + bd if y > 2015: break if not digits.difference(nsplit(y, 4), ds4, nsplit(bd, 2)): years.append(y) if len(years) == 2 and years[0] == 1974: # output solution printf("{d} -> {years}", d=date(year, d.month, d.day).strftime("%d/%m/%Y"))

**Solution:** Granny’s date of birth is: 26/05/1936.

So Granny is 38 in 1974 → (26/05/1974, 38).

And she is 47 in 1983 → (26/05/1983, 47).

In 2015 (when the puzzle was set) she was 79.

If we consider all dates in 1974 then there is a further solution of: 25/06/1936.

So, the puzzle only has a unique solution if posed between 27th May and 24th June. A fact that was not mentioned when the puzzle was included in the book **The Sunday Times Brain Teasers 1** (2019).

LikeLike

]]>Thanks Frits . Much neater – and I now know how to assign a variable within an expression. Onwards and upwards!

LikeLike

]]>An excellent full programming solution in MiniZinc, with some new functions.

LikeLike

]]>**Run:** [ @replit ]

from enigma import (irange, cproduct, div, printf) # start with 26 red, 26 black R = B = 26 # remove an even (non-zero) number of red cards from the pack for k in irange(2, R, step=2): # pile 1 has r1 reds and b1 blacks (at least 1 black) for (r1, b1) in cproduct([irange(0, R - k), irange(1, B)]): # chance of drawing a black is a whole number percentage pb = div(100 * b1, r1 + b1) if pb is None: continue # a black card is moved to pile 2 which now has r2 reds and b2 blacks (r2, b2) = (R - k - r1, B - b1 + 1) # chance of a red card is also pb percent pr = div(100 * r2, r2 + b2) if pr is None or pr != pb: continue # output solution printf("k={k}: p1={r1}r + {b1}b -> pb={pb}%; p2={r2}r + {b2}b -> pr={pr}%")

**Solution:** 8 red cards were removed from the pack.

From the 18 red and 26 black cards the two piles can be made in two ways:

pile 1:6 red + 24 black; P(black) = 24/30 = 80%

(1 black card is moved to pile 2)

pile 2:12 red + 3 black; P(red) = 12/15 = 80%

pile 1:12 red + 3 black; P(black) = 3/15 = 20%

(1 black card is moved to pile 2)

pile 2:6 red + 24 black; P(red) = 6/30 = 20%

LikeLike

]]>I spent some time in making a generic version of GeoffR’s Minizinc program.

As the numFirst and numAsOf functions do not work with “var int” parameters I had to call them several times.

Using the fact that the first digit of the largest number must be a 1 (as p1 + p2 plus 100 is palindromic and has to end in 1) didn’t help to bring the run time under one second.

% A Solution in MiniZinc include "globals.mzn"; % return <n>-th digit of number <a> with length<len> function var int: nthDigit(var int: a, var int: len, var int: n) = ((a mod pows[len + 2 - n]) div pows[len + 1 - n]); % return number of the first <len> digits function var int: numFirst(array[int] of var int: a, var int: len) = sum(i in 1..len) (pows[len + 1 - i] * a[i]); % return number as of the <b>-th digit function var int: numAsOf(array[int] of var int: a, var int: b) = let { int: len = length(a) }in sum(i in b..len) (pows[len + 1 - i] * a[i]); % count how many digits have a correct mirror digit function var int: palin(var int: a, var int: len, var int: b) = sum(i in 1..b) ( nthDigit(a, len, i) == nthDigit(a, len, len + 1 - i) ); % digits used in the two numbers var 1..9: a; var 1..9: b; var 1..9: c; var 1..9: d; var 1..9: e; var 1..9: f; var 1..9: g; var 1..9: h; var 1..9: i; % make a tables of powers of 10 set of int: pows = {pow(10, j) | j in 0..8}; constraint i == 8; % largest number has to end in 8 constraint all_different ([a, b, c, d, e, f, g, h, i]); var 1..9999: p1; % smallest number var 1..99999999: p2; % largest number var 1..999999999: prod; % product var 8..9: L; % length palindrome var 1..4: L1; % length smallest number % set smallest number to p1 constraint p1 == numFirst([a, b, c, d, e, f, g, h, i], L1); % set largest number to p2 constraint p2 == numAsOf([a, b, c, d, e, f, g, h, i], L1 + 1); constraint p1 mod 10 == 3; % last digit of smallest number is 3 % first digit of product must be a 4 (needed for performance) constraint nthDigit(prod, L, 1) == 4; constraint prod == p1 * p2; % set length variable L constraint (prod > 99999999 /\ L == 9) \/ (prod <= 99999999 /\ L == 8); % product should be a palindrome constraint palin(prod, L, 4) == 4; % find smallest palindromic product solve minimize(prod); output["Smallest palindrome = " ++ show(prod)];

LikeLike

]]>Easier: palin = lambda num: (s := str(num)) == s[::-1]

The “for x .. for y ..” can be replaced by “for y in perm(digs, i):”

LikeLike

]]>Given that the number ending in ‘3’ is the smaller of the two numbers, I could have made line 7

‘for i in range(5,8)’, which shaves a bit of time off.

LikeLike

]]>from itertools import combinations as comb, permutations as perm #test whether number 'num' is palindromic palin = lambda num:sum(str(num)[n]!=str(num)[-n-1] for n in range(len(str(num))//2)) == 0 digs=['1','2','4','5','6','7','8','9'] res=999999999 #work through possible values for 'left' of two smaller numbers for i in range(1,8): for x in comb(digs,i): for y in perm(x): l = int("".join(y)) #work through possible values for 'right' of two smaller numbers (ending in 3) for v in perm([w for w in digs if w not in y]): r=int("".join(v))*10+3 if r>l:continue #number ending in 3 is smallest number prod=l*r #test for output conditions if str(prod)[0]!='4':continue if not palin(prod):continue if not palin(l+r+100):continue if prod<res:res=prod print('Smallest valid product is:', res)

LikeLike

]]>% A Solution in MiniZinc include "globals.mzn"; % re-using Frits' toNum function function var int: toNum(array[int] of var int: a) = let { int: len = length(a) }in sum(i in 1..len) ( ceil(pow(int2float(10), int2float(len-i))) * a[i] ); % Digits for the two numbers being multiplied together % which are AB and CDEFGHI var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D; var 1..9:E; var 1..9:F; var 1..9:G; var 1..9:H; var 1..9:I; constraint all_different ([A,B,C,D,E,F,G,H,I]); var 10..99:AB; var 1000000.. 9999999:CDEFGHI; % last digits of the two numbers constraint B == 3 /\ I == 8; % abcdefgh - main product var 1..9:a; var 0..9:b; var 0..9:c; var 0..9:d; var 0..9:e; var 0..9:f; var 0..9:g; var 1..9:h; % mnopqrst - 2nd palindrome var 1..9:m; var 0..9:n; var 0..9:o; var 0..9:p; var 0..9:q; var 0..9:r; var 0..9:s; var 1..9:t; % Two palindromes var 40000000..45000000:abcdefgh; var 1000000..2000000:mnopqrs; % Two numbers being multiplied together constraint AB == toNum([A,B]); constraint CDEFGHI == toNum([C,D,E,F,G,H,I]); % Main and 2nd palindromes constraint abcdefgh == toNum([a,b,c,d,e,f,g,h]); constraint mnopqrs == toNum([m,n,o,p,q,r,s]); % check main product is palindromic constraint (a == 4 /\ h == 4) /\ b == g /\ c == f /\ d == e; % main palindromic product constraint CDEFGHI * AB == abcdefgh; % form 2nd palindrome constraint mnopqrs == CDEFGHI + AB + 100; constraint m = s /\ n == r /\ o == q; % find smallest palindromic product solve minimize(abcdefgh); output["Smallest palindrome = " ++ show(abcdefgh) ++ "\n" ++ "Sum is: " ++show(CDEFGHI) ++ " * " ++ show(AB) ++ " = " ++ show(abcdefgh) ++ "\n2nd palindrome = " ++ show(mnopqrs)];

LikeLike

]]>But in any case the letter was sent on 3/2, which was misinterpreted as 2nd March, so the birthdays must be after that date.

This Python program runs in 57ms. (Internal run time is 434µs).

**Run:** [ @replit ]

from datetime import (date, timedelta) from enigma import (defaultdict, repeat, catch, subsets, printf) # reverse the day/month of a date rev = lambda d: catch(date, d.year, d.day, d.month) # is a date reversible? is_rev = lambda d: rev(d) == d # group days by day of the week g = defaultdict(list) # look for dates in 1964 that can be misinterpreted as US style dates inc = lambda x, i=timedelta(days=1): x + i for d in repeat(inc, date(1964, 1, 1)): if d.year > 1964: break # try US format d_ = rev(d) if not d_: continue # must be the same day of the week as the original date w = d.isoweekday() if d_.isoweekday() == w: g[w].append(d) # consider the day of the week for k in g.keys(): # we need 9 dates the come after 2nd March for ds in subsets((d for d in g[k] if d > date(1964, 3, 2)), size=9): # the first birthday is the correct date (third son) if not is_rev(ds[0]): continue # the second birthday is not the correct date (eldest son, sent to setter) if is_rev(ds[1]): continue # the third birthday is the correct date (eldest grandson) if not is_rev(ds[2]): continue # the fourth birthday is not the correct date (sent to wife) if is_rev(ds[3]): continue # so the wife's birthday is ... d = rev(ds[3]) # output solution printf("{d}", d=d.strftime("%A, %d %B %Y"))

**Solution:** Her birthday is on: Saturday, 7th November 1964.

There is only one set of dates that works:

4/4 = 4th April, third son

9/5 = 9th May, eldest son; misinterpreted as 5th September (the setter’s birthday)

6/6 = 6th June, eldest grandson

11/7 = 11th July; misinterpreted at 7th November (the setter’s wife’s birthday)

8/8 = 8th August

5/9 = 5th September, setter; misinterpreted at 9th May

10/10 = 10th October

7/11 = 7th November, setter’s wife; misinterpreted as 11th July

12/12 = 12th December

LikeLike

]]>Good point!

Fixed now.

LikeLike

]]>@Jim,

I expected “for i in irange(5, n – 1):” ( where the last digit of the smallest number is still 3)

LikeLike

]]>`SubstitutedExpression()`

]] solver from the This Python program runs in 98ms.

**Run:** [ @replit ]

from enigma import ( Accumulator, SubstitutedExpression, irange, is_npalindrome as is_npal, sprintf as f, printf ) # find the smallest product r = Accumulator(fn=min, collect=1) # symbols to use syms = "ABCDEFGHI" n = len(syms) for i in irange(1, n // 2): # construct the alphametic puzzle; X < Y (X, Y) = (syms[:i], syms[i:]) (x, y) = (X[-1], Y[-1]) # X * Y is palindromic and starts (and ends) in the digit 4 exprs = [ f("is_npal({X} * {Y})"), f("({x} * {y}) % 10 = 4") ] if len(X) == len(Y): exprs.append(f("{X[0]} < {Y[0]}")) p = SubstitutedExpression( exprs, digits=irange(1, 9), # digits are 1-9 s2d={ x: 3 }, # final digit of X is 3 answer=f("({X}, {Y})"), env=dict(is_npal=is_npal), ) # solve it for (s, (x, y)) in p.solve(verbose=0): z = x * y printf("[{x} * {y} = {z}]") r.accumulate_data(z, (x, y)) # output solution printf("min product = {r.value}") for (x, y) in r.data: v = x + y + 100 printf(" = {x} * {y}; {x} + {y} + 100 = {v} [{s}palindrome]", s=('' if is_npal(v) else 'not '))

**Solution:** The smallest product is 40699604.

Ignoring the final palindromic addition check, there are 3 candidate palindromic products that meet the criteria (in decreasing order)

424393424 = 7163 × 59248

43122134 = 1453 × 29678

40699604 = 23 × 1769548

The final one gives the answer to the puzzle, and is also the only one where the sum of the multiplicands and 100 is also palindromic.

There are also two further palindromic products where the *larger* of the multiplicands ends in the digit 3:

449757944 = 56219743 × 8

49900994 = 167453 × 298

LikeLike

]]>P = (R / T) × ((R − 1) / (T − 1)) × ((R − 2) / (T − 2)) × ((R − 3) / (T − 3))

P = (R × (R − 1) × (R − 2) × (R − 3)) / (T × (T − 1) × (T − 2) × (T − 3))

And this probability is “one in a four-figure number”, so the largest it can be is 1/1000 and the smallest is 1/9999.

The following Python program considers total numbers of marbles from 399 down to 350, and then looks for numbers of red marbles that give an appropriate value for P.

It runs in 57ms. (Internal run time is 1.2ms).

**Run:** [ @replit ]

from enigma import (irange, printf) # consider total number of marbles (nearly 400) for T in irange(399, 350, step=-1): # denominator of P d = T * (T - 1) * (T - 2) * (T - 3) # R = number of red marbles for R in irange(4, T): # numerator of P n = R * (R - 1) * (R - 2) * (R - 3) # calculate p = 1/P (p, x) = divmod(d, n) if p > 9999: continue if p < 1000: break if x == 0: # output solution printf("red = {R}, blue = {B}; total = {T} -> P = 1/{p}",B=T - R)

**Solution:** There were 45 red marbles and 342 blue marbles.

So, 387 marbles in total. And the probability of choosing 4 red is 1/6176.

LikeLike

]]>For an even faster solution we can use the [[ `SubstitutedExpression.split_sum`

]] solver.

The following run file executes in 72ms. And the internal run time of the generated program is 234µs).

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression.split_sum "FLORAL + EASTER = BONNET" --extra="N < L < E < T" --answer="BONNET"

LikeLike

]]>Yes the (200, 350) case is a bit of a grey area. I decided he might like one last play with his coins before he banked them, so I might as well check it, as I prefer solutions that exhaustively explore the solution space.

As it turns out it doesn’t provide an answer, so it doesn’t really matter if you check it or not.

LikeLike

]]>If the distance to bridge is *x* centimetres, then *x* must be divisible by 100.

And the time is *t* minutes (less than 30).

If the 5 speeds are: *a, b, c, d, e*, then we have:

t = x/a + x/a = 2x / a

t = x/b + x/1.1b = 21x / 11b

t = x/c + x/1.2c = 11x / 6c

t = x/d + x/1.3d = 23x / 13d

t = x/e + x/1.4e = 12x / 7e

From which we see that *x* must also be divisible by 11, 6, 13, 7.

Placing some sensible limits on distance and speeds, we can suppose *x* is less than 10km (= 10,000m = 1,000,000cm), and that speeds are less than 15mph (≈ 40,000cm/min).

This Python program runs in 61ms. (Internal runtime is 129µs).

**Run:** [ @replit ]

from enigma import (irange, mlcm, div, printf) # x must be a multiple of m m = mlcm(100, 11, 6, 13, 7) # consider possible total times: "a little less than half an hour" for t in irange(29, 21, step=-1): # consider possible values of x (up to 10km) for x in irange(m, 1000000, step=m): # calculate the speeds vs = [ div(20 * x, 10 * t), div(21 * x, 11 * t), div(22 * x, 12 * t), div(23 * x, 13 * t), div(24 * x, 14 * t), ] # check speeds if None in vs or vs[-1] * 1.4 > 40000: continue # output solution printf("d={d} metres [t={t} x={x} {vs}]", d=div(2 * x, 100))

**Solution:** The total length of the race is 6006m.

And the race took exactly 25 minutes.

The outward speeds are:

1:24024 cm/min (≈ 8.96 mph); 12.5 min out + 12.5 min back (@ 8.96 mph)

2:22932 cm/min (≈ 8.55 mph); 13.1 min out + 11.9 min back (@ 9.40 mph)

3:22022 cm/min (≈ 8.21 mph); 13.6 min out + 11.4 min back (@ 9.85 mph)

4:21252 cm/min (≈ 7.92 mph); 14.1 min out + 10.9 min back (@ 10.30 mph)

5:20592 cm/min (≈ 7.68 mph); 14.6 min out + 10.4 min back (@ 10.75 mph)

Note that the speeds on the return leg are not all whole numbers of cm/min.

LikeLike

]]>JIm: Thank you so much for taking the time to unpick my messy code and for your very helpful advice – your countfac is much simpler and I’m now wiser! I couldn’t work out what on earth I’d done in the lambda an hour after writing it!

Agree on the stop value of 351, but I did think about the 200/201 and concluded that Spencer would have banked the lot if it had reached 200, and hence he would only have been playing with up to 199 coins. Perhaps I’m overthinking it!

LikeLike

]]>