The first programme is for Harry and Tom’s numbers, with multiple output configuration giving only two solutions, with the palindromic condition identifying Harry and Tom’s Numbers

% A Solution in MiniZinc for Harry and Tom's numbers include "globals.mzn"; % Harry and Toms 5,6 and 7 digit numbers are in format ABCBA, ABFGBA and ABHIJBA % with Harry's all palindromic and Tom's not all palindromic, % with the 5-digit number palindromic by definition var 1..9:A; var 1..9:B; var 0..9:C; % ABC var 0..9:F; var 0..9:G; % FG var 0..9:H; var 0..9:I; var 0..9:J; % HIJ constraint A != B; var 10000..99999: ABCBA = 10000*A + 1000*B + 100*C + 10*B + A; var 100000..999999: ABFGBA = 100000*A + 10000*B + F*1000 + 100*G + 10*B + A; var 1000000..9999999: ABHIJBA = 1000000*A + 100000*B + 10000*H + 1000*I + 100*J + 10*B + A; set of int: sq5 = {n*n | n in 100..316}; set of int: sq6 = {n*n | n in 317..999}; set of int: sq7 = {n*n | n in 1000..3162}; constraint ABCBA in sq5 /\ ABFGBA in sq6 /\ ABHIJBA in sq7; solve satisfy; output["Tom and Harry's numbers are " ++ show(ABCBA) ++ ", " ++ show(ABFGBA) ++ ", " ++ show(ABHIJBA) ]; % Tom and Harry's numbers are 14641, 143641, 1490841 - Tom's numbers (not all palindromic) %---------- % Tom and Harry's numbers are 69696, 698896, 6948496 - Harry's numbers (all palindromic) %---------- % ========== % Finished in 64msec

The second MiniZinc programme is for Dick’s numbers and also gives the same answers as Jim’s solution

% A Solution in MiniZinc for Dick's Numbers include "globals.mzn"; % Dicks 5,6 and 7 digit numbers are in the format abcab, abfgab and abhijab var 1..9: a; var 1..9: b; var 0..9: c; var 0..9: f; var 0..9: g; var 0..9: h; var 0..9: i; var 0..9: j; constraint a != b; var 10000..99999: abcab = 10000*a + 1000*b + 100*c + 10*a + b; var 100000..999999: abfgab = 100000*a + 10000*b + 1000*f + 100*g + 10*a + b; var 1000000..9999999: abhijab = 1000000*a + 100000*b + 10000*i + 1000*j + 10*a + b; set of int: sq5 = {n*n | n in 100..316}; set of int: sq6 = {n*n | n in 317..999}; set of int: sq7 = {n*n | n in 1000..3162}; constraint abcab in sq5 /\ abfgab in sq6 /\ abhijab in sq7; solve satisfy; output [ "Dick's numbers are " ++ show(abcab) ++ ", " ++ show(abfgab) ++ ", " ++ show(abhijab) ]; % Dick's numbers are 76176, 763876, 7695076 % ---------- % Dick's numbers are 76176, 767376, 7695076 % ---------- % ========== % Finished in 70msec]]>

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

from collections import defaultdict from itertools import product from enigma import irange, printf # test for palindromic strings is_palindromic = lambda x: x == x[::-1] # record square numbers of the form AB...AB (type1) and AB...BA (type2) type1 = defaultdict(lambda: defaultdict(list)) type2 = defaultdict(lambda: defaultdict(list)) for i in irange(100, 3162): s = str(i * i) n = len(s) k = s[:2] if k == s[-2:]: type1[k][n].append(s) if k == s[:-3:-1]: type2[k][n].append(s) # find D's 2-digit number for (k, vs) in type1.items(): # consider possible 5-, 6-, 7- digit numbers for (n5, n6, n7) in product(vs[5], vs[6], vs[7]): printf("D = {k} [{n5}, {n6}, {n7}]") # find H and T's numbers for (k, vs) in type2.items(): # consider possible 5-, 6-, 7- digit numbers for (n5, n6, n7) in product(vs[5], vs[6], vs[7]): # are they all palindromic? t = ('H' if all(is_palindromic(x) for x in (n5, n6, n7)) else 'T') printf("{t} = {k} [{n5}, {n6}, {n7}]")

**Solution:** (1) Dick’s 7-digit number is 7695076; (2) Harry’s 7-digit number is 6948496; (3) Tom’s 7-digit number is 1490841.

So, the 2-digit numbers are D=76, H=69, T=14.

Harry’s (all palindromic) numbers are: 69696, 698896, 6948496.

Tom’s (not all palindromic) numbers are: 14641, 143641, 1490841.

Dick has a choice of 6-digit numbers. His numbers are: 76176, (763876 or 767376), 7695076.

]]>The following run file executes in 115ms.

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

#!/usr/bin/env python -m enigma -r SubstitutedDivision "t???? / ?b = a?tb" "t? - ?? = g?" "g?? - b?? = g?" "g?? - g?? = ya" "ya? - a?t = ?a"

**Solution:** The sum is: 95080 ÷ 53 = 1793 (remainder 51).

The puzzle presents a collection of “cause sets” and “event sets”, where the collection of causes in the cause set, entail the collection of events in the event set. We need to determine which individual causes entail specific events.

The program works using the principle that for any particular event the cause that triggers it must be contained in the intersection of all cause sets that have that event in the corresponding event set, and cannot appear in the union of the cause sets that do not have that event in the corresponding event set.

This Python program runs in 75ms.

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

from enigma import flatten, join, sprintf as f # intersection of a bunch of sequences def intersection(first, *rest): return set(first).intersection(*rest) # find possible causes for event e def causes(e, css, ess): # go through the list of (causes, events) # and collect causes that may cause event e (ins) # and those that don't cause event e (outs) (ins, outs) = (list(), list()) for (cs, es) in zip(css, ess): (ins if e in es else outs).append(cs) # result is the intersection of ins, less the union of the outs return intersection(*ins).difference(*outs) # <ss> is a sequence of (<causes>, <events>) pairs # find maps: <cause> -> <event> # that satisfy the given pairs def solve(css, ess): # find possible causes for each event d = dict() for e in flatten(ess): if e in d: continue cs = causes(e, css, ess) if not cs: return None d[e] = cs # output a solution print(f("{ss} -> {d}", ss='[' + join((f("{cs} -> {es}") for (cs, es) in zip(css, ess)), sep=", ") + ']', d='[' + join((f("{k} <- {vs}", vs=(join(sorted(d[k]), sep="|") or 'None')) for k in sorted(d.keys())), sep=", ") + ']' )) # causes and events given in the puzzle css = ["DE", "BCE", "ACD"] # sequence of causes ess = ["qr", "qst", "pt"] # corresponding events # consider each of the causes for (i, cs) in enumerate(css): css1 = list(css) if len(cs) > 1: # delete one of the causes for (j, _) in enumerate(cs): css1[i] = cs[:j] + cs[j + 1:] solve(css1, ess) # add an exra cause (F) css1[i] = cs + 'F' solve(css1, ess)

**Solution:** Sergeant Simple either added a D to statement 3, or missed an F from statement 1. We can say for sure that: A causes p, B causes s, C causes t, and E causes q. But we can’t be sure about the cause of r (it’s D in the first case and F in the second).

There are two possible corrected collections of statements:

1. Deleting the D from the statement 3 gives:

D+E → q+r

B+C+E → q+s+t

A+C → p+t

This has a solution of:

A → p

B → s

C → t

D → r

E → q

2. Adding cause F to statement 1 gives:

D+E+F → q+r

B+C+E → q+s+t

A+C+D → p+t

This has a solution of:

A → p

B → s

C → t

D → –

E → q

F → r

The common pairings to both these cases are:

A → p

B → s

C → t

E → q

*r* is caused by *D* or *F*, in the two cases, so we can’t be sure.

The published solution seems to have missed the first of these cases, so is solely derived from the second case. Although I think the first case is neater, as it doesn’t have a cause with no effect.

]]>It runs in 392ms.

from minizinc import MiniZinc from enigma import partition, irange, join, printf # find an example tournament given the number of wins by each player def example(wins): n = len(wins) p = MiniZinc(f""" % win: <player i> vs <player j> = 1 iff <player i> wins array [1..15, 1..15] of var 0..1: win; % no-one plays themselves constraint forall (i in 1..15) (win[i, i] = 0); % only one winner per match constraint forall (i, j in 1..15 where i < j) (win[i, j] + win[j, i] = 1); % each player wins the required number of times {join( (f"constraint sum (j in 1..15) (win[{i}, j]) = {w}" for (i, w) in enumerate(wins, start=1)), sep=";" )}; solve satisfy; """) # gender of player i g = list(('M' if 2 * w > n else 'F') for w in wins) g.insert(0, None) # make it 1-indexed # look for an example solution for s in p.solve(solver="mzn-gecode"): # output the solution win = s['win'] r = 0 for (i, w) in enumerate(wins, start=1): ws = list(j for (j, w) in win[i].items() if w) printf("player {i}{g[i]}: {k} wins = [{ws}]", ws=join((f"{w}{g[w]}" for w in ws), sep=" "), k=len(ws)) if g[i] == 'F': r += sum(1 for w in ws if g[w] == 'M') printf("wins for F vs. M = {r}") return # no solutions print("[UNSATISFIABLE]") # decompose <t> into <k> numbers from <ns> def decompose(t, k, ns, s=[]): if k == 1: if t in ns: yield s + [t] else: for (i, n) in enumerate(ns): if not(n < t): break yield from decompose(t - n, k - 1, ns[i:], s + [n]) # primes below 15 primes = (2, 3, 5, 7, 11, 13) # find valid decompositions for s in decompose(105, 15, primes): # each prime should appear a prime number of times if all(s.count(p) in primes for p in primes): # partition the scores into women and men (men, women) = partition((lambda x: x > 7), s) (w, m) = (len(women), len(men)) printf("{w} women = {women}, {m} men = {men}") # find an example outcome example(s[::-1]) printf()

And here’s the output:

10 women = [2, 2, 3, 3, 3, 5, 5, 7, 7, 7], 5 men = [11, 11, 13, 13, 13] [UNSATISFIABLE] 10 women = [2, 2, 3, 3, 5, 5, 5, 7, 7, 7], 5 men = [11, 11, 11, 13, 13] player 1M: 13 wins = [2M 4M 5M 6F 7F 8F 9F 10F 11F 12F 13F 14F 15F] player 2M: 13 wins = [3M 4M 5M 6F 7F 8F 9F 10F 11F 12F 13F 14F 15F] player 3M: 11 wins = [1M 6F 7F 8F 9F 10F 11F 12F 13F 14F 15F] player 4M: 11 wins = [3M 6F 7F 8F 9F 10F 11F 12F 13F 14F 15F] player 5M: 11 wins = [3M 4M 6F 7F 8F 9F 10F 11F 12F 13F 14F] player 6F: 7 wins = [7F 8F 9F 10F 11F 12F 13F] player 7F: 7 wins = [8F 9F 10F 11F 12F 14F 15F] player 8F: 7 wins = [9F 10F 11F 12F 13F 14F 15F] player 9F: 5 wins = [10F 12F 13F 14F 15F] player 10F: 5 wins = [11F 12F 13F 14F 15F] player 11F: 5 wins = [9F 12F 13F 14F 15F] player 12F: 3 wins = [13F 14F 15F] player 13F: 3 wins = [7F 14F 15F] player 14F: 2 wins = [6F 15F] player 15F: 2 wins = [5M 6F] wins for F vs. M = 1

In this example, the match 5M vs. 15F is won by player 15 (female).

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

from enigma import partition, T, printf # decompose <t> into <k> numbers from <ns> def decompose(t, k, ns, s=[]): if k == 1: if t in ns: yield s + [t] else: for (i, n) in enumerate(ns): if not(n < t): break yield from decompose(t - n, k - 1, ns[i:], s + [n]) # primes below 15 primes = (2, 3, 5, 7, 11, 13) # find valid decompositions for s in decompose(105, 15, primes): # each prime should appear a prime number of times if not all(s.count(p) in primes for p in primes): continue # partition the scores into women and men (men, women) = partition((lambda x: x > 7), s) (w, m) = (len(women), len(men)) printf("{w} women = {women}, {m} men = {men}") # total number of matches = T(15 - 1) = 105 # number of F vs F matches = T(w - 1) # so the number of matches where at least one player is a man is: 105 - T(w - 1) matchesM = T(14) - T(w - 1) # and the number of matches won by the men are: winM = sum(men) # so the number of matches where a women beat a man winF = matchesM - winM printf("{matchesM} matches with at least one M, {winM} matches won by M, {winF} matches won by F") printf("[{s}]", s=("NOT POSSIBLE" if winF < 0 else "SOLUTION")) printf()

**Solution:** 10 women entered the tournament. There was 1 match were a woman beat a man.

Here’s some analysis:

There are 15 players, so *T(15 – 1)* = 105 matches in total.

And there are two ways to decompose 105 into 15 primes below 15, where each prime appears a prime number of times:

[1] (2 + 2 + 3 + 3 + 3 + 5 + 5 + 7 + 7 + 7) + (11 + 11 + 13 + 13 + 13) = 105

[2] (2 + 2 + 3 + 3 + 5 + 5 + 5 + 7 + 7 + 7) + (11 + 11 + 11 + 13 + 13) = 105

Win totals up to 7 belong to the women, totals of 8 or more belong to the men. So in both cases there are 10 women and 5 men in the tournament.

There are *T(10 – 1)* = 45 matches where a woman plays another woman.

So there are (105 – 45) = 60 remaining matches where at least one player is a man.

In case [1] the total number of matches won by the men is (11 + 11 + 13 + 13 + 13) = 61. This is not possible, as there are only 60 matches that can possibly be won by a man.

In case [2] the total number of matches won by the men is (11 + 11 + 11 + 13 + 13) = 59, which leaves 1 match involving a man that must have been won by a woman.

So this gives the required solution.

]]>from itertools import product from enigma import irange, tuples, nconcat, printf digits = tuple(irange(0, 9)) (incs, decs) = (list(), list()) # consider possible 5-digit increasing numbers for (i, s1) in enumerate(tuples(digits, 5)): inc = nconcat(s1) dec = nconcat(reversed(s1)) # and 3-digit increasing numbers that don't share digits for s2 in tuples((digits[i + 5:] if i < 3 else digits[:i]), 3): incs.append((inc, nconcat(s2))) decs.append((dec, nconcat(reversed(s2)))) # find solutions for ((m1, t1), (m2, t2)) in product(incs, decs): # my mileage (m2 - m1) = d is less than the initital mileage d = m2 - m1 if not(0 < d < m1 and (t1 + d) % 1000 == t2): continue # output solution printf("d={d} [m1={m1:05d} m2={m2:05d}, t1={t1:03d} t2={t2:03d}]")]]>

# consecutive three (increasing) digit sequences i3 = [111 * d + 12 for d in range(8)] # consecutive five (increasing) digit sequences i5 = [11111 * d + 1234 for d in range(6)] # consecutive three (decreasing) digit sequences d3 = [111 * d - 12 for d in range(2, 10)] # consecutive five (decreasing) digit sequences d5 = [11111 * d - 1234 for d in range(4, 10)] # try all combinations of first and second sale mileages for m1 in i5: for m2 in d5: # the second owner does less miles than the first if not (0 < m2 - m1 < m1): continue s1, s2 = set(str(m1)), set(str(m2)) # consider the trip meter at the first sale for t1 in i3: # which shares no digits with the mileage if s1.intersection(str(t1)): continue # find the trip meter mileage at the second sale t2 = (t1 + m2 - m1) % 1000 # which shares no digits with the mileage if t2 not in d3 or s2.intersection(str(t2)): continue print(f'{m2-m1:5d} miles (buy:{m1:05d}/{t1:03d}, sell:{m2:05d}/{t2:03d}).')]]>

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

from enigma import irange, tuples, nconcat, nsplit, printf digits = tuple(irange(0, 9)) # consider possible 5-digit increasing numbers # for the initial reading for (i, s1) in enumerate(tuples(digits, 5)): m1 = nconcat(s1) # consider possible 5-digit decreasing numbers # for the final reading for s2 in tuples(digits[::-1], 5): m2 = nconcat(s2) # my mileage (m2 - m1) = d is less than the initial mileage d = m2 - m1 if not(0 < d < m1): continue # consider possible 3-digit increasing trip readings # with no numbers in common with the initial mileage for r1 in tuples((digits[i + 5:] if i < 3 else digits[:i]), 3): t1 = nconcat(r1) # which gives a final trip reading of... t2 = (t1 + d) % 1000 # the digits should be decreasing r2 = nsplit(t2, 3) if not all(a - b == 1 for (a, b) in tuples(r2, 2)): continue # and have no digits in common with the final mileage if len(set(s2 + r2)) != 8: continue # output solution printf("d={d} [m1={m1:05d} m2={m2:05d}, t1={t1:03d} t2={t2:03d}]")

**Solution:** The car had gone 41976 miles since acquired by the setter.

When the setter acquired the car it had done 56789 miles, and the trip odometer read 234.

The setter disposed of the car after having gone 41976 miles, bringing the total mileage to 98765 miles.

The trip odometer now reads the final three digits of (234 + 976) = 1210, i.e. 210.

]]>This run file executes in 183ms.

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

#!/usr/bin/env python -m enigma -r SubstitutedSum "SEVEN + THREE + TWO = TWELVE"

**Solution:** TWELVE = 102352.

We can make two different sums, because the values of N and O can be interchanged.

]]>82524 + 19722 + 106 = 102352

82526 + 19722 + 104 = 102352

I then wrote a Python program that uses the **minizinc.py** library to format the output from *MiniZinc*, and the whole thing executes in 365ms.

Here’s the *MiniZinc* model (`enigma1046.mzn`):

%#! mzn-gecode -a % label the teams int: A = 1; int: B = 2; int: C = 3; int: R = 4; int: U = 5; % week[<week-no>, <team>] = <opponent> or 0 if not playing array [1..5, 1..5] of var 0..5: week; % each week exactly one team doesn't play constraint forall (w in 1..5) (sum (t in 1..5) (week[w, t] = 0) = 1); % each team doesn't play in exactly one week constraint forall (t in 1..5) (sum (w in 1..5) (week[w, t] = 0) = 1); % opponents reciprocate constraint forall (w in 1..5, t in 1..5) (week[w, t] != 0 -> week[w, week[w, t]] = t); % each team plays each other team once constraint forall (t in 1..5) (forall (x in 0..5 where x != t) (sum (w in 1..5) (week[w, t] == x) = 1)); % result[<week-no>, <team>] = 2 for a win, 1 for a draw, 0 for a lose array [1..5, 1..5] of var {0, 1, 2}: result; % scores for a non-playing team are 0 constraint forall (w in 1..5, t in 1..5) (week[w, t] = 0 -> result[w, t] = 0); % scores for opponents sum 2 constraint forall (w in 1..5, t in 1..5) (week[w, t] != 0 -> result[w, t] + result[w, week[w, t]] = 2); % B beat C in week 4 constraint week[4, B] = C /\ result[4, B] = 2; % total half-points for team t in the new system var int: new_points(var 1..5: t) = sum (w in 1..5) (w * result[w, t]); % total for team A in the new system is 7 (= 14 half-points) constraint new_points(A) = 14; % total for teams B, C, R in the new system is 6 (= 12 half-points) constraint new_points(B) = 12; constraint new_points(C) = 12; constraint new_points(R) = 12; % total for team U in the new system is 5 (= 10 half-points) constraint new_points(U) = 10; % total half-points for team t in the traditional system var int: old_points(var 1..5: t) = sum (w in 1..5) (result[w, t]); % points for each team var int: pA = new_points(A); var int: pA = old_points(A); var int: pB = old_points(B); var int: pC = old_points(C); var int: pR = old_points(R); var int: pU = old_points(U); % order would be reversed (U wins, R, C, B tie, A loses) constraint pA < pB /\ pB = pC /\ pC = pR /\ pR < pU; solve satisfy;

And here’s the *Python* wrapper program:

from minizinc import MiniZinc from enigma import irange, sprintf as f # load the model p = MiniZinc("enigma1046.mzn") # map indices to teams team = dict(enumerate("-ABCRU")) # solve the model for (week, result) in p.solve(result="week result"): # week i for i in irange(1, 5): (w, r) = (week[i], result[i]) print(f("week {i}:")) # team j for j in irange(1, 5): t = team[j] u = team[w[j]] if u == "-": print(f(" team {t}: no match")) else: m = ["lose", "draw", "win"][r[j]] s = (f(" (+{p:.1f} points)", p=0.5 * r[j] * i) if r[j] else "") print(f(" team {t}: played {u}, {m}{s}"))

**Solution:** The results for Rangers were:

week 1: no match.

week 2: Rangers vs. United, match drawn.

week 3: Borough vs. Rangers, win for Rangers.

week 4: Albion vs. Rangers, match drawn.

week 5: City vs. Rangers, win for City.

The complete results are:

week 1: A vs C = win for C, B vs U = win for U, R does not play.

week 2: A vs B = win for B, R vs U = draw, C does not play.

week 3: B vs R = win for R, C vs U = win for U, A does not play.

week 4: A vs R = draw, B vs C = win for B, U does not play.

week 5: A vs U = win for A, C vs R = win for C, B does not play.

Giving points in the traditional system of: A=3, B=C=R=4, U=5.

]]>