This Python program runs in 107ms.

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

from itertools import permutations from collections import Counter from enigma import join, printf # the columns (in some order) cols = ("RBGGBR", "BBGRRG", "GGRBRB", "RGBRBG", "GRRBGB", "BRBGGR") # the rows (in some order) rows = ("BGBRGR", "RBBGRG", "GGRBRB", "RRGBGB", "GBRRBG", "BRGGBR") # turn the columns into a multiset cols = Counter(cols) # consider the possible orderings for the rows for rs in permutations(rows): # the corresponding columns cs = Counter(join(x) for x in zip(*rs)) # check they match the given columns if cs == cols: # output a solution for r in rs: printf("{r}") printf()

**Solution:** The painting is reproduced below:

This run file executes in 135ms.

#!/usr/bin/env python -m enigma -r SubstitutedExpression --answer="EIGHTMAN" "GGG * TTT = ENIGMA" "ordered(*nsplit(GGG * HHH)) == ordered(E, N, I, G, M, A)"]]>

It executes in 205ms.

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

#!/usr/bin/env python -m enigma -r SubstitutedExpression --invalid="0,AEGIMN" # no leading zeros --invalid="2|4|6|8,A" # ENIGMA is odd --verbose=16 # if you only want to see the answer --answer="ENIGMA" # check the given VPDs "ENIGMA % E = 0" "E != 1" "ENIGMA % NI = 0" "ENIGMA % IG = 0" "ENIGMA % G = 0" "G != 1" "ENIGMA % GMA = 0" "ENIGMA % M = 0" "M != 1" "ENIGMA % MA = 0" "ENIGMA % A = 0" "A != 1" # and these are the only VPDs "ENIGMA % N != 0 or N == 1" "ENIGMA % I != 0 or I == 1" "ENIGMA % EN != 0" "ENIGMA % GM != 0" "ENIGMA % ENI != 0" "ENIGMA % NIG != 0" "ENIGMA % IGM != 0" "ENIGMA % ENIG != 0" "ENIGMA % NIGM != 0" "ENIGMA % IGMA != 0" "ENIGMA % ENIGM != 0" "ENIGMA % NIGMA != 0"

The fact that ENIGMA is odd is not necessary to solve the puzzle, but this fact along with the conditions that E, NI and IG divide ENIGMA are enough to narrow the answer down to a single possibility.

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

from itertools import permutations, product from enigma import Football, irange, printf # match values according to the tribes # Pukka - all values match def P(s, t): return all(a == b for (a, b) in zip(s, t)) # Wotta Woppa - no values match def W(s, t): return all(a != b for (a, b) in zip(s, t)) # Shilli-Shalla - alternating matches/non-matching def S(s, t): (s0, s1) = (s[0::2], s[1::2]) (t0, t1) = (t[0::2], t[1::2]) return (P(s0, t0) and W(s1, t1)) or (W(s0, t0) and P(s1, t1)) # generate selections from a bunch of iterators def iproduct(ss): # initial lists of values vs = tuple([] for _ in ss) js = list(j for (j, _) in enumerate(ss)) ss = tuple(iter(s) for s in ss) while js: js2 = list(js) # go through the lists of values for j in js: # add a new value to the list try: x = next(ss[j]) except StopIteration: js2.remove(j) continue vs[j].append(x) # generate selections using this value yield from product(*((v[-1:] if i == j else v) for (i, v) in enumerate(vs))) js = js2 # generators for possible (X, Y) scores in various types of matches X v Y # subject to no team winnong by more than 3 goals # unplayed matches def scores_x(): yield None # win for X against Y def scores_w(): x = 1 while True: for y in irange(0, x - 1): if x - y > 3: continue yield (x, y) x += 1 # draw def scores_d(): x = 0 while True: yield (x, x) x += 1 # win for Y against X def scores_l(): for (x, y) in scores_w(): yield (y, x) # generators for each type of match scores = dict(x=scores_x, w=scores_w, d=scores_d, l=scores_l) # find solutions for a specified 3 team table def solve(table): football = Football(games="wdlx") # consider possible games outcomes for (AB, AC, BC) in football.games(repeat=3): # make the table A = football.table([AB, AC], [0, 0]) B = football.table([AB, BC], [1, 0]) C = football.table([AC, BC], [1, 1]) # collect (played, w, l, d) rows1 = tuple((t.played, t.w, t.l, t.d) for t in (A, B, C)) # choose an assignment of tribes to rows for fns in permutations((P, W, S)): # check the partial rows match the tribes if not all(fn(r, t[:4]) for (fn, r, t) in zip(fns, rows1, table)): continue # find the scores in each match (using the goals for/against values given) for (sAB, sAC, sBC) in iproduct(list(scores[m]() for m in (AB, AC, BC))): # compute goals for, goals against for each team gA = football.goals([sAB, sAC], [0, 0]) gB = football.goals([sAB, sBC], [1, 0]) gC = football.goals([sAC, sBC], [1, 1]) # add in the "goals for", "goals against" columns rows2 = tuple(r + x for (r, x) in zip(rows1, (gA, gB, gC))) # and check the complete rows still match if not all(fn(r, t) for (fn, r, t) in zip(fns, rows2, table)): continue # return a solution (tribe functions, match outcomes, match scores) yield (fns, (AB, AC, BC), (sAB, sAC, sBC)) # the table given (played, w, l, d, goals for, goals against) table = ( (2, 1, 0, 1, 3, 2), (1, 2, 2, 0, 4, 3), (2, 2, 2, 1, 0, 2), ) # solve the problem for (fns, (AB, AC, BC), (sAB, sAC, sBC)) in solve(table): # output solution (tA, tB, tC) = (fn.__name__ for fn in fns) printf("tribes: A={tA}, B={tB}, C={tC}") printf("A vs B = ({AB}) {sAB[0]}-{sAB[1]}") printf("A vs C = ({AC}) {sAC[0]}-{sAC[1]}") printf("B vs C = ({BC}) {sBC[0]}-{sBC[1]}") printf() break # we only want the first solution

**Solution:** A are Pukkas, B are Wotta-Woppas, C are Shilli-Shallas. The scores in the matches are: A vs B = 2-2, A vs C = 1-0, B vs C = 3-0.

The `iproduct()` function takes a (bounded) sequence of (potentially unbounded) iterators and generates tuples corresponding to the Cartesian product of the tuples, every tuple would eventually appear, but if some of the iterables are unbounded the function will never finish (and will eventually run out of memory). The standard `itertools.product()` function does not operate on unbounded iterators.

The program stops after it finds the first solution, and in fact this is the only possible solution.

]]>% A Solution in MiniZinc include "globals.mzn"; var 1..9:E; var 1..9:N; var 1..9:I; var 1..9:G; var 1..9:M; var 1..9:A; var 100000..999999: ENIGMA = 100000*E + 10000*N + 1000*I + 100*G + 10*M + A; var 10..99: NI = 10*N + I; var 100..999: GMA = 100*G + 10*M + A; var 10..99: MA = 10*M + A; var 10..99: IG = 10*I + G; % ENIGMA represents an odd six-digit number constraint ENIGMA mod 2 == 1; % 1 is excluded as being an improper divisor constraint E != 1 /\ G != 1 /\ M != 1 /\ A != 1; % All the digits are different constraint all_different ( [E, N, I, G, M, A] ); % Check all visible proper divisors specified constraint ENIGMA mod E == 0 /\ ENIGMA mod NI == 0 /\ ENIGMA mod IG == 0 /\ ENIGMA mod G == 0 /\ ENIGMA mod GMA == 0 /\ ENIGMA mod M == 0 /\ ENIGMA mod MA == 0 /\ ENIGMA mod A == 0; solve satisfy; output [ "ENIGMA = " ++ show(ENIGMA) ]; % ENIGMA = 921375 % ---------- % Finished in 2s 41msec]]>

and the journey time for distance d miles is 600d/(100 + x) minutes.

The arrival time is that plus x.

By differentiating we find a minimum when (100 + x)² = 600d,

so 100 + x = √(600d) and x = √(600d) – 100, which gives us expressions for both speed and times of departure and arrival in terms of x or d, depending on which we already know.

t = (x² +100x + 4800) / (x + 100)

where *x* is the departure time (in minutes after 9am).

This has a local minimum at:

x = 40√3 − 100 ≈ −30.718

t = 20(4√3 − 5) ≈ 38.564

So Amber should set off at 30.718 minutes before 9am, which we would more normally write as 08:29:17 (to the nearest second).

]]>This Python program runs in 81ms.

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

from enigma import find_min, sprintf, printf # format time x (minutes after 9am) def time(x): x += 540.0 h = int(x) // 60 x -= 60.0 * h m = int(x) s = (x - m) * 60.0 return sprintf("{h:02d}:{m:02d}:{s:05.2f}") # speed if setting off at x minutes after 9am v = lambda x: (10.0 + x / 10.0) # function to return arrival time for cycling distance d (miles) # (setting off at x minutes after 9am) def f(d): return lambda x: x + 60.0 * d / v(x) # find a minimum start time for cycling distance d def fmin(d): return find_min(f(d), -90.0, 90.0) # find Amber's minimum start time r = fmin(8.0) printf("(1) {start} -> {arrive}", start=time(r.v), arrive=time(r.fv)) # if Matthew cycles d miles, how close is minimum start time to 09:20? def M(d): r = fmin(d) return abs(r.v - 20.0) # find the closest value to 09:20 r = find_min(M, 0.0, 50.0) t = fmin(r.v) printf("(2) distance = {r.v:.2f} miles, {start} -> {arrive}", start=time(t.v), arrive=time(t.fv))

**Solution:** (1) Amber should set off at 08:29:17. (2) Matthew cycles 24 miles to work.

To the nearest hundredth of a second, Amber’s start time should be 08:29:16.92. She will arrive at work at 09:38:33.84.

If Matthew sets off at 09:20, he is cycling at an average speed of 12 mph, so he makes his 24 mile journey in 2 hours and arrives at work at 11:20.

]]>This run file executes in 108ms.

#!/usr/bin/env python -m enigma -r # a b c # * d e f # --------- # O N E # T W O # g h i # --------- # T H R E E SubstitutedExpression --symbols="EHNORTWabcdefghi" --distinct="EHNORTW" --answer="THREE" --reorder=0 --invalid="0,OTadg" --invalid="1|3|5|7|9,EO" "abc * f = ONE" "abc * e = TWO" "abc * d = ghi" "abc * def = THREE" "ONE % 6 = 0" "TWO % 6 = 0" "THREE % 6 = 0"]]>

This run files executes in 122ms.

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

#!/usr/bin/env python -m enigma -r SubstitutedDivision "????? / ?x = ??x" "??? - ?? = ?" "" "??? - ??x = 0"

**Solution:** The correct sum is 10176 ÷ 96 = 106.

So, x=6.

]]>% A Solution in MiniZinc include "globals.mzn"; % 2-digit square ia AB, 3-digit square is CDE and 4-digit square is FGHI var 0..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 0..9:H; var 0..9:I; constraint all_different([A, B, C, D, E, F, G, H, I]); % A, B, C and F cannot be zero constraint A != 0 /\ C != 0 /\ F != 0; % Digits 9, 4, 1 and 0 all appeared in each of the three sets constraint D = 0 \/ E = 0 \/ F = 0 \/ G = 0 \/ H = 0 \/ I = 0; constraint A = 1 \/ B = 1 \/ C = 1 \/ D = 1 \/ E = 1 \/ F = 1 \/ G = 1 \/ H = 1 \/ I = 1; constraint A = 4 \/ B = 4 \/ C = 4 \/ D = 4 \/ E = 4 \/ F = 4 \/ G = 4 \/ H = 4 \/ I = 4; constraint A = 9 \/ B = 9 \/ C = 9 \/ D = 9 \/ E = 9 \/ F = 9 \/ G = 9 \/ H = 9 \/ I = 9; % 2, 3 and 4 digits squares in each set var 16..81: AB = 10*A + B; var 100..961: CDE = 100*C + 10*D + E; var 1089..9801: FGHI = 1000*F + 100*G + 10*H + I; set of int: sq2 = {n*n | n in 4..9}; set of int: sq3 = {n*n | n in 10..31}; set of int: sq4 = {n*n | n in 32..99}; constraint AB in sq2 /\ CDE in sq3 /\ FGHI in sq4; solve satisfy; output [ "Sq2 = " ++ show(AB) ++ " Sq3 = " ++ show(CDE) ++ " Sq4 = " ++ show(FGHI) ]; % Sq2 = 16 Sq3 = 784 Sq4 = 9025 - 3 not used - Dick's set % Sq2 = 36 Sq3 = 729 Sq4 = 5041 - 8 not used - Tom or Harry's set % Sq2 = 36 Sq3 = 841 Sq4 = 9025 <<< 7 not used - the extra set and the answer % Sq2 = 36 Sq3 = 289 Sq4 = 5041 - 7 not used - Tom or Harry's set % ------- % ========== % Finished in 85msec]]>

[1] C = 1

[2] B > A

[3] D ≠ 4

[4] C = 4 or D = 4

[5] B ≠ 4

[6] A > D

[1], [3] and [4] cannot all be true, so one of them must be the false statement. Meaning [2], [5], [6] are all true.

So: *B > A > D* and *B ≠ 4*. The only possible assignment is *B = 3, A = 2, D = 1* giving *C = 4*. Which makes [2] and [3] true and [1] is the false statement.

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

from itertools import permutations from enigma import join, printf # logical implication: <p> implies <q> def implies(p, q): return not(p) or q # "safety" scores (higher numbers are safer) scores = (1, 2, 3, 4) # consider assignments of scores for (A, B, C, D) in permutations(scores): # evaluate the statements ss = ( # 1. "C is the most lethal" (C == 1), # 2. "anyone safer than B is safer than A" all(implies(x > B, x > A) for x in scores), # simplifies to: (B > A) # 3. "D is not the safest" (D != 4), # 4. "anyone safer than D is no less lethal than C" all(implies(x > D, x <= C) for x in scores), # simplifies to: (D == 4 or C == 4) # 5. "B is not the safest" (B != 4), # 6. "anyone safer than A is safer than D" all(implies(x > A, x > D) for x in scores), # simplifies to: (A > D) ) # but exactly one of the statements is false if ss.count(False) != 1: continue printf("[A={A} B={B} C={C} D={D}, ss={ss}]") # output a solution (order from safest to least safe) order = tuple(n for (x, n) in sorted(zip((A, B, C, D), "ABCD"), reverse=True)) printf("order = {order}", order=join(order, sep=","))

**Solution:** Cutaway is the safest, then Borethrough, then Axehead, and Divot is the least safe.

The first statement (“C is the most lethal”) is false (in fact C is the safest).

We can replace the evaluations for statements 2 and 6 with the equivalent (and simpler) expressions [[ `(B > A)` ]] and [[ `(A > D)` ]] respectively, and the evaluation for statement 4 can be replaced with [[ `(D == 4 or C == 4)` ]].

% A Solution in MiniZinc include "globals.mzn"; % Digits for the three sets 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:J; % My six prime numbers var 100..999: ABC = 100*A + 10*B + C; var 100..999: ACB = 100*A + 10*C + B; var 100..999: BAC = 100*B + 10*A + C; var 100..999: BCA = 100*B + 10*C + A; var 100..999: CAB = 100*C + 10*A + B; var 100..999: CBA = 100*C + 10*B + A; % Harry's six prime numbers var 100..999: DEF = 100*D + 10*E + F; var 100..999: DFE = 100*D + 10*F + E; var 100..999: EFD = 100*E + 10*F + D; var 100..999: EDF = 100*E + 10*D + F; var 100..999: FED = 100*F + 10*E + D; var 100..999: FDE = 100*F + 10*D + E; % Tom's six prime numbers var 100..999: GHJ = 100*G + 10*H + J; var 100..999: GJH = 100*G + 10*J + H; var 100..999: HJG = 100*H + 10*J + G; var 100..999: HGJ = 100*H + 10*G + J; var 100..999: JHG = 100*J + 10*H + G; var 100..999: JGH = 100*J + 10*G + H; predicate is_prime(var int: x) = x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0)); % My six prime numbers constraint sum ( [ is_prime(ABC), is_prime(ACB), is_prime(BAC), is_prime(BCA), is_prime(CAB), is_prime(CBA) ] ) == 6; % Harry's six prime numbers constraint sum ( [ is_prime(DEF), is_prime(DFE), is_prime(EFD), is_prime(EDF), is_prime(FED), is_prime(FDE) ] ) == 6; % Tom's six prime numbers constraint sum ( [ is_prime(GHJ), is_prime(GJH), is_prime(HJG), is_prime(HGJ), is_prime(JHG), is_prime(JGH) ] ) == 6; % The three sets of digits var set of int: s1 = { A, B, C }; var set of int: s2 = { D, E, F }; var set of int: s3 = { G, H, J }; % Three different sets of numbers found constraint s1 != s2 /\ s1 != s3 /\ s2 != s3; % Order output of digits constraint A < C /\ B < C /\ C < F; constraint D < F /\ E < F /\ F < J ; constraint G < J; solve satisfy; output [ "My Digits = " ++ show(A) ++ ", " ++ show(B) ++ ", " ++ show(C) ++ "\n" ++ "Harry's Digits = "++ show(D) ++ ", " ++ show(E) ++ ", " ++ show(F) ++ "\n" ++ "Tom's Digits = "++ show(G) ++ ", " ++ show(H) ++ ", " ++ show(J) ]; % My Digits = 1, 1, 3 % Harry's Digits = 3, 3, 7 % Tom's Digits = 1, 9, 9 % ---------- % ========== % Finished in 187msec]]>

import datetime import sqlite3 import json # to remove unicode prefix when printing strings # populate a table with all dates in year 2000 days = [] dt = datetime.datetime(2000, 1, 1) for n in xrange(366): days.append( (dt.strftime('%A'), int(dt.strftime('%d')), dt.strftime('%B')) ) dt += datetime.timedelta(days=1) db = sqlite3.connect(':memory:') c = db.cursor() c.execute('CREATE TABLE Y2000(DAY varchar, NUMBER integer, MONTH varchar)') c.executemany('INSERT INTO Y2000 VALUES(?,?,?)', days) db.commit() c.execute('SELECT COUNT(*) from Y2000') print "Y2000 count:", c.fetchall()[0][0] # If I told you the DAY and the NUMBER you could also work out the MONTH. c.execute('create view Y2000_1 as ' + 'select DAY,NUMBER from Y2000 '+ 'group by DAY,NUMBER '+ 'having count(MONTH)=1') c.execute('create view Y2000_2 as ' + 'select DAY,NUMBER,MONTH from Y2000 '+ 'where DAY||NUMBER in (select DAY||NUMBER from Y2000_1)') c.execute('SELECT COUNT(*) from Y2000_2') print "Y2000_2 count:", c.fetchall()[0][0] # So now if I told you the first letter in the spelling of the MONTH you could # work out the MONTH. c.execute('create view Y2000_3 as ' + 'select substr(MONTH,1,1) M1 '+ 'from (select distinct MONTH from Y2000_2) '+ 'group by M1 '+ 'having count(M1)==1 ') c.execute('create view Y2000_4 as ' + 'select DAY,NUMBER,MONTH from Y2000_2 '+ 'where substr(MONTH,1,1) in (select M1 from Y2000_3)') c.execute('SELECT COUNT(*) from Y2000_4') print "Y2000_4 count:", c.fetchall()[0][0] # So now if I told you how many Es there are in the spelling of the MONTH you # could work out the MONTH. c.execute('create view Y2000_5 as ' + "select length(MONTH)-length(replace(MONTH,'e','')) es "+ 'from (select distinct MONTH from Y2000_4) '+ 'group by es '+ 'having count(es)==1 ') c.execute('create view Y2000_6 as ' + 'select DAY,NUMBER,MONTH from Y2000_4 '+ "where length(MONTH)-length(replace(MONTH,'e','')) in (select es from Y2000_5)") c.execute('SELECT COUNT(*) from Y2000_6') print "Y2000_6 count:", c.fetchall()[0][0] # So now if I told you the NUMBER you could work out the DAY and MONTH. c.execute('create view Y2000_7 as ' + 'select NUMBER from Y2000_6 '+ 'group by NUMBER '+ 'having count(NUMBER)==1 ') c.execute('create view Y2000_8 as ' + 'select DAY,NUMBER,MONTH from Y2000_6 '+ "where NUMBER in (select NUMBER from Y2000_7)") c.execute('SELECT * from Y2000_8') print "Y2000_8", json.dumps(c.fetchall())]]>

This Python program executes in 84ms.

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

from datetime import date, timedelta from collections import namedtuple from enigma import filter_unique, printf # values for DAY DAY = [ "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" ] # values for MONTH MONTH = [ "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" ] # create (day, number, month) tuples for the required year Date = namedtuple('Date', 'day, number, month') year = 2000 rs = list() d = date(year, 1, 1) i = timedelta(1) while d.year == year: rs.append(Date(DAY[d.weekday()], d.day, MONTH[d.month - 1])) d += i # "if I told you the DAY and the NUMBER you could work out the MONTH" (rs, _) = filter_unique(rs, (lambda d: (d.day, d.number)), (lambda d: d.month)) # "if I told you the first letter in MONTH you could work out the MONTH" (rs, _) = filter_unique(rs, (lambda d: d.month[0]), (lambda d: d.month)) # "if I told you how many E's there were in MONTH you could work out the MONTH" (rs, _) = filter_unique(rs, (lambda d: d.month.count('E')), (lambda d: d.month)) # "if I told you the NUMBER you could work out the DAY and MONTH" (rs, _) = filter_unique(rs, (lambda d: d.number), (lambda d: (d.day, d.month))) # output solutions for (day, number, month) in rs: printf("DAY={day} NUMBER={number} MONTH={month}")

**Solution:** DAY=Wednesday, NUMBER=30, MONTH=August.

From the initial set of 366 candidates, the statements narrow down the possibilities to 96, then 64, then 3 and finally 1.

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

from enigma import isqrt, SubstitutedExpression # return the number of squares between a and b (inclusive, a < b) def nsquares(a, b): if b < a: return 0 # the number of squares between 0 and n (inclusive) is isqrt(n) + 1 i = isqrt(b) return (i + 1 if a == 0 else i - isqrt(a - 1)) # invalid digits d2i = { 0: 'NOT' } # no leading zeros for d in (1, 3, 5, 7, 9): d2i[d] = 'ENOTY' # even numbers # solve the alphametic expressions SubstitutedExpression( [ "nsquares(TEN, TWENTY) = ONE", "TEN % 3 != 0" ], env=dict(nsquares=nsquares), d2i=d2i, answer="NOW", ).go()

**Solution:** NOW = 869.

The assignment of letters to digits is:

E=2 N=8 O=6 T=4 W=9 Y=0

There are two further solutions where TEN *is* a multiple of 3:

]]>E=0 N=2 O=6 T=4 W=1 Y=8

E=0 N=8 O=6 T=4 W=9 Y=2