This Python program can be used to find sequences of specified lengths. It considers collections of semiprimes of increasing size, until it is possible to make a sequence of the required length. It runs in 85ms.

**Run:** [ @repl.it ]

from collections import OrderedDict from enigma import factor, irange, first, get_argv, printf # map semi-primes to their factors m = OrderedDict() # check if n is the final member of a sequence of length k def seqs(n, k): if k < 2: if k == 1: yield [n] return # find the penultimate item in the sequence for n2 in reversed(m): d = n - n2 if not(d > 0): continue if n < (k - 1) * d: break # make the sequence s = first(irange(n, 1, step=-d), k) # check the sequence is comprised of semi-primes that don't share factors fs = set().union(*(m.get(x, ()) for x in s)) if len(fs) != 2 * k: continue yield s # sequence lengths to find ks = list(int(x) for x in get_argv()) or [4, 5] printf("[lengths = {ks}]") # look for semi-primes n n = 0 while ks: n += 1 # check for a semi-prime with 2 different factors fs = factor(n) if not(len(fs) == 2 and fs[0] != fs[1]): continue m[n] = fs # look for sequences for k in list(ks): ss = list(seqs(n, k)) if ss: for s in ss: printf("length {k}: {s} d={d}", s=s[::-1], d=('?' if len(s) < 2 else s[0] - s[1])) # we only want the first sequence ks.remove(k)

**Solution:** The smallest number in Harry’s sequence is 91, the largest is 187. The smallest number in Tom’s sequence is 205, the largest is 221.

Harry’s full sequence is: (91, 123, 155, 187), the common difference being 32.

Tom’s full sequence is: (205, 209, 213, 217, 221), the common difference being 4.

Some sequences with minimal final element:

length 1: (6)

length 2: (14, 15) d=1

length 3: (33, 34, 35) d=1

length 4: (91, 123, 155, 187) d=32

length 5: (205, 209, 213, 217, 221) d=4

length 6: (713, 731, 749, 767, 785, 803) d=18

length 7: (707, 779, 851, 923, 995, 1067, 1139) d=72

length 8: (1007, 1073, 1139, 1205, 1271, 1337, 1403, 1469) d=66

length 9: (3811, 3991, 4171, 4351, 4531, 4711, 4891, 5071, 5251) d=180

length 10: (2449, 2899, 3349, 3799, 4249, 4699, 5149, 5599, 6049, 6499) d=450

length 11: (1111, 2461, 3811, 5161, 6511, 7861, 9211, 10561, 11911, 13261, 14611) d=1350

length 12: (14039, 14309, 14579, 14849, 15119, 15389, 15659, 15929, 16199, 16469, 16739, 17009) d=270

length 13: (14039, 14309, 14579, 14849, 15119, 15389, 15659, 15929, 16199, 16469, 16739, 17009, 17279) d=270

length 14: (11387, 16637, 21887, 27137, 32387, 37637, 42887, 48137, 53387, 58637, 63887, 69137, 74387, 79637) d=5250

length 15: (108253, 108673, 109093, 109513, 109933, 110353, 110773, 111193, 111613, 112033, 112453, 112873, 113293, 113713, 114133) d=420

length 16: (63551, 68591, 73631, 78671, 83711, 88751, 93791, 98831, 103871, 108911, 113951, 118991, 124031, 129071, 134111, 139151) d=5040

In the program we use an [[ `OrderedDict()` ]] to record the semi-primes, so we can iterate through the keys in the order they were most recently inserted.

The following run-file executes in 114ms.

**Run:** [ @repl.it ]

#!/usr/bin/env python -m enigma -r SubstitutedDivision "kxmk / dk = bgp" "kx - gm = dp" "dpm - dmt = t" "tk - xb = db"

**Solution:** The correct sum is: 8528 ÷ 18 = 473 (remainder 14).

I calculated it as

which is the nearest integer to

d(n) = the number of derangements of n elements

d(1) = 0

d(n) = n × d(n – 1) + (–1)^n

and:

c(n) = the number of cyclic permutations of n elements

c(1) = 0

c(n) = factorial(n – 1)

by recursive formulae, or we can build them up from the previous terms as we consider increasing *n*.

**Run:** [ @repl.it ]

from enigma import printf # consider increasing n (n, d, c) = (2, 1, 1) while True: printf("n={n} d={d} c={c} p={p:.3f}", p=float(c) / float(d)) if 2 * c < d: break # increase n and calculate d(n) and c(n) c *= n n += 1 d *= n d += (-1 if n % 2 else 1)

**Solution:** There will be 6 people at the party.

It runs in 200ms.

**Run:** [ @repl.it ]

from itertools import permutations from enigma import Football, irange, update, join, printf # check a sequence of (symbol, value) assignments is consistent def check(ss): d=dict() for (s, v) in ss: v1 = d.get(s) if v1 is None: # new symbol if v in d.values(): return False d[s] = v else: # existing symbol, check values if v != v1: return False # looks OK return True # scoring system old and new (all games are played) football = Football(games="wdl", points=dict(w=2, d=1)) football1 = Football(games="wdl", points=dict(w=3, d=1)) # the teams teams = (A, B, C, D) = (0, 1, 2, 3) # possible digits digits = set(irange(0, 9)) # the table (and the for / against columns) (table, gf, ga) = (dict(w='?S??', l='?T??', d='?I??', points='T??E'), 'ILSN', '?LIO') # find outcomes for the matches for (ms, d1) in football.substituted_table(table): # we have I, S, so we can determine scores for team C for ss1 in football.substituted_table_goals(gf, ga, ms, teams=[C], d=d1): # choose a value for L, so we can determine scores for team B for L in digits.difference(d1.values()): d2 = update(d1, [('L', L)]) for ss2 in football.substituted_table_goals(gf, ga, ms, teams=[B], d=d2, scores=ss1): # choose values for N, O, and we can determine scores for team D for NO in permutations(digits.difference(d2.values()), 2): d3 = update(d2, 'NO', NO) for ss3 in football.substituted_table_goals(gf, ga, ms, teams=[D], d=d3, scores=ss2): # check A.goals_for = I (fA, aA) = football.goals(*(football.extract(ss3, A))) if not(fA == d3['I']): continue # check one of the games was won by a margin of 5 goals if not any(abs(x - y) == 5 for (x, y) in ss3.values()): continue # calculate the complete table t = [football.table(*(football.extract(ms, i))) for i in teams] g = [(fA, aA)] + [football.goals(*(football.extract(ss3, i))) for i in (B, C, D)] # determine the positions (based on points, then goal difference) pos = sorted(teams, key=(lambda i: (t[i].points, g[i][0] - g[i][1])), reverse=1) # the winner was decided on goal difference if not(t[pos[0]].points == t[pos[1]].points): continue # calculate tables according to the new scoring system t = [football1.table(*(football1.extract(ms, i))) for i in teams] # check new assignments assign = [ ('I', g[A][0]), ('T', t[A].points), ('S', t[B].w), ('T', t[B].l), ('I', t[B].d), ('L', g[B][1]), ('L', g[B][0]), ('I', g[C][1]), ('S', g[C][0]), ('O', g[D][1]), ('N', g[D][0]), ('E', t[D].points), ] if not check(assign): continue # determine the positions (based on new points, then goal difference) pos1 = sorted(teams, key=(lambda i: (t[i].points, g[i][0] - g[i][1])), reverse=1) # the winners in the new system were 2nd in the old system if not(pos1[0] == pos[1]): continue # output solutions fmt = lambda s: join(("ABCD"[i] for i in s), sep=",") printf("pos: {pos} -> {pos1}", pos=fmt(pos), pos1=fmt(pos1)) football.output_matches(ms, ss3, teams="ABCD", d=d3)

**Solution:** A v B = 0-1; A v C = 0-0; A v D = 0-5; B v C = 0-2; B v D = 5-4; C v D = 0-0.

In the old system the final positions were:

1: C, 4 pts

2: B, 4 pts

3: D, 3 pts

4: A, 1 pt

In the new system:

]]>1: B, 6 pts

2: C, 5 pts

3: D, 4 pts

4: A, 1 pt

again for a square array with (n + 1) × (n + 1) cells.

]]>If we have reached either the top edge or right edge (i.e. we are in the same row or column as our target), then there is only a single possible path towards the target.

Otherwise, we can move in either of the three permissible directions to three new squares, so we sum the number of paths to the target from those three squares.

The following Python program works on a rectangular grid the size of which can be specified on the command line. For small grids we don’t need to cache results, or use symmetry to reduce the problem. For the 8×8 grid it runs in 93ms.

**Run:** [ @repl.it ]

from enigma import arg, printf # count number of paths from (x, y) to (0, 0) def paths(x, y): if x == 0 or y == 0: return 1 else: return paths(x - 1, y) + paths(x, y - 1) + paths(x - 1, y - 1) n = arg(8, 0, int, prompt="n") m = arg(n, 1, int, prompt="m") t = paths(n - 1, m - 1) printf("[{n}x{m}] number of paths = {t}")

**Solution:** There are 48,639 possible paths.

So, the son is correct.

OEIS A001850 [ https://oeis.org/A001850 ] gives the number of paths on increasing square grids.

If we define the function:

>>> f = lambda n: sum(C(n, k) * C(n + k, k) for k in irange(0, n))

Then the number of paths on the 8×8 grid is given by *f(7)*, i.e.:

>>> f(7) 48639]]>

My approach is to assign a number to each of the 8 possible variations of the book. Together these numbers need to sum to 57, and satisfy the other conditions given.

Once we have a viable set of numbers we know that the total sales of Bunion is fewer than the sales of the Velvet+Silver+Myrrh (= VSM) volume.

But if the interviewer is able to deduce VSM knowing the number of B’s sales, it seems to me that VSM must take on the maximum possible value (and B must be one less than this, which is also the maximum possible value for B).

This Python program runs in 82ms.

**Run:** [ @repl.it ]

from collections import namedtuple from enigma import irange, filter_unique, printf # we only need decompositions of 17 in pairs d17s = list((i, 17 - i) for i in irange(0, 17)) # tuple for results T = namedtuple("T", "VSM VSS VGM VGS RSM RSS RGM RGS B") # accumulate results rs = list() # "what about those scented with soap?" # "3 were not only printed in silver, but also bound in velvet" VSS = 3 # "34 were scented with myrrh" # so, VSM + VGM + RSM + RGM = 34, # and, "half those scented with myrrh were printed in silver" # so, VSM + RSM = 17, VGM + RGM = 17 for (VSM, RSM) in d17s: # "29 were printed in silver" # so, VSM + VSS + RSM + RSS = 29, and we already have VSM, VSS, RSM RSS = 29 - (VSS + VSM + RSM) # from above, VGM + RGM = 17 for (VGM, RGM) in d17s: # "27 were bound in velvet" # so, VSM + VSS + VGM + VGS = 27 VGS = 27 - (VSM + VSS + VGM) # total is 57, and we only have RGS to assign RGS = 57 - (VSM + VSS + VGM + VGS + RSM + RSS + RGM) r = T(VSM, VSS, VGM, VGS, RSM, RSS, RGM, RGS, 0) # ignore any set with negative values if any(v < 0 for v in r): continue # B's sales were less than VSM for B in irange(0, VSM - 1): rs.append(r._replace(B=B)) # if we knew B we would know VSM (rs, _) = filter_unique(rs, (lambda r: r.B), (lambda r: r.VSM)) # output solutions for r in rs: printf("B={B} -> VSM={VSM} [{r}]", B=r.B, VSM=r.VSM)

**Solution:** The number of luxury editions sold is 17.

And Bunion’s sales must be 16.

The breakdown of Meek’s sales is:

VSM = 17

VSS = 3

VGM = k

VGS = 7 – k

RSM = 0

RSS = 9

RGM = 17 – k

RGS = 4 + k

where *k = 0..7*.

Which gives a total of 57.

Checking the other conditions:

Velvet = 17 + 3 + k + (7 – k) = 27

Silver = 17 + 3 + 0 + 9 = 29

Myrrh = 17 + k + 0 + (17 – k) = 34

Myrrh + Silver = 17 + 0 = 17

Soap + Silver + Velvet = 3

The published solution is:

Answer:13.Draw three intersecting circles, putting in x luxury editions and y editions in grey, velvet and myrrh. You will find that x + y = 13. To resolve this uniquely, Bunion must have sold 13 copies.

Adopting this approach I get:

VSM = x

VSS = 3

VGM = y

VGS = 24 – (x + y)

RSM = 17 – x

RSS = 9

RGM = 17 – y

RGS = x + y – 13

These sum to 57, and satisfy the other equations.

We get viable sets of numbers when *x* and *y* are chosen such that none of the above give negative results (so we have *13 ≤ x + y ≤ 24* and *x, y ≤ 17*).

To fix *x + y = 13* (the smallest possible value) we would require some additional condition such as: RGS = 0, VGS = 11, or RSM + RGM = 21

It’s possible that if the setter approached the problem this way they just added the 7 enclosed regions together to get 57, forgetting the RGS region (which corresponds to the “outside” of the diagram), implicitly setting RGS = 0.

And if we do assume RGS = 0 (we could add an extra clause to the puzzle to state that the volume was not available in Rags+Grey+Soap, or just that no-one had bought this combination), then we do indeed get a solution of VSM = 13 (and B = 12).

Although I did not come across any further correction/clarification for this puzzle in the Google Books archive of *New Scientist*.

It runs in 107ms.

**Run:** [ @repl.it ]

from enigma import irange, filter_unique, printf # express total <t> using denominations <ds>, min quantity 1 def express(t, ds, s=[]): if not(ds): if t == 0: yield s else: (d, *ds) = ds for i in irange(1, t // d): yield from express(t - d * i, ds, s + [i]) # the 4 rates (in increasing order) rates = (2, 3, 5, 7) # indices for rates 3 and 5 (i3, i5) = (rates.index(x) for x in (3, 5)) # generate numbers of calls at each of the rates, such that: # + the total bill is 100 # + the total number of calls is 25 # + there is at least one call at each rate # + there are more calls charged at 5 than charged at 3 def calls(): for s in express(100, rates): if not(sum(s) == 25): continue if not(s[i5] > s[i3]): continue yield tuple(s) # find results unique by the number of calls at the cheapest rate (rs, _) = filter_unique(calls(), (lambda s: s[0])) for r in rs: # the solution has a single digit number of calls at the lowest rate if not(r[0] < 10): continue # output solution for (n, x) in zip(r, rates): printf("{n} calls at {x}z") printf()

**Solution:** He made 9 calls at 3 zorinds.

The full solution is:

3 calls at 2z

9 calls at 3z

12 calls at 5z

1 call at 7z

The requirement that the number of calls made at the cheapest rate (2z) is a 1 digit number rules out a second solution of:

13 calls at 2z

1 call at 3z

3 calls at 5z

8 calls at 7z

There are also 14 sets of calls where the number of 2z calls does not uniquely identify a result. (There are 3 with 5 calls, 4 with 7 calls, 4 with 9 calls, 3 with 11 calls).

]]>It runs in 346ms.

**Run:** [ @repl.it ]

from enigma import SubstitutedExpression, join, irange, printf # make the alphametic expressions exprs = [ # ELEVEN and TWELVE have different scores "E + N != T + W", # each of the other words has a score equal to ELEVEN or TWELVE "(O == E + L + E + V) or (O + N == T + W + E + L + V)", "(T + W + O == E + L + E + V + E + N) or (O == E + L + V + E)", "(T + H + R == E + L + V + N) or (H + R == W + L + V)", "(N + I == E + L + E + V) or (N + I + N == T + W + E + L + V)", "(T == E + L + E + V) or (N == W + E + L + V)", "(T + H + I + R + T == E + L + V) or (T + H + I + R + N == W + L + V)" ] # express the puzzle as an alphametic # - digits: each symbol stands for a digit from 1 to 9 # - distinct: symbols do not necessarily have distinct values # - verbose: don't show your workings p = SubstitutedExpression(exprs, digits=irange(1, 9), distinct="", verbose=0) # solve the alphametic (words, words2) = (x.split() for x in ["ONE TWO THREE NINE TEN THIRTEEN", "ELEVEN TWELVE"]) for s in p.solve(): # compute scores for each word score = dict((w, sum(s[x] for x in w)) for w in words + words2) # scores for ELEVEN, TWELVE (s11, s12) = (score[x] for x in words2) # find words with the same score (w11, w12) = ([w for w in words if score[w] == n] for n in [s11, s12]) # output solution printf("ELEVEN = {s11} = ({w11}) [TWELVE = {s12} = ({w12})] [{s}]", w11=join(w11, sep=", "), w12=join(w12, sep=", "), s=join((join((k, '=', s[k])) for k in sorted(s.keys())), sep=" ") )

**Solution:** ONE, THREE, NINE and TEN have the same score as ELEVEN.

There are three ways of assigning the letters. We always have:

E=1, H=1, N=3, R=1

and one of:

(1) I=1, L=1, O=4, T=4, V=1, W=8

(2) I=2, L=2, O=5, T=5, V=1, W=9

(3) I=2, L=1, O=5, T=5, V=2, W=9

These give:

(1) ELEVEN = 8, TWELVE = 16

(2) ELEVEN = 9, TWELVE = 19

(3) ELEVEN = 9, TWELVE = 19

And in each case we have:

ELEVEN = ONE, THREE, NINE, TEN

TWELVE = TWO, THIRTEEN

So, restricting the digits to be from 1 to 8 would give the first of these assignments as a unique solution.

]]>**Run:** [ @repl.it ]

from itertools import product from enigma import irange, Football, printf # possible scorelines (not more than 7 goals scored in any match) ss = dict() ss['w'] = list((x, y) for x in irange(1, 7) for y in irange(0, min(x - 1, 7 - x))) ss['d'] = list((x, x) for x in irange(0, 3)) ss['l'] = list((x, y) for (y, x) in ss['w']) ss['x'] = [ None ] # scoring system (points for goals are added separately) football = Football(points=dict(w=10, d=5)) # find possible match outcomes for (AB, AC, BC) in football.games(repeat=3): A = football.table([AB, AC], [0, 0]) B = football.table([AB, BC], [1, 0]) C = football.table([AC, BC], [1, 1]) if A.points > 3 or B.points > 7 or C.points > 21: continue for (sAB, sAC, sBC) in product(ss[AB], ss[AC], ss[BC]): (fA, aA) = football.goals([sAB, sAC], [0, 0]) (fB, aB) = football.goals([sAB, sBC], [1, 0]) (fC, aC) = football.goals([sAC, sBC], [1, 1]) if not(A.points + fA == 3 and B.points + fB == 7 and C.points + fC == 21): continue printf("AB={AB}:{sAB} AC={AC}:{sAC} BC={BC}:{sBC}") printf("A={A} f={fA} a={aA}") printf("B={B} f={fB} a={aB}") printf("C={C} f={fC} a={aC}") printf()

**Solution:** The scores in the played matches are: A vs C = 3-4, B vs. C = 2-2. The A vs B match is not yet played.

To do this we must consider 10! = 3,628,800 numbers. The Python program runs in 955ms.

from itertools import permutations from enigma import irange, nconcat, printf # count the total number (t) and those with leading zeros (z) (t, z) = (0, 0) # consider digits from 0 to 9 in some order for s in permutations(irange(0, 9)): # turn them into a number n = nconcat(s) # is it divisible by 11? if n % 11 != 0: continue t += 1 if s[0] == 0: z += 1 printf("(a) {a}, (b) {b} [t={t} z={z}]", a=t, b=t - z)

But we can be a bit cleverer.

A test for divisibility by 11 is to take the alternating sum of the digits, and see if that is divisible by 11.

So for 0234871956 we have:

0 – 2 + 3 – 4 + 8 – 7 + 1 – 9 + 5 – 6 = –11

So we can choose five digits, that sum to *n*, and the remaining digits will sum to *(45 – n)*. We need to check if the difference between these sums is a multiple of 11. The difference is:

n – (45 – n) = 2n – 45

And 45 mod 11 = 1, so we also need *2n* mod 11 = 1.

Once we satisfy this we can arrange the chosen five digits in 5! = 120 positions, and the remaining five digits in another 120 positions, giving 14,400 possible pandigitals from this selection that are divisible by 11.

This Python program counts the possibilities in 77ms.

**Run:** [ @repl.it ]

from itertools import combinations from enigma import irange, factorial, printf # how many pandigitals in a batch? k = factorial(5) ** 2 # and how many start with 0 (if 0 is selected) k0 = k // 5 # count the total number (t) and those with leading zeros (z) (t, z) = (0, 0) # consider 5 digits for s in combinations(irange(0, 9), 5): n = sum(s) if (2 * n) % 11 != 1: continue t += k if 0 in s: z += k0 printf("(a) {a}, (b) {b} [t={t} z={z}]", a=t, b=t - z)

**Solution:** (a) There are 316,800 pandigitals divisible by 11, including those with a leading 0; (b) There are 285,120 pandigitals divisible by 11, excluding those with a leading 0.

In fact there are 11 different ways of choosing 5 digits that sum to 17 (and 9 of them include 0). Taking the remaining digits from each of these we find 11 different ways of choosing 5 digits that sum to 28 (and 2 of them include 0). 17 and 28 are the only possible sums *n*, such that *2n* mod 11 = 1.

So the total number of pandigitals divisible by 11 is: 22 × 14400 = 316,800.

And of those the number with a leading zero is: 11 × 14400 / 5 = 31,680.

(The difference between the algorithms is even more apparent when they are run on repl.it, where the time taken to start the Python interpreter isn’t counted. The fastest time I achieved with the first program was 36.09s, for the second program I got a run time of 637.68μs, about 56,596× faster).

]]>We can apply these and then use the [[ `SubstitutedExpression()` ]] solver from the **enigma.py** library to solve the remaining conditions of the puzzle.

The following run file executes in 142ms.

**Run:** [ @repl.it ]

# we transform the first grid to the second by equating: # # A = C = E, P = K, N = L, X = V # # A B C D E A B A D A # F G H I J F G H I J # K L M N P -> K L M L K # Q R S T U Q R S T U # V W X Y Z V W V Y Z SubstitutedExpression --distinct="AHMSV" --template="Across: 1. {ABADA}; 4. {GHI}; 5. {KLMLK}; 6. {RST}; 7. {VWVYZ} / Down: 1. {AFKQV}; 2. {AHMSV}; 3. {AJKUZ}" --solution="" --header="" # 1a = ABADA # "A prime ..." "is_prime(ABADA)" # "... which is also a square reversed ..." "is_square(ADABA)" # "... the first 2 digits form a square ..." "is_square(AB)" # "... the last 2 digits form a prime ..." "is_prime(DA)" # 4a = GHI # "the square root of 7 across" "GHI ** 2 = VWVYZ" # 5a = KLMLK # "a palindromic square" "is_square(KLMLK)" # 6a = RST # "the square root of the reverse of 2 down" "RST ** 2 = VSMHA" # 7a = VWVYZ # "a square ..." "is_square(VWVYZ)" # "... which is a prime when reversed" "is_prime(ZYVWV)" # 1d = AFKQV # "a prime ..." "is_prime(AFKQV)" # "... which is also a square reversed ..." "is_square(VQKFA)" # "... the first 3 digits form a square ..." "is_square(AFK)" # "... which is also a square when reversed ..." "is_square(KFA)" # "... the last 2 digits form a prime ..." "is_prime(QV)" # "... which is also a prime when reversed" "is_prime(VQ)" # 2d = AHMSV # "a prime ..." "is_prime(AHMSV)" # "... which is also a square when reversed ..." "is_square(VSMHA)" # "... all the digits are different ..." (see --distinct) # "... the first 3 digits form a square ..." "is_square(AHM)" # "... which is also a square when reversed ..." "is_square(MHA)" # 3d = AJKUZ # "a square ..." "is_square(AJKUZ)" # "... which is a prime when reversed" "is_prime(ZUKJA)"

**Solution:** 1 across = 16141; 1 down = 14437; 3 down = 18496; 7 across = 70756.

I made some (reasonable) simplifications in my program. I don’t consider “castling” moves (but as both Kings have already been moved I don’t think it is possible), nor “en passant” captures for pawns. I don’t consider promotions in my code, but as we are only going to play a single move I don’t think this matters.

The layout of the board given means some assignments of chess pieces to the drinks are not possible. The white gin and vodka are on row 1, so neither of them can be Pawns (Pawns can’t move backwards), and the white whisky and brandy are on row 8, so neither of those can be Pawns either (as they would have been promoted to something else). Also there are two black rums which are both on white squares, so without promotions rum cannot be King, Queen or Bishop, but with promotions there could be multiple Queens or Bishops that move on white squares.

This Python 3 program determines possible assignments using the three statements about the behaviour of the pieces, and then looks for a move for white which will put black in check. It runs in 234ms.

**Run:** [ @repl.it ]

from itertools import product, permutations from enigma import irange, update, cached, printf # find value <v> in dictionary <d> def find(d, v): for (k, v1) in d.items(): if v1 == v: return k return None # is position p = (x, y) a valid square on the chessboard def valid(p): (x, y) = p return 0 <= x <= 7 and 0 <= y <= 7 # position in algebraic chess notation def pos(p): (x, y) = p return "abcdefgh"[x] + "12345678"[y] # swap sides (white <-> black) def swap(p): (x, y) = p return (x, 7 - y) # rotational symmetry order 4 rot4 = lambda x, y: [(x, y), (-y, x), (-x, -y), (y, -x)] # adjacent delta moves (horizontal / vertical) h_v = rot4(0, 1) # diagonal delta moves dia = rot4(1, 1) # deltas in all directions adj = h_v + dia # knight move deltas knight = rot4(2, 1) + rot4(1, 2) # a chess board, white to move class Board(object): # white and black are dicts of <pos> -> <piece> def __init__(self, white, black): self.white = white self.black = black # find valid moves from <p> along a line delta <d>, max distance <k> def line(self, p, d, k=7): ((x, y), (dx, dy)) = (p, d) for i in irange(1, k): p = (x + i * dx, y + i * dy) # are we still on the board? if not valid(p): break # have we hit a white piece? if p in self.white: break # otherwise, this is a valid move yield p # have we hit a black piece? if p in self.black: break # moves along lines with deltas in <ds> def lines(self, p, ds, k=7): for d in ds: yield from self.line(p, d, k) # possible moves for the various pieces: # castle (rook) def R(self, p): return self.lines(p, h_v) # bishop def B(self, p): return self.lines(p, dia) # queen def Q(self, p): return self.lines(p, adj) # king (ignoring castling) def K(self, p): return self.lines(p, adj, 1) # knight def N(self, p): return self.lines(p, knight, 1) # pawn (ignoring: "en passant" and promotion) def P(self, p): (x, y) = p # pawns can move forward one (if not blocked) p1 = (x, y + 1) if valid(p1) and not(p1 in self.white or p1 in self.black): yield p1 # or forward 2 if on home row (and not blocked) if y == 1: p2 = (x, y + 2) if not(p2 in self.white or p2 in self.black): yield p2 # or can take a piece diagonally for p in [(x - 1, y), (x + 1, y)]: if p in self.black: yield p # generate moves for piece <n> @ position <s> def moves(self, s, n): return getattr(self, n)(s) # does white have black in check? def check(self): # find the black king k = find(self.black, 'K') # can white piece <n> @ <s> attack it? for (s, n) in white.items(): if k in self.moves(s, n): return s # play a move from <s> to <p> def play(self, s, t): (white, black) = (self.white, self.black) # move the white piece n = white[s] white = update(white, [(t, n)]) del white[s] # is there a capture? if t in black: black = update(black, []) del black[t] # return the new board return Board(white, black) # for each of the pairs of pieces can we find an example where: # # TT: (A threatens B) and (B threatens A) # TN: (A threatens B) and (B does not threaten A) @cached def T(A, B, f): # consider positions for white piece A xs = irange(0, 3) # there is left/right symmetry ys = irange(*((1, 6) if A == 'P' else (0, 7))) for p in product(xs, ys): # consider the squares that A potentially threatens b = Board({ p: A }, {}) for q in b.moves(p, A): # check A threatens B b1 = Board({ p: A }, { q: B }) if not(q in b1.moves(p, A)): continue # check if B threatens A (p2, q2) = (swap(p), swap(q)) b2 = Board({ q2: B }, { p2: A }) # is A threatened by B? if (p2 in b2.moves(q2, B)) == f: return (p, q) TT = lambda A, B: T(A, B, True) TN = lambda A, B: T(A, B, False) # consider possible assignments of drinks to chess pieces for (b, g, k, r, v, w) in permutations('KQRNBP'): # we can skip certain assigments: # - pawns cannot be on the end rows # - there is only one king if g == 'P' or v == 'P' or w == 'P' or b == 'P' or r == 'K': continue # "if v threatens g then g threatens v" # a counterexample is: (v threatens g) and (g does not threaten v) = TN(v, g) if TN(v, g): continue # "if b threatens w then w does not threaten b" # a counterexample is: (b threatens w) and (w threatens b) = TT(b, w) if TT(b, w): continue # "if k does not threaten r then r does not threaten k" # a counterexample is: (k does not threaten r) and (r threatens k) = TN(r, k) if TN(r, k): continue # the given board white = { (0, 0): g, (7, 0): v, (1, 1): r, (3, 6): k, (0, 7): w, (7, 7): b } black = { (5, 5): k, (0, 4): g, (3, 4): w, (5, 4): r, (6, 4): v, (0, 3): r, (4, 2): b } b1 = Board(white, black) # black is not currently in check if b1.check(): continue # for each white piece... for (s, n) in white.items(): # ... consider possible moves for t in b1.moves(s, n): # if we play this move... b2 = b1.play(s, t) # ... does it leave black in check? x = b2.check() if x: printf("brandy={b} gin={g} kirsch={k} rum={r} vodka={v} whisky={w}") printf("+ move {n} @ {s} -> {t}, check from {x}", s=pos(s), t=pos(t), x=pos(x))

**Solution:** Mortar’s mating move was to move the white pawn (rum) at b2.

This move puts the black king (kirsch) at f6 in check from the white queen (gin) at a1.

It seems most likely that white would move the pawn at b2 to b4, as black will have to use his next move to get out of check, and so wouldn’t be able to capture the pawn “en passant”.

I found there are three ways of assigning the drinks to the chess pieces. However:

Kirsch is always a King, and rum is always a Pawn, so the third statement is:

If a King does not threaten a Pawn, then the Pawn does not threaten the King.

which seems reasonable.

Gin is always a Queen, and vodka is either a Bishop or a Rook, so the first statement is one of:

If a Bishop threatens a Queen, then the Queen threatens the Bishop.

If a Rook threatens a Queen, then the Queen threatens the Rook.

which also seem reasonable.

Brandy and whisky are (Rook, kNight), (kNight, Bishop) or (kNight, Rook), so the second statement is one of:

If a Rook threatens a kNight, then the kNight does not threaten the Rook.

If a kNight threatens a Bishop, then the Bishop does not threaten the kNight.

If a kNight threatens a Rook, then the Rook does not threaten the kNight.

which also seem reasonable.

The assignments I found that work are:

Brandy = Rook; Gin = Queen; Kirsch = King; Rum = Pawn; Vodka = Bishop; Whisky = kNight

Brandy = kNight; Gin = Queen; Kirsch = King; Rum = Pawn; Vodka = Rook; Whisky = Bishop

Brandy = kNight; Gin = Queen; Kirsch = King; Rum = Pawn; Vodka = Bishop; Whisky = Rook

Each of these assignments gives a move by white of b2 to b3 or b4 to put black in check.

The published solution only gives the middle of these assignments. But maybe to a more experienced chess player there is a reason that the other two can be ruled out.

]]>This run file executes in 75ms.

**Run:** [ @repl.it ]

SubstitutedExpression --digits="1-9" --answer="OTHER // 25" "OTHER % 25 = 0" "OTHER < 25 * BEST" "2 * GOOD = BEST" "is_prime(ODD)"]]>

**Run:** [ @repl.it ]

from enigma import filter_unique, unpack, seq_all_different, join, printf # index of answer in the table index = (P, Q, R, S, T, U) = (0, 1, 2, 3, 4, 5) # the answers from each candidate table = { 'A': (2, 1, 1, 4, 3, 2), 'B': (4, 3, 3, 5, 1, 3), 'C': (5, 3, 4, 1, 3, 5), 'D': (2, 2, 1, 3, 4, 5), 'E': (5, 2, 2, 5, 1, 2), 'F': (3, 3, 1, 3, 4, 4), 'G': (3, 3, 5, 1, 5, 5), 'H': (1, 4, 2, 2, 5, 1), 'I': (1, 3, 3, 1, 4, 3), 'J': (2, 2, 4, 1, 1, 4), } # X was told the answer to P, can she work out the answer to Q? (xY, xN) = filter_unique( table.keys(), (lambda k: table[k][P]), (lambda k: table[k][Q]) ) # Y is told X's answer and the answer to R, can she work out S? (yY, yN) = filter_unique( [('Y', k) for k in xY] + [('N', k) for k in xN], unpack(lambda x, k: (x, table[k][R])), unpack(lambda x, k: table[k][S]) ) # Z is told Y's answer and the answer to T, can she work out U? (zY, zN) = filter_unique( [('Y', k) for (x, k) in yY] + [('N', k) for (x, k) in yN], unpack(lambda y, k: (y, table[k][T])), unpack(lambda y, k: table[k][U]) ) # if were told Z's answer, we could find a question that would identify the exemplar for (z, ks) in zip('YN', (zY, zN)): # possible exemplar candidates ks = list(k for (y, k) in ks) # choose a question for i in index: # can it be used to distinguish the candidates in ks? if seq_all_different(table[k][i] for k in ks): # which (if any) candidates chose 1? cs = list(k for k in ks if table[k][i] == 1) # output solution printf("z={z}, q={i}, 1->{cs}", i="PQRSTU"[i], cs=join(cs, sep=","))

**Solution:** Z’s answer was “no”. Knowing the answer to Question P would allow you to identify which candidate got all the answers correct. If the answer to Question P is 1, then candidate I got all the answers correct.

And if candidate I got all 6 questions correct, then no-one else got more than 3 correct.

Here is a manual solution that uses the same approach as the program:

X was told the answer to P, and asked if she could work out Q:

If P = 3: F, G is exemplar; Q = 3; X = yes

If P = 4: B is exemplar; Q = 3; X = yesIf P = 1: H, I is exemplar; Q = 3, 4; X = no

If P = 2: A, D, J is exemplar; Q = 1, 2; X = no

If P = 5: C, E is exemplar; Q = 2, 3; X = no

Y is told X’s answer, and the answer to R, and asked if she could work out R:

If X said yes, then: B, F, G is exemplar.

If R = 1: F is exemplar; S = 3; Y = yes

If R = 3: B is exemplar; S = 5; Y = yes

If R = 5: G is exemplar; S = 1; Y = yes

R = 2, 4 is impossible, as none of B, F, G can be exemplar.

But, if X said no, then: A, C, D, E, H, I, J is exemplar.

If R = 3: I is exemplar; S = 1; Y = yes

If R = 4: C, J is exemplar; S = 1; Y = yesIf R = 1: A, D is exemplar; S = 3, 4; Y = no

If R = 2; E, H is exemplar; S = 2, 5; Y = no

R = 5 is impossible.

Z is told Y’s answer, and the answer to T, and asked if she can work out U:

If Y said yes, then: B, C, F, G, I, J is exemplar.

If T = 3: C is exemplar; U = 5; Z = yes

If T = 5: G is exemplar; U = 5; Z = yesIf T = 1: B, J is exemplar; U = 3, 4; Z = no

If T = 4: F, I is exemplar; U = 3, 4; Z = no

If Y said no, then: A, D, E, H is exemplar.

If T = 1: E is exemplar; U = 2; Z = yes.

If T = 3: A is exemplar; U = 2; Z = yes

If T = 4: D is exemplar; U = 5; Z = yes

If T = 5: H is exemplar; U = 1; Z = yes

T = 2: is impossible in both cases.

We are now told Z’s answer:

If Z said yes, then: A, C, D, E, G, H is exemplar.

Can we distinguish them with one question?

As there are 6 candidates and only 5 possible answers, no we can’t.

P = 2, 5, 2, 5, 3, 1; no

Q = 1, 3, 2, 2, 3, 4; no

R = 1, 4, 1, 2, 5, 2; no

S = 4, 1, 3, 5, 1, 2; no

T = 3, 3, 4, 1, 5, 5; no

U = 2, 5, 5, 2, 5, 1; no

But, if Z said no, then: B, F, I, J is exemplar.

Can we distinguish them with one question?

P = 4, 3, 1, 2; yes

Q = 3, 3, 3, 2; no

R = 3, 1, 3, 4; no

S = 5, 3, 1, 1; no

T = 1, 4, 4, 1; no

U = 3, 4, 3, 4; no

So knowing the answer to P will tell us which candidate is exemplar:

P = 1: I is exemplar

P = 2: J is exemplar

P = 3: F is exemplar

P = 4: B is exemplar

P = 5 is impossible.

]]>http://discrete.openmathbooks.org/dmoi2/sec_recurrence.html

explains how to derive a closed expression for a recurrence relation

(at least the basic sort without an additional constant or a term such as R).

In this case 1 + √5 = 2φ and 1 – √5 = -2/φ are the roots of the characteristic equation x² – 2x – 4 = 0 corresponding to the recurrence relation for P (without the R term).

The factors are chosen to give the correct values for P1 and P2.