LikeLike

]]>We can place lower and upper limits on the values of the stamps.

The stamps are ordered: *B* > *R* > *G* > *V* > *Y*, which gives minimum values of: 5, 4, 3, 2, 1.

And 5 black stamps are used in making a value of 100, so black must have a value of less than (100 – (3 + 3×2)) / 5 = 18.2.

Similarly, 5 green stamps are used in making a value of 50, so green must have a value of less than (50 – (5 + 4 + 2 + 1)) / 5 = 7.6.

So we can write down the following ranges for each denomination:

B:5..18

R:4..17

G:3..7

V:2..6

Y:1..5

And we have three equations relating the values. So choosing values for *V* and *Y* will give us a set of 5 simultaneous equations that can be solved to find the other values.

This Python program use the [[ `matrix.linear()`

]] solver for systems of linear equations from the **enigma.py** library. It runs in 63ms.

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

from enigma import irange, subsets, matrix, as_int, express, group, printf # choose values for Y and V for (Y, V) in subsets(irange(1, 6), size=2): # make the equations eqs = [ # B R G V Y = ??? ((5, 0, 1, 3, 0), 100), # 100-pim parcel ((1, 2, 2, 2, 2), 50), # 1st 50-pim parcel ((1, 1, 5, 1, 1), 50), # 2nd 50-pim parcel ((0, 0, 0, 0, 1), Y), # Y value ((0, 0, 0, 1, 0), V), # V value ] # solve the equations for integer values # (either matrix.linear() or as_int() may raise a ValueError) try: (B, R, G, V, Y) = (as_int(x) for x in matrix.linear(*zip(*eqs))) except ValueError: continue # check ordering if not(B > R > G > V > Y > 0): continue # find combinations that make 50, grouped by the total number of stamps d = group(express(50, [Y, V, G, R, B]), by=sum) # output minimal configurations k = min(d.keys()) for (y, v, g, r, b) in d[k]: printf("{k} stamps; 50 = {b}x {B} (black), {r}x {R} (red), {g}x {G} (green), {v}x {V} (violet), {y}x {Y} (yellow)")

**Solution:** A 50-pim parcel can be sent using 5 stamps: 2 black, 1 red, 1 green, 1 yellow.

The values of the stamps are:

black = 18 pim

red = 9 pim

green = 4 pim

violet = 2 pim

yellow = 1 pim

So each stamp is half (rounded down) the next highest value.

There are 621 different ways to make 50 using the above denominations.

Here’s a manual derivation of the denominations:

[1]B + 2R + 2G + 2V + 2Y = 50

[2]B + R + 5G + V + Y = 50

Taking 2×**[2]** – **[1]** gives:

B + 8G = 50

And we know *G* is in the range 3 .. 7, and *B* is in the range 5 .. 18 and *G* < *B*:

G = 3 ⇒ B = 26 [not possible]

G = 4 ⇒ B = 18

G = 5 ⇒ B = 10

G = 6 ⇒ B = 2 [not possible]

G = 7 ⇒ B = –6 [not possible]

And:

5B + G + 3V = 100

Where *V* is in the range 2 .. 6 and *V* < *G*:

G = 4, B = 18 ⇒ V = 2

G = 5, B = 10 ⇒ V = 15 [not possible]

So we have: *V* = 2, *G* = 4, *B* = 18.

From which we see *Y* = 1, and *R* = 9.

LikeLike

]]>from collections import Counter # max prime N = 13 # list of prime numbers P = [2, 3, 5, 7, 11, 13] # 4-digit palindromes palindromes = sorted(1001 * a + 110 * b for a in range(1, 10) for b in range(0, 10)) # initialize dictionary (divisibility by prime) d = {key: [] for key in palindromes} singles = [] # consider sequences of primes by increasing length for p in P: # maintain dictionary (divisibility by prime) for x in palindromes: d[x].append(x % p == 0) c = Counter(map(tuple, d.values())) # get combinations unique by divisibility of primes # but not unique by shorter sequences c2 = [k for k, v in c.items() if v == 1 and all(k[:-i] not in singles for i in range(1, len(k)))] if len(c2) == 1: for x in palindromes: if d[x] == list(c2[0]): print(f"{p} -> {x}") if p != N: print("Error: palindrome already known before prime", N) exit() # add "unique by shorter sequences" to singles for x in [k for k, v in c.items() if v == 1]: singles.append(x)

LikeLike

]]>This Python program considers increasing prime numbers *p*, and looks for palindromes that can be uniquely identified by knowing if it is divisible by primes up to (and including) *p*.

To solve this puzzle we look up to *p = 13*, but the maximum prime can be specified on the command line. (We have to go to 859 to be sure of determining the palindrome).

The program runs in 45ms.

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

from enigma import Primes, irange, filter_unique, diff, join, arg, printf # max prime N = arg(13, 0, int) # 4-digit palindromes palindromes = sorted(1001 * a + 110 * b for a in irange(1, 9) for b in irange(0, 9)) #palindromes.remove(2772) # puzzle text says 2772 is excluded, but that makes it impossible # palindromes unique by divisibility of primes, but not unique by shorter sequences r = set() # consider sequences of primes by increasing length ps = list() for p in Primes(N + 1): ps.append(p) # find unique palindromes by this sequence of primes s = filter_unique(palindromes, lambda x: tuple(x % p == 0 for p in ps)).unique # unique palindromes we haven't seen before s = diff(s, r) if s: printf("{p} -> {s}", s=join(s, sep=", ", enc="()")) # update the list of unique palindromes r.update(s)

**Solution:** The number was 6006.

If 2772 is removed from the set of possible palindromes (by removing the comment from line 8), we see that 8778 would also be uniquely identified by the divisibility values for primes up to 13.

So although the setter would know the answer (because he knows if the number is divisible by 13 or not), we can’t work it out:

6006 → 2=Y, 3=Y, 5=N, 7=Y, 11=Y, 13=Y

8778 → 2=Y, 3=Y, 5=N, 7=Y, 11=Y, 13=N, [17=N, 19=Y]

2772 → 2=Y, 3=Y, 5=N, 7=Y, 11=Y, 13=N, [17=N, 19=N]

But, if 2772 is included in the set of possible palindromes, then 8778 and 2772 cannot be distinguished until we are told the answer for divisibility by 19, so the fact that the setter can work out the number at *p = 13* means that it must be 6006.

There are two palindromes that can be distinguished with a shorter sequence of answers:

5005 → 2=N, 3=N, 5=Y, 7=Y

5775 → 2=N, 3=Y, 5=Y, 7=Y

Note that all palindromes are divisible by 11.

LikeLike

]]>from functools import reduce # convert numbers to digit sequences and vice versa # Credit: B. Gladman d2n = lambda s: reduce(lambda x, y: 10 * x + y, s) n2d = lambda n: [n] if n < 10 else n2d(n // 10) + [n % 10] # consider the grid: # # A B # C D E # F G # H I # # J # K L # M # N P Q R # S # # T U # V W # # # # X Y Z # "VW * 20 = CHLR" for V in range(1, 10): for W in range(10): CHLR = d2n([V, W]) * 20 if len(str(CHLR)) != 4: continue (C, H, L, R) = n2d(CHLR) # "AB * AB = CDE" for A in range(1, 10): for B in range(1, 10): AB = d2n([A, B]) CDE = AB * AB if len(str(CDE)) != 3: continue (c, D, E) = n2d(CDE) if c != C: continue # "2 * NPQR == 3 * CHLR" NPQR = int(1.5 * (CHLR)) if len(str(NPQR)) != 4: continue (N, P, Q, r) = n2d(NPQR) if r != R: continue # "PT * KL = CHLR" for K in range(1, 10): PT = CHLR // d2n([K, L]) if len(str(PT)) != 2: continue (p, T) = n2d(PT) if p != P: continue # "CHLR + PT = MSWZ" MSWZ = CHLR + PT if len(str(MSWZ)) != 4: continue (M, S, W, Z) = n2d(MSWZ) # "2 * (FG - AB) = VY" for Y in range(10): FG = AB + d2n([V, Y]) // 2 if len(str(FG)) != 2: continue (F, G) = n2d(FG) # "TU + 6 = HI" for U in range(10): HI = d2n([T, U]) + 6 if len(str(HI)) != 2: continue (h, I) = n2d(HI) if h != H: continue # "HI < DI" if not HI < d2n([D, I]): continue # "HI * 7 = KQU" KQU = HI * 7 if len(str(KQU)) != 3: continue (k, q, u) = n2d(KQU) if k != K or q != Q or u != U: continue # "is_cube(XYZ)" for X in [1, 2, 3, 5, 7]: XYZ = d2n([X, Y, Z]) if not XYZ in {125, 216, 343, 512, 729}: continue # "AFJN % BG = 0" for J in range(10): AFJN = d2n([A, F, J, N]) if not AFJN % d2n([B, G]) == 0: continue print("CHLR CDE AB PT FG HI KQU MSWZ XYZ") print(CHLR, CDE, AB, PT, FG, HI, KQU, MSWZ, XYZ) print("A B C D E F G H I J K L") print(A, B, C, D, E, F, G, H, I, J, K, L) print("M N P Q R S T U V W X Z") print(M, N, P, Q, R, S, T, U, V, W, X, Z)

LikeLike

]]>LikeLike

]]>I used the [[ `SubstitutedExpression`

]] solver from the **enigma.py** library to solve the puzzle as a collection of alphametic expressions.

The following run file executes in 131ms.

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

#!/usr/bin/env python -m enigma -r # consider the grid: # # A B # C D E # F G # H I # # J # K L # M # N P Q R # S # # T U # V W # # # # X Y Z SubstitutedExpression --distinct="" # 1a. AB = "Number of redpoll cows" # 3a. CDE = "Square of the number of redpoll cows" # 5a. FG = "Total number of cows" # 12d. VY = "Total number of horns possessed by all the cattle" "AB * AB = CDE" "AB < FG" "2 * (FG - AB) = VY" # 6a. HI = "Mr Little's age" (= wife + 6) # 11a. TU = "Age of Mrs. Little" # 7d. KQU = "Seven times Mr Little's age" # 4d. DI = "Age at which Mr Little intends to retire" "TU + 6 = HI" "HI * 7 = KQU" "HI < DI" # 7a. KL = "Number of ewes per ram." # 9a. NPQR = "Acreage of rough grassland. 1.5 acres per ewe" # 12a. VW = "Number of ewes in flock (in scores) # 3d. CHLR = "Number of ewes on the farm" # 10d. PT = "Number of rams on the farm" # 8d. MSWZ = "Total number of sheep" "VW * 20 = CHLR" "2 * NPQR == 3 * CHLR" "CHLR + PT = MSWZ" "PT * KL = CHLR" # 13a. XYZ = "Cube of the number of collies on the farm" "is_cube(XYZ)" # 1d. AFJN = "Area of farmyard in square yards" # 2d. BG = "Length of farmyard in yards" "AFJN % BG = 0" # across / down --template="(AB CDE FG HI KL NPQR TU VW XYZ) (AFJN BG CHLR DI KQU MSWZ PT VY)" --solution=""

**Solution:** The completed grid looks like this:

From which we get the following information

Number of redpoll cows = 13

Number of shorthorn cows = 36

(Total number of cows = 49)

(Total number of horns = 72)Number of ewes = 1540 (= 77× 20)

Number of rams = 35

(Ewes/ram = 44)

(Total number of sheep = 1575)

Area of rough grassland = 2310Mr. Little’s age = 59

Retirement age = 69

(Mrs. Little’s age = 53)Area of farmyard = 1482

Length of farmyard = 39

(Width of farmyard = 38)Number of collies = 5

And from the text we can deduce the following further information:

(George’s age = 26)

(Mary’s age = 13)(Number of cats = 4)

LikeLike

]]>[m for m in range(1, 50) if m * (m + 1)> 198][0]

can be achieved more efficiently (probably) by:

next(filter(lambda m: m * (m + 1)> 198, range(1, 50)))

LikeLike

]]>Here’s an exhaustive version, based on the observation that if *Y = tri(y)* then *R ≥ y + 1*, otherwise it will not be possible to complete an additional row of the Y triangle using R blocks.

It still runs in 46ms.

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

from enigma import tri, irange, inf, first, subsets, is_triangular, printf # generate triangular numbers from tri(a) to tri(b) tris = lambda a=1, b=inf: (tri(i) for i in irange(a, b)) # collect triangular numbers less than 100 ts = first(tris(), (lambda x: x < 100)) # choose (R, B) pairs that sum to another triangular number for (R, B) in subsets(ts, size=2): if not is_triangular(R + B): continue # consider values for Y for Y in tris(is_triangular(B) + 1, R - 1): if is_triangular(R + Y) and is_triangular(B + Y): printf("R={R} B={B} Y={Y}")

LikeLike

]]>One (ugly) statement, no imports.

The “is_square” method “n**.5 % 1 == 0” will fail for large numbers of n (like 152415789666209426002111556165263283035677490).

print([(r * (r + 1) // 2, b * (b + 1) // 2, y * (y + 1) // 2) # Triangular root <m> of n = m*(m+1)/2 is (sqrt(8n+1) - 1) / 2 # combinations of red and blue triangular roots and number of blocks < 100 for (r, b) in [(c1, c2) for c1 in range(1, [m for m in range(1, 50) if m * (m + 1)> 198][0] - 1) for c2 in range(c1 + 1, [m for m in range(1, 50) if m * (m + 1)> 198][0]) # they could use all these blocks to make another triangle if (4 * (c1 * (c1 + 1) + c2 * (c2 + 1)) + 1)**.5 % 1 == 0] for y in range(b + 1, 99) # red and yellow triangle, with no blocks ever left over # blue and yellow triangle, with no blocks ever left over if (4 * (r * (r + 1) + y * (y + 1)) + 1)**.5 % 1 == 0 and (4 * (b * (b + 1) + y * (y + 1)) + 1)**.5 % 1 == 0])

LikeLike

]]>Similar.

from enigma import SubstitutedExpression, is_square # the alphametic puzzle p = SubstitutedExpression( [ # If n is the mth triangular number, then n = m*(m+1)/2 # and m = (sqrt(8n+1) - 1) / 2 # number of reds = RE * (RE + 1)/2 # number of blues = BL * (BL + 1)/2 # number of yellows = YW * (YW + 1)/2 "RE > 0", "BL > 0", # My blue one's taller "BL > RE", # fewer than 100 each "BL * (BL + 1) // 2 < 100", # they could use all these blocks to make another triangle "is_square(4 * (BL * (BL + 1) + RE * (RE + 1)) + 1)", # Dad could buy some yellow blocks to build a triangle bigger # than either of ours was, or a red and yellow triangle, # or a yellow and blue triangle, with no blocks ever left over "YW > BL", "is_square(4 * (YW * (YW + 1) + RE * (RE + 1)) + 1)", "is_square(4 * (YW * (YW + 1) + BL * (BL + 1)) + 1)", ], answer="RE * (RE + 1) // 2, BL * (BL + 1) // 2, YW * (YW + 1) // 2", distinct="", reorder=0, d2i={}, verbose=0) for (_, ans) in p.solve(): print(f"{ans}")

LikeLike

]]>@Jim, I can imagine a similar puzzle in 3D. Didn’t we have such a puzzle before?

LikeLike

]]>from enigma import SubstitutedExpression # the alphametic puzzle p = SubstitutedExpression( [ # number of reds = RE * (RE + 1)/2 # number of blues = BL * (BL + 1)/2 # number of yellows = YW * (YW + 1)/2 "RE > 0", "BL > 0", # My blue one's taller "BL > RE", # fewer than 100 each "BL * (BL + 1) // 2 < 100", # they could use all these blocks to make another triangle "RE * (RE + 1) // 2 + BL * (BL + 1) // 2 == JK * (JK + 1) // 2", # Dad could buy some yellow blocks to build a triangle bigger # than either of ours was, or a red and yellow triangle, # or a yellow and blue triangle, with no blocks ever left over "YW > BL", "YW * (YW + 1) // 2 + RE * (RE + 1) // 2 == CD * (CD + 1) // 2", "YW * (YW + 1) // 2 + BL * (BL + 1) // 2 == FG * (FG + 1) // 2", ], answer="RE * (RE + 1) // 2, BL * (BL + 1) // 2, YW * (YW + 1) // 2", distinct="", reorder=0, d2i={}, verbose=0) for (_, ans) in p.solve(): print(f"{ans}")

LikeLike

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

from enigma import tri, irange, inf, first, subsets, is_triangular, printf # generate triangular numbers from tri(a) to tri(b) tris = lambda a=1, b=inf: (tri(i) for i in irange(a, b)) def solve(): # collect triangular numbers less than 100 ts = first(tris(), (lambda x: x < 100)) # choose (R, B) pairs that sum to another triangular number RBs = list(s for s in subsets(ts, size=2) if is_triangular(sum(s))) # consider values for Y for Y in tris(is_triangular(min(B for (R, B) in RBs)) + 1): # look for R, B pairs that work with Y for (R, B) in RBs: if B < Y and is_triangular(R + Y) and is_triangular(B + Y): yield (R, B, Y) # look for the first solution for (R, B, Y) in solve(): printf("R={R} B={B} Y={Y}") break

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

It makes me wish Python allowed something like a [[ `while ...`

]] clause in list comprehensions, so they could be terminated early:

ts = (t for t in tris() while t < 100)

This exact syntax was actually proposed [ PEP 3142 ], but not implemented.

I’ve folded the functionality into the [[ `first()`

]] function in the **enigma.py** library, so it can take a function and collection of items will stop when the function returns a false value for an item.

LikeLike

]]>Python 3.9 introduced multiple arguments version of math.gcd.

Without imports.

def prime_factors(n): i = 2 factors = [] while i * i <= n: if n % i: i = 3 if i == 2 else i + 2 else: n //= i factors.append(i) if n > 1: factors.append(n) return factors def is_a_power(n): pf = prime_factors(n) if len(pf) < 2: return False # occurences of digits oc = {pf.count(x) for x in set(pf)} if mult_gcd(list(oc)) == 1: return False return True # calculate greatest common divisor of multiple numbers def mult_gcd(li): if len(li) == 1: return li[0] for i in range(len(li) - 1): a = li[i] b = li[i+1] while b: (a, b) = (b, a % b) if a == 1: return a li[i+1] = a return a # record totals and pairs of numbers ss = [(A + B, A, B) for A in range(2, 8) for B in range(A, 9) if B % A != 0] # for each total that is not an exact power, add in B to the total while len(ss) > 1: ss = list((t + B, A, B) for (t, A, B) in ss if not is_a_power(t)) # output candidate solutions for (t, A, B) in ss: print(f"A={A} B={B}")

LikeLike

]]>Here is a more efficient program that reuses the code I wrote for **Enigma 1777** to generate powers in numerical order.

Then for each power we remove pairs of numbers that can be used to make that power, until there is only a single pair remaining.

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

from enigma import Primes, irange, subsets, div, printf # generate powers in numerical order (starting from 2 ** 2) def powers(): base = { 2: 2 } power = { 2: 4 } maxp = 2 primes = Primes(32, expandable=1).generate(maxp + 1) while True: # find the next power n = min(power.values()) yield n # what powers are involved ps = list(p for (p, v) in power.items() if v == n) # increase the powers for p in ps: base[p] += 1 power[p] = pow(base[p], p) # do we need to add in a new power? if maxp in ps: maxp = next(primes) base[maxp] = 2 power[maxp] = pow(2, maxp) # possible pairs of numbers ss = list((A, B) for (A, B) in subsets(irange(2, 8), size=2) if B % A != 0) # consider increasing powers for n in powers(): # remove any pairs that can make n ss = list((A, B) for (A, B) in ss if div(n - A, B) is None) if len(ss) < 2: printf("solution: {ss}") break

LikeLike

]]>It runs in 49ms.

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

from enigma import subsets, irange, prime_factor, mgcd, unpack, printf # check a number is _not_ an exact power check = lambda n, gcd=unpack(mgcd): gcd(e for (p, e) in prime_factor(n)) == 1 # record totals and pairs of numbers ss = list((A + B, A, B) for (A, B) in subsets(irange(2, 8), size=2) if B % A != 0) # for each total that is not an exact power, add in B to the total while len(ss) > 1: ss = list((t + B, A, B) for (t, A, B) in ss if check(t)) # output candidate solutions for (t, A, B) in ss: printf("A={A} B={B}")

**Solution:** The two numbers are 6 and 8.

To show that this is indeed a solution we need to show that *(6 + 8k)* can never be a non trivial exact power.

To find a counter example, we are looking for for some positive integers *k, a, n*, where *n ≥ 2*, such that:

6 + 8k = a^n

2(3 + 4k) = a^n

Clearly the LHS is a multiple of 2. So the RHS must also be a multiple of 2, so let: *a = 2b*.

2(3 + 4k) = (2b)^n

2(3 + 4k) = (2^n)(b^n)

3 + 4k = 2^(n – 1)(b^n)

Clearly the RHS is divisible by 2, but the LHS is not divisible by 2.

So *(6 + 8k)* can never be an exact power of an integer.

Most of the pairs drop out fairly quickly, but (3, 7) hangs in until we get to 2187 = 3 + 312×7 = 3^7.

Leaving (6, 8) as the only remaining candidate.

LikeLike

]]>LikeLike

]]>`SubstitutedExpression`

]] solver from the The following run file executes in

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

#!/usr/bin/env python -m enigma -r SubstitutedExpression # all numbers are even --invalid="1|3|5|7|9,LES" "ALL * THE = SAINTS" --answer="SAINT"

**Solution:** SAINT = 69357.

LikeLike

]]>@Jim, Very clever. I also was thinking to start with the missing digits but would probably not have come up with this solution.

nconcat(xs) modulo 4 must be 3 but this will not help an already very fast program.

LikeLike

]]>The original code took 240 ms to print the answer, and 667 msec to complete the search on my I7 laptop.

LikeLike

]]>@GeoffR,

You don’t have to the count for each variable A-R, only for 1,3,5,7,9.

However, the program doesn’t seem to run faster.

% Each odd digit appears three or four times in the grid array[1..5] of int: nbrs = [1,3,5,7,9]; constraint forall (i in 1..5) (count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], nbrs[i], 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], nbrs[i], 4));

LikeLike

]]>Five other scales can be found by transposing the scale up or down by appropriate numbers of semitones, always with 1 in first position. Each can be reversed to give the other six.

I think the solution corresponds to C E F# G# A A#.

Some of those sharps may be better designated as flats of the note above

(though in an equitempered scale it makes no difference).

Five semitones apart is the interval known as a fourth; that leaves seven semitones, known as a fifth, to the octave note. Those are generally considered the most harmonious intervals, so a person with normal hearing would want as many as possible in the scale. I bet S’s so-called music sounds even worse than the stuff they play in the supermarket so that we don’t linger over our shopping a moment longer than necessary. But then I’ve always said that the only good composer is a dead composer — preferably one who’s been dead for a couple of centuries.

LikeLike

]]>Hovever, it gets the same answer as Jim’s first solution and runs in about the same time as that solution – about 0.7 sec.

% A Solution in MiniZinc include "globals.mzn"; % four-by-four grid % A B C D % E F G H % J K L M % N P Q R set of int: odds = {1,3,5,7,9}; 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; var 1..9: K; var 1..9: L; var 1..9: M; var 1..9: N; var 1..9: P; var 1..9: Q; var 1..9: R; % Row 1 digits constraint A in odds /\ B in odds /\ C in odds /\ D in odds; constraint all_different ([A,B,C,D]); % Row 2 digits constraint E in odds /\ F in odds /\ G in odds /\ H in odds; constraint all_different ([E,F,G,H]); % Row 3 digits constraint J in odds /\ K in odds /\ L in odds /\ M in odds; constraint all_different ([J,K,L,M]); % Row 4 digits constraint N in odds /\ P in odds /\ Q in odds /\ R in odds; constraint all_different ([N,P,Q,R]); % Each odd digit appears three or four times in the grid % Row 1 digit counts constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], A, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], A, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], B, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], B, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], C, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], C, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], D, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], D, 4) ; % Row 2 digit counts constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], E, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], E, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], F, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], F, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], G, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], G, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], H, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], H, 4) ; % Row 3 digit counts constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], J, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], J, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], K, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], K, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], L, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], L, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], M, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], M, 4) ; % Row 4 digit counts constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], N, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], N, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], P, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], P, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], Q, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], Q, 4) ; constraint count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], R, 3) \/ count_eq ( [A,B,C,D,E,F,G,H,J,K,L,M, N,P,Q,R], R, 4) ; % Down column digits are all different constraint all_different([A,E,J,N]); constraint all_different([B,F,K,P]); constraint all_different([C,G,L,Q]); constraint all_different([D,H,M,R]); % Across numbers in grid var 1000..9999:ABCD = 1000*A + 100*B + 10*C + D; var 1000..9999:EFGH = 1000*E + 100*F + 10*G + H; var 1000..9999:JKLM = 1000*J + 100*K + 10*L + M; var 1000..9999:NPQR = 1000*N + 100*P + 10*Q + R; % Down numbers in grid var 1000..9999:AEJN = 1000*A + 100*E + 10*J + N; var 1000..9999:BFKP = 1000*B + 100*F + 10*K + P; var 1000..9999:CGLQ = 1000*C + 100*G + 10*L + Q; var 1000..9999:DHMR = 1000*D + 100*H + 10*M + R; % Each average (across and down) is a whole number(see description) % Average of across numbers var 1000..9999: Av_Across; Av_Across = (ABCD + EFGH + JKLM + NPQR) div 4; % Across average digits (A1, A2, A3, A4) var 1..9:A1; var 1..9:A2; var 1..9:A3; var 1..9:A4; constraint A1 == Av_Across div 1000 /\ A1 mod 2 == 1; constraint A2 = Av_Across div 100 mod 10 /\ A2 mod 2 == 1; constraint A3 = Av_Across div 10 mod 10 /\ A3 mod 2 == 1; constraint A4 = Av_Across mod 10 /\ A4 mod 2 == 1; % Average of down numbers var 1000..9999: Av_Down; Av_Down = (AEJN + BFKP + CGLQ + DHMR) div 4; % Down average digits (D1, D2, D3, D4) var 1..9:D1; var 1..9:D2; var 1..9:D3; var 1..9:D4; constraint D1 == Av_Down div 1000 /\ D1 mod 2 == 1; constraint D2 = Av_Down div 100 mod 10 /\ D2 mod 2 == 1; constraint D3 = Av_Down div 10 mod 10 /\ D3 mod 2 == 1; constraint D4 = Av_Down mod 10 /\ D4 mod 2 == 1; % Larger average is down average constraint Av_Down > Av_Across; % Each average used an odd number of digits constraint card({A1,A2,A3,A4}) mod 2 == 1 /\ card({D1,D2,D3,D4}) mod 2 == 1; solve satisfy; output ["Average across = " ++ show(Av_Across) ++ "\nAverage down = " ++ show(Av_Down) ++ "\nTypical grid:" ++ "\n" ++ show(ABCD) ++ "\n" ++ show(EFGH) ++ "\n" ++ show(JKLM) ++ "\n" ++ show(NPQR)];

LikeLike

]]>If we start with the four 4-digit numbers that form the rows, we can construct a fifth 4-digit number using the missing digit from each column.

When these five numbers are added together, each possible digit now appears in each position, so we get:

T = 1111 × (1 + 3 + 5 + 7 + 9) = 27775

We can then make possible actual totals by subtracting a 4-digit number composed of different odd digits from **T**.

(Each possible digit appears 4 times in our collection of five 4-digit numbers, and in the actual grid we want each digit to appear at least 3 times, so we can only remove at most 1 copy of each digit).

A viable total must be divisible by 4 to give the average, which itself must consist of an odd number of different odd digits.

And for any pair of averages the row average must be less than the column average.

The following program uses this technique to find possible viable pairs of averages. It turns out there is only one viable pair, so this gives us our solution.

It runs in 44ms.

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

from enigma import subsets, nconcat, div, nsplit, printf # allowable digits digits = {1, 3, 5, 7, 9} # generate possible averages for 4x 4-digit numbers # with no digit repeated in the same position def avgs(): # if all digits appeared in each position then 4 copies # of each digit would be used, and the sum would be ... T = 1111 * sum(digits) # now delete a digit from each position # at least 3 copies of each digit must remain, so we can only # delete at most one instance of any digit for xs in subsets(digits, size=4, select="P"): # calculate the average avg = div(T - nconcat(xs), 4) if avg is None: continue # check it uses an odd number of different odd digits s = nsplit(avg, fn=set) if not(len(s) in digits and digits.issuperset(s)): continue # return the average yield avg # choose possible row and column averages for (ravg, cavg) in subsets(sorted(avgs()), size=2): printf("row avg = {ravg}, col avg = {cavg}")

All that remains is to show that a grid can be constructed using the required averages. Fortunately my first program (above) finds many possible ways to do this.

LikeLike

]]>Thanks, I copied it from the run version (something which I normally don’t do).

LikeLike

]]>@Frits

The `digits`

parameter should be a collection of permissible digits (not a string), e.g.:

digits=(1,3,5,7,9)

LikeLike

]]>@Jim, I don’t know if this is caused by your latest update.

from enigma import SubstitutedExpression # the alphametic puzzle p = SubstitutedExpression( [ "WXYZ = 3713", ], distinct="", digits="1,3,5,7,9", answer="W, XYZ", d2i={}, verbose=256 ) for (_, ans) in p.solve(): print(f"{ans}")

No solution is given, see generated code:

[SubstitutedExpression: replacing ({WXYZ} = 3713) -> (3713 = {WXYZ})] -- [code language="python"] -- def _substituted_expression_solver1(): try: _x_ = int(3713) except NameError: raise except: raise if _x_ >= 0: _Z = _x_ % 10 if _Z != 0 and _Z != 1 and _Z != 2 and _Z != 3 and _Z != 4 and _Z != 5 and _Z != 6 and _Z != 7 and _Z != 8 and _Z != 9: _x_ //= 10 _Y = _x_ % 10 if _Y != 0 and _Y != 1 and _Y != 2 and _Y != 3 and _Y != 4 and _Y != 5 and _Y != 6 and _Y != 7 and _Y != 8 and _Y != 9: _x_ //= 10 _X = _x_ % 10 if _X != 0 and _X != 1 and _X != 2 and _X != 3 and _X != 4 and _X != 5 and _X != 6 and _X != 7 and _X != 8 and _X != 9: _x_ //= 10 _XYZ = _Z + _Y*10 + _X*100 _W = _x_ % 10 if _W != 0 and _W != 1 and _W != 2 and _W != 3 and _W != 4 and _W != 5 and _W != 6 and _W != 7 and _W != 8 and _W != 9: _x_ //= 10 if _x_ == 0: _WXYZ = _Z + _Y*10 + _X*100 + _W*1000 _r_ = (_W), (_XYZ) yield ({ 'W': _W, 'X': _X, 'Y': _Y, 'Z': _Z }, _r_)

LikeLike

]]>@Frits: Yes, that would probably be clearer. I’ll change it.

I wrote that bit of code before I was using **STUV** and **WXYZ** for the averages, so I didn’t already have them broken out into separate digits.

You can use a `lambda`

expression to implement a sort of `where`

clause in Python:

<expr1> where (x=<expr2>, y=<expr3>)

can be implemented as:

(lambda x, y: <expr1>)(<expr2>, <expr3>)

or:

(lambda x=<expr2>, y=<expr3>: <expr1>)()

LikeLike

]]>First time I see a nested lambda.

I would have used

“check({W, X, Y, Z})” etc

and

–code=”check = lambda s: len(s) in odds and odds.issuperset(s)”

LikeLike

]]>`SubstitutedExpression`

]] solver from the It runs in 687ms.

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

# suppose the layout is: # # A B C D # E F G H # J K L M # N P Q R SubstitutedExpression --digits="1,3,5,7,9" # no repeated digit in any row or column --distinct="ABCD,EFGH,JKLM,NPQR,AEJN,BFKP,CGLQ,DHMR" # don't reorder the expressions (they all use all 16 symbols) --reorder=0 # each average uses an odd number of different odd digits --code="avg = lambda *ns: ediv(sum(ns), len(ns))" --code="odds = {1, 3, 5, 7, 9}" --code="check = lambda s: len(s) in odds and odds.issuperset(s)" # row average = WXYZ "avg(ABCD, EFGH, JKLM, NPQR) = WXYZ" "check({W, X, Y, Z})" # col average = STUV, is greater than row average "avg(AEJN, BFKP, CGLQ, DHMR) = STUV" "STUV > WXYZ" "check({S, T, U, V})" # each digit appears at least 3 times --code="count = lambda s: all(s.count(d) > 2 for d in odds)" "count([A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R])" # required answer (row avg, col avg) --answer="(WXYZ, STUV)" # [optional] less verbose output --template="({ABCD}, {EFGH}, {JKLM}, {NPQR}) -> {WXYZ}, {STUV}" --solution=""

**Solution:** The row average is 5159. The column average is 5951.

There are 56 possible arrangements of digits in the grid. Here is one:

1 9 7 5

3 5 1 7

5 3 9 1

9 7 5 3

Row average = (1975 + 3517 + 5391 + 9753) / 4 = 5159.

Column average = (1359 + 9537 + 7195 + 5713) / 4 = 5951.

LikeLike

]]>LikeLike

]]>Jim and Frits, thank you. originally I used CAMPARI. In my incorrect CIDER (3) deduction I thought that E and R were in row 1. 🙂

LikeLike

]]>True, the problem lies with the CIDER (3) D …. deduction.

LikeLike

]]>If I remove the WHISKY, VODKA, CAMPARI, FLAGON conditions in my program it finds 4 possible solutions. So they can’t all be redundant.

But adding just CAMPARI back in gets us back to a unique solution.

LikeLike

]]>A, S, D, F, G, H, J, K, L (9)

Z, X, C, V, B, N, M (7)

BEER (1) B, E, R -> row 2

STOUT (1) S, T, O, U -> row 3

only 3 more letters can go to row 3, and from CIDER, at least one must be D or I

LAGER (2) A, G, L -> row 1

SQUASH (2*) Q, H -> row 3 (full except for D or I)

first row P, W, Y – > row 2

second row F, J, K -> row 1

HOCK (2) C -> row 1

CIDER (3) D -> row 3 (full), I -> row 2

MUZZY (2) M, Z -> row 2 (full)

third row N, X, V -> row 1

CHAMBERTIN comes from rows 1, 3, 1, 2, 2, 2, 2, 3, 2, 1

In this method WHISKY (3), VODKA (2), CAMPARI (2) and FLAGON (2) are redundant

LikeLike

]]>from itertools import combinations as comb letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" orig = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"] sol = ["ACFGJKLNVX", "BEIMPRWYZ", "DHOQSTU"] # only used for printing common1 = [set("BEER"), set("STOUT")] common2 = [set("LAGER"), set("CAMPARI"), set("SHERRY"), set("HOCK"), set("VODKA"), set("FLAGON"), set("SQUASH"), set("MUZZY")] common3 = [set("WHISKY"), set("CIDER")] r = {4 : "1", 2 : "2", 1 : "3"} # row number # return 0 if all letters have been uniquely placed in a row def unsolved(): c = 0 for let in letters: c += d[let] if d[let] not in {1, 2, 4}: return c return 0 # remove <val> from letter <let> def remove(let, val, text = ""): if d[let] & val == 0: return # already removed d[let] = d[let] & (7 - val) if text != "": print(f"remove letter {let} from row {r[val]} {text}") # one row in common def cond1(s): # skip if all letters in <s> are known if all(d[x] in {1, 2, 4} for x in s): return # check if letters in <s> have only one common bit common = 7 for x in s: common &= d[x] if common in {1, 2, 4}: print(f"place letters {','.join(s)} in row {r[common]} ", end = "") print("(as they have to share 1 row)") for x in s: d[x] = common # set all letters to the same value # two rows in common def cond2(s): known = [x for x in s if d[x] in {1, 2, 4}] rows_used = set(d[x] for x in known) if len(rows_used) == 2: # we have solved letters in 2 rows # so all letters in <s> have to be in these 2 rows twoRows = sum(rows_used) missing_row = 7 - twoRows for x in s: if x in known: continue # letter can be in either one of the 2 rows? if d[x] == twoRows: continue rows_used = sorted(list(rows_used), reverse=1) text = "(letters "+ ",".join(s) +" must occur in rows " text += ", ".join(r[x] for x in rows_used) + ")" remove(x, missing_row, text) # three rows in common def cond3(s): # for each row check candidates for i in [1, 2, 4]: li = [x for x in s if d[x] & i] if len(li) == 1 and d[li[0]] != i: # one candidate for row i print(f"place letter {li[0]} in row {r[i]} (other ", end = "") print(f"letters in {','.join(s)} are not possible in row {r[i]})") d[li[0]] = i return # check if all letters in a row are known def row_full(): for i, n in enumerate([4, 2, 1]): c = 0 for let in letters: if d[let] == n: c += 1 if c == len(orig[i]): # row <i> is full so remove <i> from other candidates for let in letters: if d[let] == n: continue remove(let, n, "(already " + str(len(orig[i])) + " letters have to be in row " + r[n] +")") # check whether the number of letters in bottom 2 rows exceeds 16 def bottom2(c1 ,c2): common_letters = c1 & c2 if len(common_letters) < 2: return False known = [x for x in common_letters if d[x] in {1, 2, 4}] rows_used = list(set(d[x] for x in known)) if len(rows_used) != 1: return False bot2 = [x for x in common_letters if x != known[0] and x not in orig[0] and d[x] & d[known[0]] == 0] # different row from known # known[0] in one bottom row and <bot2> in the other bottom row? if len(bot2) > 0: # suppose <bot2> is not in top row, so <bot2> and <known> encompass # 2 rows, this means that all letters in c1 and c2 are in bottom 2 rows # check if letters in bottom 2 rows exceeds 16 (9 + 7) # count entries in 2 bottom rows if <bot2> is not in top row li = [x for x in letters if x in orig[0] or # the 10 QWERTYUIOP entries ((d[x] & 4) == 0 or # not in top row x in (c1 | c2) )] # in c1 or in c2 max = len(orig[1]) + len(orig[2]) if len(li) > max: # put QWERTY... up front notTop = {x for x in orig[0]} | {x for x in letters if d[x] & 4 == 0} # letter <bot2[0]> can not be in bottom 2 rows text = "(assuming letter " + bot2[0] + " in bottom 2 rows leads to\n" text += "letters " + ",".join(notTop | c1 | c2) text += " in bottom 2 rows (exceeding total of " + str(max) + "))" remove(bot2[0], 1, text) remove(bot2[0], 2, text) return True return False # print rows def report(li): print() for i, x in enumerate(li): for y in x: if d[y] == (1 << (2 - i)): # letter is known print(f"{y} = {d[y]:<5}", end=" ") else: print(f"{y} = {d[y]:#05b}", end=" ") print() print() d = dict() # initialize letters for x in letters: d[x] = 7 # remove original row number for each letter for i, y in enumerate(orig): for x in y: remove(x, (1 << (2-i))) prev = 0 for loop in range(99): for x in common1: cond1(x) for x in common2: cond2(x) for x in common3: cond3(x) row_full() # check if all letters in a row are known nr_unsolved = unsolved() if nr_unsolved == 0: # are we done? report(sol) squash_rows = sorted(d[x] for x in "SQUASH") if squash_rows[0] != squash_rows[1] or \ squash_rows[-1] != squash_rows[-2]: print("letters in SQUASH are all in one row but for a single letter") break report(sol) if nr_unsolved == prev: # nothing has been solved in this loop # check all combinations of words which reside on 2 rows for c1, c2 in comb(common2, 2): # check whether the number of letters in bottom 2 rows exceeds 16 if bottom2(c1, c2): break prev = nr_unsolved

LikeLike

]]>`grouping`

]] functionality was added to the This Python program runs in 45ms.

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

from enigma import grouping clubs = ('Barnet', 'Exeter', 'Gillingham', 'Plymouth', 'Southend', 'Walsall') players = ('Aguero', 'Ibrahimovic', 'Lampard', 'Neymar', 'Schweinsteiger', 'Suarez') managers = ('Conte', 'Mourinho', 'Pellegrini', 'Terim', 'Van Gaal', 'Wenger') grouping.solve([clubs, players, managers], grouping.share_letters(2))

**Solution:** The new players are Lampard (Barnet), Suarez (Exeter), Aguero (Gillingham), Neymar (Plymouth), Ibrahimovic (Southend) and Schweinsteiger (Walsall).

The groups are:

Barnet, Lampard, Mourinho

Exeter, Suarez, Wenger

Gillingham, Aguero, Terim

Plymouth, Neymar, Conte

Southend, Ibrahimovic, Pellegrini

Walsall, Schweinsteiger, Van Gaal

LikeLike

]]>@Jim Thanks, it works.

However, –verbose=256 doesn’t seem to work properly anymore.

LikeLike

]]>`SubstitutedExpression`

]] solver from the It adds the `denest`

parameter to [[ `SubstitutedExpression`

]] (which available as `--denest`

or `-X`

from the command line or in run files), that changes the way the code is generated to reduce the amount of nesting in the generated code.

The upshot of this is, if you are using [[ `SubstitutedExpression`

]] and you run foul of the `"SyntaxError: too many statically nested blocks"`

issue, you can just use [[ `SubstitutedExpression(..., denest=1)`

]] (or add the `--denest`

or `-X`

parameter to a run file or on the command line).

The run file given above, with the additional `--denest`

parameter runs in 93ms using CPython 2.7.

Assuming no problems show up in further testing, this will be rolled out in the next **enigma.py** update.

**Update:** I have rolled this out in the `2021-01-14`

version of **enigma.py**.

LikeLike

]]>I am busy with a program to solve the puzzle without using brute force but am kind of stuck….

LikeLike

]]>Thanks, it works but it takes approx 30 seconds.

The program itself takes 39ms.

LikeLike

]]>@Frits: It should be working now.

LikeLike

]]>@Jim, thanks.

It doesn’t work if the filetype is py but that’s OK.

LikeLike

]]>I have now installed the latest nightly build of PyPy ([PyPy 7.3.4-alpha0 with MSC v.1927 64 bit (AMD64)] on win32).

It seems to be a recent error in PyPy.

https://foss.heptapod.net/pypy/pypy/-/issues/3342

“pypy3 -m ensurepip” doesn’t give errors anymore but repl.it still fails.

LikeLike

]]>@Frits: I don’t think your local installation of PyPy will affect repl.it. I think the problem is running PyPy under repl.it (which is not directly supported). Maybe the template I used to allow you to do this only works for the owner of the repl. (You could try forking it and see if that works for you).

LikeLike

]]>@Frits: I would have thought if the **S** was on a different line then typing **SQUASH** would involve typing 2 letters that were on a different row. But the puzzle text is open to interpretation there. You can just remove one of the **S**‘s from the list on line 32 to try the other interpretation. Fortunately it doesn’t change the answer.

On Unix like operating systems the `#!`

line tells the OS how to execute a file, so the file just needs to be executable. (And a while ago I made a command called **run**, to make it even easier to run such files).

Otherwise you can just run the command manually. So for this file:

% pypy -m enigma -r teaser660.run

Or if you haven’t put **enigma.py** in your `$PYTHONPATH`

, just put them in the same directory (or specify full paths):

% pypy enigma.py -r teaser660.run

Alternatively you can run it from the Python shell, using the [[ `enigma.run()`

]] function:

% pypy >>>> from enigma import run >>>> run("teaser660.run") A=1 B=2 C=1 D=3 E=2 F=1 G=1 H=3 I=2 J=1 K=1 L=1 M=2 N=1 O=3 P=2 Q=3 R=2 S=3 T=3 U=3 V=1 W=2 X=1 Y=2 Z=2 (C, H, A, M, B, E, R, T, I, N) = (1, 3, 1, 2, 2, 2, 2, 3, 2, 1) [1 solution]

(I have [[ `from enigma import *`

]] in my `.pythonrc`

file, so code from **enigma.py** is always available in interactive Python shells, and I don’t need to import them separately).

LikeLike

]]>So if we double each term we get numbers all on the same side of the street.

But perhaps the puzzle would then be too easy!

LikeLike

]]>Yesterday I updated PyPy. I didn’t realize repl.it depended on it.

(error: Makefile:52: recipe for target ‘exec_pip’ failed). I’ll try to reinstall Pip under PyPy.

Have you already used the 64 bit PyPy nightly build?

]]>@Jim,

If the S of SQUASH was the single letter in a different row the multiset line would not recognize it (it depends how you interpret “a single letter”).

Do you know an easy way how I can execute the run file like a normal py file in a Command Prompt (without creating a main each and every time)?

LikeLike

]]>@Frits: Odd. It works for me if I’m logged in, but not if I try to run the repl anonymously. You have to jump through some hoops to get PyPy running in repl.it, so it is not surprising the end result is a bit fragile.

LikeLike

]]>@Jim, repl.it isn’t working for this puzzle

LikeLike

]]>`SubstitutedExpression`

]] solver from the `--denest`

]] option, which generates code that is not as deeply nested, and allows the program to run under the standard Python interpreter.
The following run file executes in 93ms.

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

#!/usr/bin/env python -m enigma -r SubstitutedExpression --distinct="" --digits="1,2,3" # no letter is in its original row --invalid="1,QWERTYUIOP" --invalid="2,ASDFGHJKL" --invalid="3,ZXCVBNM" # number of letters per row remained unchanged --code="count = lambda s: tuple(s.count(x) for x in (1, 2, 3))" "count([A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]) == (10, 9, 7)" # rows used for each word "len({B, E, E, R}) = 1" "len({S, T, O, U, T}) = 1" "len({S, H, E, R, R, Y}) = 2" "len({W, H, I, S, K, Y}) = 3" "len({H, O, C, K}) = 2" "len({L, A, G, E, R}) = 2" "len({V, O, D, K, A}) = 2" "len({C, A, M, P, A, R, I}) = 2" "len({C, I, D, E, R}) = 3" "len({F, L, A, G, O, N}) = 2" "len({S, Q, U, A, S, H}) = 2" "len({M, U, Z, Z, Y}) = 2" # SQUASH is all in one row, except for a single letter "1 in multiset([S, Q, U, A, S, H]).values()" # answer is the rows involved in typing CHAMBERTIN --answer="(C, H, A, M, B, E, R, T, I, N)" # [optional] less verbose output --template="" # [experimental] work around statically nested block limit --denest

**Solution:** The rows involved in typing **CHAMBERTIN** are: 1, 3, 1, 2, 2, 2, 2, 3, 2, 1.

The new assignment of rows to keys is:

Row 1:A C F G J K L N V X

Row 2:B E I M P R W Y Z

Row 3:D H O Q S T U

Although we don’t know what order the keys are in in a row.

]]>1453, 897, 556, 341, 215, 126, 89, 37, 52

1453, 899, 554, 345, 209, 136, 73, 63, 10

The ratio of successive terms oscillates about phi, and converges quite slowly.

LikeLike

]]>That’s not the way our posties (male or female) work.

I think a better puzzle could have put Simpson in an even-numbered house,

all houses receiving post that day being on the one side of the street.

That was a good idea to divide by phi. Repeated division, each time rounding to the nearest integer, would get us most of the way to the start of the sequence, though it would be safer after a while to take S(n) = S(n+2) – S(n+1) .

Did we need to know that it starts with two single-digit terms? 1453/phi has a unique value!

LikeLike

]]>@Geoff: I took it to mean that at each stage the number of blocks knocked off is less than the number of blocks remaining after that shot. (So the number knocked off is less than half the number of blocks available).

So at the beginning there are 200 blocks available, so in the first shot we can knock off between 1 and 99.

If we knock off 3 with the first shot, there are 197 blocks remaining, so the second shot can knock off between 1 and 98.

If we knock off 53 with the second shot, there are 144 blocks remaining, so the third shot can knock off between 1 and 71.

And so on, until all 7 shots are taken.

LikeLike

]]>LikeLike

]]>Evidently, if we write down the numbers of the houses that the postman delivered to, in the order he delivered, we are told that for every house he visited (after the first two houses), the number of that house is the sum of the numbers of the previous two houses delivered to.

So the numbers of the houses visited form a Fibonacci sequence. And we are told the sequence starts with two single digit numbers, and the number 1453 is in the sequence.

This Python program runs in 45ms.

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

from enigma import subsets, irange, fib, printf # collect solutions r = set() # choose a single digit number to start the sequence for (a, b) in subsets(irange(1, 9), size=2, select="P"): ns = list() for n in fib(a, b): if n > 1453: break ns.append(n) if n == 1453: r.add(ns[-2]) printf("[({a}, {b}) -> {ns}]") printf("answer = {r}")

**Solution:** The postman had just come from house number 898.

There are in fact three possible starting pairs of house numbers:

(2, 5) → (7, 12, 19, 31, 50, …)

(3, 2) → (5, 7, 12, 19, 31, 50, …)

(5, 7) → (12, 19, 31, 50, …)

but they all end up settling down to generating the same sequence of higher valued house numbers, which does eventually reach 1453:

…, 5, 7, 12, 19, 31, 50, 81, 131, 212, 343, 555, 898, 1453, …

And the number immediately preceding 1453 is 898.

As the ratio of consecutive terms in a Fibonacci sequence approaches the value of the *Golden Ratio* (φ = (1 + √5) / 2), we can immediately calculate a good guess to the answer:

% python >>> phi = 0.5 + sqrt(5, 4) >>> 1453 / phi 898.0033856535972

LikeLike

]]>check5 can also be written as:

check5 = lambda s: sum(y - x in {5, 7} for x in s for y in s if y > x)

LikeLike

]]>I tried the binary approach first and was annoyed that this took more than twice as long as your first solution Jim. Having swapped other bits of my algorithm for yours I think the biggest time difference comes from this: your first approach generates trials with only the 6 notes in; my binary approach generates trials with all 12 positions included, and then I have to use an if statement or similar to test only the 6.

LikeLike

]]>I tried to use different and probably less efficient code.

check1 = lambda s: sum(y == (x + 1) % 12 \ for (x, y) in list(zip(s, s[1:] + s[:1]))) check5 = lambda s: sum(y - x == 5 or 12 + x - y == 5 for x in s for y in s if y > x) # loop backwards as first found solution will have maximal value # and thus the 2nd 1 bit will be as far to the right as possible for A in range(7, 0, -1): for B in range(8, A, -1): for C in range(9, B, -1): for D in range(10, C, -1): for E in range(11, D, -1): if check1([0, A, B, C, D, E]) != 2: continue if check5([0, A, B, C, D, E]) != 1: continue # print solution for k in range(12): print("1", end="") if k in {0, A, B, C, D, E} \ else print("0", end="") print() exit()

LikeLike

]]>Here is another solution that treats the scales as numbers represented in binary.

We can use the [[ `bit_permutations()`

]] function from the **enigma.py** library (described here [link]) to generate patterns with 6 bits set in lexicographic order.

It runs in 45ms overall, and has an internal runtime of 55µs.

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

from enigma import bit_permutations, dsum2, printf # count notes with and interval of k def interval(n, k): m = n << (12 - k) # notes shifted up an interval of k v = n | (n << 12) # wrapped values return dsum2(v & m) # count the set bits # choose a bit pattern with 6 of the 12 digits set for n in bit_permutations(0b100000011111, 0b111111111111): # count notes with an interval of 1 and 5 if interval(n, 1) == 2 and interval(n, 5) == 1: printf("{n} -> {n:012b}") # we only need the first value break

And here is an “unrolled” version that has an internal runtime of only 24µs [ link ].

LikeLike

]]>@Brian: The lowest number will have the largest leftmost grouping of 0’s. And fortunately combinations are generated in lexicographic order, so that gives us what we want.

LikeLike

]]>LikeLike

]]>This Python program runs in 45ms.

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

from enigma import subsets, irange, join, printf # count notes with an interval of k interval = lambda ps, k: sum((p + k) % 12 in ps for p in ps) # choose six notes to exclude for xs in subsets(irange(1, 11), size=6): ps = tuple(p for p in irange(0, 11) if p not in xs) # count notes with an interval of 1 and 5 if not(interval(ps, 1) == 2 and interval(ps, 5) == 1): continue # the solution is the first value we find printf("{s}", s=join("01"[i in ps] for i in irange(0, 11))) break

**Solution:** Skaredahora’s favourite scale is 100010101110.

We can consider this as a binary representation of the number 2222.

There are 12 possible scales starting with 1 that have exactly 2 notes with another note 1 above, and exactly one having another note 5 above.

There are also 12 possible scales starting with 0.

LikeLike

]]>@Tony,

Interesting idea.

I am not coding in Python for a very long time.

I like to use list comprehensions so I would have used something like this:

nsd1 = [(s2, c3) for s in range(2, 15) for c in range(2, 6) if is_prime(d := (s2 := s*s) - (c3 := c*c*c)) and 2 * d < s2]

As you can calculate d3 from n2 and n3 you don’t need to use a dictionary to store the squares and cubes.

LikeLike

]]>@GeoffR, I am not sure if the limit of 79 for missiles is correct.

The minimum for 6 missiles is 41 (2+3+5+7+11+13) –> (200 – 41) / 2 = 79.5 ???

I don’t immediately see why the first missile can’t be 83.

LikeLike

]]>Coding the seq_all_different and is_prime clauses for each missile separately will bring the run time down from 1 second to 47ms.

LikeLike

]]>Adding this will only print the 2 solutions

var 2..197:B6A; % 7th Missile M7 can be swapped with 6th Missile M6 constraint B6A = blocks - (M1 + M2 + M3 + M4 + M5 + M7) /\ M7 < B6A /\ B6A in none_of_afore;

LikeLike

]]>The programme produces outputs for the number of blocks remaining as 53, 41 and 47.

However, only an output of 47 blocks remaining , after the 7th missile, allows the 6th and 7th missiles (shots) to be interchanged, so this is the answer to the teaser.

% A Solution in MiniZinc include "globals.mzn"; int:blocks = 200; % list of missiles in sequence fired var 2..79:M1; var 2..79:M2; var 2..79:M3; var 2..79:M4; var 2..79:M5; var 2..79:M6; var 2..79:M7; % all missiles are different (and prime numbers) constraint all_different ([M1, M2, M3, M4, M5, M6, M7]); % list of blocks remaining after each missile (shot) var 2..197:B1; var 2..197:B2; var 2..197:B3; var 2..197:B4; var 2..197:B5; var 2..197:B6; var 2..197:B7; % set of primes less than 200 set of int :primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43,47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199}; % sets of squares and cubes less than 200 set of int: cubes = { x * x * x | x in 2..5 }; set of int: squares = { x * x | x in 2..14 }; % set of all integers between 1 and 200 set of int: all_int = {x | x in 1..200}; % set of squares times primes set of int: sq_x_pr = {i * j | i in squares, j in primes where i * j < 200}; % set of cubes times primes set of int: cb_x_pr = {i * j | i in cubes, j in primes where i * j < 200}; % set of integers, none of the aforementioned set of int : none_of_afore = all_int diff primes diff cubes diff squares diff sq_x_pr diff cb_x_pr; % 1st Missile M1 leaves Blocks B1, displacing M1 blocks constraint B1 == blocks - M1 /\ M1 < B1 /\ M1 in primes /\ B1 in primes; % 2nd Missile M2 leaves Blocks B2 constraint B2 = blocks - (M1 + M2) /\ M2 < B2 /\ M2 in primes /\ B2 in squares; % 3rd Missile M3 leaves Blocks B3 constraint B3 = blocks - (M1 + M2 + M3) /\ M3 < B3 /\ M3 in primes /\ B3 in cubes; % 4th Missile M4 leaves Blocks B4 constraint B4 = blocks - (M1 + M2 + M3 + M4) /\ M4 < B4 /\ M4 in primes /\ B4 in sq_x_pr; % 5th Missile M5 leaves Blocks B5 constraint B5 = blocks - (M1 + M2 + M3 + M4 + M5) /\ M5 < B5 /\ M5 in primes /\ B5 in cb_x_pr; % 6th Missile M6 leaves Blocks B6 constraint B6 = blocks - (M1 + M2 + M3 + M4 + M5 + M6) /\ M6 < B6 /\ M6 in primes /\ B6 in none_of_afore; % 7th Missile M7 leaves Blocks B7 constraint B7 = blocks - (M1 + M2 + M3 + M4 + M5 + M6 + M7) /\ M7 < B7 /\ M7 in primes /\ B7 in primes; solve satisfy; output [" Missile sequence: " ++ show(M1),", ", show(M2), ", ", show(M3),", ", show(M4),", ", show(M5),", ", show(M6), ", ", show(M7) ++ "\n" ++ " Blocks left: " ++ show(B1),", ", show(B2), ", ", show(B3),", ", show(B4),", ", show(B5),", ", show(B6), ", ", show(B7) ]; % Missile sequence: 3, 53, 19, 13, 31, 11, 17 % Blocks left: 197, 144, 125, 112, 81, 70, 53 % ---------- % Missile sequence: 3, 53, 19, 13, 31, 11, 23 % Blocks left: 197, 144, 125, 112, 81, 70, 47 <<< ans % ---------- % Missile sequence: 3, 53, 19, 13, 31, 11, 29 % Blocks left: 197, 144, 125, 112, 81, 70, 41 % ---------- % Missile sequence: 3, 53, 19, 13, 31, 23, 5 % Blocks left: 197, 144, 125, 112, 81, 58, 53 % ---------- % Missile sequence: 3, 53, 19, 13, 31, 23, 11 % Blocks left: 197, 144, 125, 112, 81, 58, 47 <<< ans % ---------- % Missile sequence: 3, 53, 19, 13, 31, 23, 17 % Blocks left: 197, 144, 125, 112, 81, 58, 41 % ---------- % ==========

LikeLike

]]>LikeLike

]]>Being a Python novice, I am learning a lot from reviewing solutions on here (Jim’s or others’), so once I had solved the puzzle I came to look at this thread. Then as a test I tried to implement my algorithm using as much of Jim’s code as possible. I have not researched properly the relative time cost of looping through primes and testing other properties, as opposed to engineering the other properties and then testing if prime (which involves at least a look-up which itself must require looping). However, by starting with the square/cube pairs implied by tests 2 and 3, then working back to 200 in one direction and forwards using Jim’s code in the other, I reduced the average run-time on my machine from 0.90ms to 0.38ms.

I won’t post all my code because I am using a few long-hand functions instead of the Enigma library. However, I think the following chunk should make it clear how I adapted Jim’s code to give a list of possible solutions from which to find the pair with interchangeable d5 and d6:

convert "power" test into powers generator generates (a^k) up to and including x, from a minimum root m def powers(x,k,m=2): for a in range(m, int(x**(1/k))+1): yield a**k ... #Control program #Store long-list of primes n0=200 primes = list(Primes(n0)) #Start with all possible pairs of squares and cubes separated by a prime nsd = {} for n3 in powers(n0,3,3):# N3>cube of 2 since we need at least one cube for N5 for n2 in powers(n0,2): d3 = n2 - n3 if d3>n2/2:continue elif is_prime(d3): nsd[(n2,n3)]=d3 #For each of these pairs, run back up to n0 to find possible prime steps ns = [] for n2, n3 in nsd.keys(): for d2 in primes: if d2 > min((n0-n2),n2):break#we don't know n1 yet, so cap d2 as if d1=0 else: n1=n2+d2 d1=n0-n1 d3=nsd[(n2,n3)] if is_prime(n1) and is_prime(d1) and len(set([d1,d2,d3]))==3: #And then run downwards to apply the remaining tests (per JR approach) for solution in solve((n0-sum([d1,d2,d3])), (t4, t5, t6, t1) , [d1,d2,d3]): ns.append(solution)

LikeLike

]]>This Python program runs in 47ms.

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

from itertools import product from enigma import Rational, irange, multiply, printf F = Rational() # probability of choosing n balls with the same quality from t # where q of the t balls have the quality def P(n, t, q): return multiply(F(q - i, t - i) for i in irange(0, n - 1)) # choose the number of non-red and red balls for (n, r) in product(irange(3, 7), irange(3, 15)): # there are r red and n non-red balls t = r + n # probability of 3 being red p1 = P(3, t, r) # probability of 3 being non-red p2 = P(3, t, n) # check the conditions if p1.numerator == 1 and p2.numerator == 1 and p1 < p2: printf("n={n} r={r}, t={t}, p1={p1} p2={p2}")

**Solution:** There were 10 balls in the bag, 4 of them were red.

So, the bag contained 6 red balls, and 4 non-red balls.

When picking three balls, there is a 1/30 chance that they are all red, and a 1/6 chance of them all being non-red.

LikeLike

]]>

1 2 3

1 0 0 (0m)

0 1 0 (1m)

1 1 0 (2m)

0 0 1 (3m)

1 0 1 (4m)

0 1 1 (5m)

1 1 1 (6m)

1 0 0 (7m)

…

If we read the switches backwards we see the system of switches operates as a binary counter starting at 001 (= 1) and counting up to 111 (= 7) after 6 minutes.

After 7 minutes the counter would reach 8 = (000 + overflow), but the switches are arranged in a circle, so the overflow bit feeds into the least significant bit of the counter, so state 8 corresponds to state 1 (= 001) and we are back where we started.

The counter cycles through the non-zero states 1-7 every 7 minutes, so never achieves state 0 and the bomb will never go off.

So, with 60 switches we have a 60-bit counter which cycles through the states 1 – (2^60 – 1) every 1152921504606846975 minutes (= 2.2e+12 years), without ever reaching zero.

**Solution:** The Professor’s reply is: “Never”.

LikeLike

]]>https://www.mayhematics.com/t/1d.htm

https://mathworld.wolfram.com/MagicTour.html

It has been proved (by computer) to be impossible on an 8×8 board,

though there are many semi-magic tours.

LikeLike

]]>So, 1 is on a black square. The next square in the tour will be a white square (as a knight always moves to a square of the other colour). So, 2 will be on a white square, 3 on a black square, etc. When the tour is complete all the black squares will be odd numbers and all the white squares will be even numbers.

The rows/columns alternate between (4 black + 3 white) and (4 white + 3 black) squares, but the total of a row/column consisting of (4 black + 3 white) numbers will be (4 odd + 3 even) = even. And the total of a row/column consisting of (4 white + 3 black) will be (4 even + 3 odd) = odd. And we require the sum of each row and column to be the same (it should be T(49) / 7 = 175),

So it will not be possible for us to make a magic squares as the sums of the values in the rows and columns will necessarily alternate between even and odd, so they can’t all be the same.

Hence it is not possible to construct a magic knight’s tour on a 7×7 board.

LikeLike

]]>@Frits: You’re right. Here is a more rigorous implementation of my original approach.

We collect all possible solutions, and look for a pair that are the same except the final two values are swapped over:

from enigma import Primes, irange, inf, is_square, is_cube, group, printf # primes less than 200 primes = Primes(200) # test x is of the form (a^k)p where p is prime and a >= m def test(x, k, m=2): for a in irange(m, inf): (p, r) = divmod(x, a ** k) if p < 2: return False if r == 0 and primes.is_prime(p): return True # tests t1 = primes.is_prime # a prime t2 = is_square # a square t3 = is_cube # a cube t4 = lambda x: test(x, 2) # (a square) times (a prime) t5 = lambda x: test(x, 3) # (a cube) times (a prime) t6 = lambda x: not any(f(x) for f in (t1, t2, t3, t4, t5)) # none of the above tests = (t1, t2, t3, t4, t5, t6, t1) # remove different prime amounts from <t>, remaining amounts satisfying <tests> # return the amounts removed def solve(t, tests, ns=[]): # are we done? if not tests: yield ns else: # remove less than half of t for n in primes: t_ = t - n if not(n < t_): break if n not in ns and tests[0](t_): yield from solve(t_, tests[1:], ns + [n]) # collect solutions, with the final two values ordered d = group(solve(200, tests), by=(lambda x: tuple(x[:5] + sorted(x[5:])))) # find a pair of solution values for (k, vs) in d.items(): if len(vs) > 1: printf("remaining={r}; {vs}", r=200 - sum(k))

LikeLike

]]>@Jim, I consider ” (fewer than remained)” also as part of “above would still be valid”, see last 2 checks.

from enigma import SubstitutedExpression, is_prime, is_square, is_cube, \ seq_all_different # is number product of a prime and a square (k=2) / cube (k=3) def is_primeprod(n, k): # primes up to (200 / 2^2) P = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] if k == 3: # primes up to (200 / 2^3) P = P[:9] for p in P: (d, r) = divmod(n, p) if d < 2: return False if r == 0 and ((k == 2 and is_square(d)) or (k == 3 and is_cube(d))): return True # the alphametic puzzle p = SubstitutedExpression( [ "is_prime(200 - AB)", "is_square(200 - AB - CD)", # fewer than remained "2 * CD < 200 - AB", "is_cube(200 - AB - CD - EF)", # fewer than remained "2 * EF < 200 - AB - CD", # (a square greater than 1) times a prime "is_primeprod(200 - AB - CD - EF - GH, 2)", # fewer than remained "2 * GH < 200 - AB - CD - EF", # (a cube greater than 1) times a prime "is_primeprod(200 - AB - CD - EF - GH - IJ, 3)", # fewer than remained "2 * IJ < 200 - AB - CD - EF - GH", # none of the aforementioned "not is_prime(200 - AB - CD - EF - GH - IJ - KL)", "not is_cube(200 - AB - CD - EF - GH - IJ - KL)", "not is_square(200 - AB - CD - EF - GH - IJ - KL)", "not is_primeprod(200 - AB - CD - EF - GH - IJ - KL, 2)", "not is_primeprod(200 - AB - CD - EF - GH - IJ - KL, 3)", # fewer than remained "2 * KL < 200 - AB - CD - EF - GH - IJ", "is_prime(200 - AB - CD - EF - GH - IJ - KL - MN)", # fewer than remained "2 * MN < 200 - AB - CD - EF - GH - IJ - KL", "seq_all_different([AB, CD, EF, GH, IJ, KL, MN])", "all(is_prime(x) for x in [AB, CD, EF, GH, IJ, KL, MN])", # rules are still valid if the numbers blasted off by the sixth and # seventh shots were swapped "not is_prime(200 - AB - CD - EF - GH - IJ - MN)", "not is_cube(200 - AB - CD - EF - GH - IJ - MN)", "not is_square(200 - AB - CD - EF - GH - IJ - MN)", "not is_primeprod(200 - AB - CD - EF - GH - IJ - MN, 2)", "not is_primeprod(200 - AB - CD - EF - GH - IJ - MN, 3)", # fewer than remained (also after swapping) "2 * MN < 200 - AB - CD - EF - GH - IJ", "2 * KL < 200 - AB - CD - EF - GH - IJ - MN", ], answer="(AB, CD, EF, GH, IJ, KL, MN), \ (200 - AB, \ 200 - AB - CD, \ 200 - AB - CD - EF, \ 200 - AB - CD - EF - GH, \ 200 - AB - CD - EF - GH - IJ, \ 200 - AB - CD - EF - GH - IJ - KL, \ 200 - AB - CD - EF - GH - IJ - KL - MN)", distinct="", d2i={}, env=dict(is_primeprod=is_primeprod), verbose=0) for (_, ans) in p.solve(): print(f"{ans}")

LikeLike

]]>@Robert: For any set of numbers chosen for the seven shots the number of blocks remaining after they have all been taken does not depend on the order they were taken in. So there is a unique answer for the number of blocks remaining, even though we don’t know what order the last two shots were taken in.

LikeLike

]]>LikeLike

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

from enigma import Primes, irange, inf, is_square, is_cube, printf # primes less than 200 primes = Primes(200) # test x is of the form (a^k)p where p is prime and a >= m def test(x, k, m=2): for a in irange(m, inf): (p, r) = divmod(x, a ** k) if p < 2: return False if r == 0 and primes.is_prime(p): return True # tests t1 = primes.is_prime # a prime t2 = is_square # a square t3 = is_cube # a cube t4 = lambda x: test(x, 2) # (a square) times (a prime) t5 = lambda x: test(x, 3) # (a cube) times (a prime) t6 = lambda x: not any(f(x) for f in (t1, t2, t3, t4, t5)) # none of the above t7 = t1 # a prime tests = (t1, t2, t3, t4, t5, t6, t7) # remove different prime amounts from <t>, remaining amounts satisfying <tests> # return the amounts removed def solve(t, tests, ns=[]): # are we done? if not tests: yield ns else: # remove less than half of t for n in primes: t_ = t - n if not(n < t_): break if n not in ns and tests[0](t_): yield from solve(t_, tests[1:], ns + [n]) # find solutions for ns in solve(200, tests): r = 200 - sum(ns) # where the tests still work with the last 2 amounts swapped if t6(r + ns[-2]): printf("remaining={r} {ns}")

**Solution:** There were 47 blocks remaining on the pitch after the seventh shot.

There are six possible sequences that satisfy all the conditions of the puzzle, and they can be grouped into three pairs that give the same total number of blocks remaining:

[3, 53, 19, 13, 31, 11, 29] → 41

[3, 53, 19, 13, 31, 23, 17] → 41

[3, 53, 19, 13, 31, 11, 23] → 47

[3, 53, 19, 13, 31, 23, 11] → 47

[3, 53, 19, 13, 31, 11, 17] → 53

[3, 53, 19, 13, 31, 23, 5] → 53

Each sequence is the same for the first 5 shots, and only the middle pair consists of sequences where the final two amounts are swapped over, so this gives our solution.

LikeLike

]]>When running MiniZinc on a model with a maximise()/minimise() target you only expect to get one answer, but I think some solvers will output “best so far” solutions as they work if the “all solutions” parameter is selected.

Replies on the site are indented, so there is less horizontal space. I like replies, rather than making new comment threads, but this is an issue for code. Unfortunately there doesn’t seem to be a documented option to allow *code* blocks to wrap long lines. (Although there is a [[ `collapse="true"`

]] option which means you have to click on it to view the code, which might be useful for long programs).

Personally I’m not bothered that much by horizontal scrolling (I prefer it to lots of wrapped lines). If I really want to look at (or run) a program I will copy it into an editor.

LikeLike

]]>A – I’m not sure there is much I can do to change the way the WordPress search works. Although I have noticed that on **S2T2** the search seems to also search comments as well as posts (on **Enigmatic Code** it only seems to search posts).

B – If you use an RSS reader the Comments Feed [ https://s2t2.home.blog/comments/feed/ ] has more comments in it (20 at the moment, but I’ve just upped it to the last 100).

C – I don’t have a version of the puzzles other than on the website. (I have backup XML file, but I think that is to load the site onto another WordPress instance). I think it would be OK to use a crawler to make a local copy of the sites, just don’t do it too aggressively, otherwise WordPress might take countermeasures. I think there are about 2158 puzzles between the sites. When I archived the old Google Groups site for Sunday Times Teasers I used Opera’s “Save Page as PDF …”, but there were only a few hundred pages to archive there.

Happy New Year!

LikeLike

]]>This Python program runs in 43ms.

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

from enigma import irange, inf, div, printf # calculate stacking numbers n <= m stack = lambda n, m: sum((n - i) * (m - i) for i in irange(n)) # or: n * (n + 1) * (3 * m - n + 1) // 6 # generate (n, m) pairs where 1 < n < m def pairs(): for t in irange(5, inf): for n in irange(2, (t - 1) // 2): yield (n, t - n) # consider n, m values for (n, m) in pairs(): # number of oranges in a box b = n * m # number of stacked oranges s = stack(n, m) - 1 # number of boxes required k = div(s, b) if k is not None: printf("n={n} m={m} b={b} s={s} k={k}") break

**Solution:** There were 72 oranges in each box.

3 boxes were used, making a total of 216 oranges.

The base layer was 6 × 12 layer, using 72 oranges (= 1 box).

The remaining layers:

5 × 11 = 55

4 × 10 = 40

3 × 9 = 27

2 × 8 = 16

1 × 6 = 6 (the final layer is 1 orange short)

use 55+40+27+16+6 = 144 oranges (= 2 boxes)

Manually:

To complete a structure starting with a base that is *n × m* where *n ≤ m*, the number of oranges required is:

S(n, m) = n(n + 1)(3m – n + 1)/6

And if we use *k* boxes we have:

n(n + 1)(3m – n + 1) / 6 = kmn + 1

n(n + 1)(3m – n + 1) = 6kmn + 6

*n* divides the LHS, so *n* must divide the RHS, hence *n* divides 6.

So: *n* = 1, 2, 3, 6.

If *n* = 6:

m = 12 / (7 – 2k)

so: *m* = 12, *k* = 3.

If *n* = 3:

m = 5 / (6 – 3k)

doesn’t work.

If *n* = 2:

m = 2 / (3 – 2k)

doesn’t work.

If *n* = 1:

m = 1 / (1 – k)

doesn’t work.

So: *n = 6*, *m* = 12, *k* = 3 is the only viable solution.

LikeLike

]]>This puzzle can be solved graphically, without the need to resort to *equations of motion* (although you can solve it that way too).

If we plot the velocities of the Austin (red), the Bentley (green), and the Cortina (blue) against time we get a graph like this:

Where red carries on at a steady 30mph, green starts at 40mph and decelerates steadily to 0mph (BB’), and blue starts at 50mph and decelerates steadily to 0mph (CC’).

At X, all their speeds are 30mph, and this is the point at which the separations between the cars are zero (the almost collision).

The area under the line XB’ gives the distance travelled by green after the almost collision, and the area under the line XA’ gives the distance travelled by blue after the almost collision.

And the difference between these distances corresponds to their final separation:

area(XUB’) – area(XUC’) = 45 yards

(45 – 45/2) units = 45 yards

1 unit = 2 yards

Similarly we can calculate the difference between the areas under the lines CX and BX to get the separation of green and blue at the time they started braking:

area(CAX) – area(BAX) = (10 – 5) units

= 10 yards

**Solution:** The Bentley and the Cortina were 10 yards apart at the time they started braking.

LikeLike

]]>@GeoffR, Yes, per default Minizinc will print intermediate solutions. In the configuration editor you have to use “user-defined behavior”.

I normally try to keep code within lines of 80 bytes. I will not correct for indentations within WordPress itself.

In your program “d” and “dig1” always seem to be the same as you have fixed PIN to 6 digits. Also N always have to be 5 in that case.

LikeLike

]]>Looking at the three solutions in the output, the first two solutions are not the minimum sum of the three two-digit numbers (224 and 225). The third solution is the answer and has a minimum sum of 223. Should have seen that earlier!

LikeLike

]]>On running your latest two MiniZinc postings, I found that they both give three solutions as output.

From my own experience in MinZinc, I also know this is possible and the correct solution can easily be found as one of the output solutions. However, for this teaser, my code produced a single solution. Maybe, with some minor amendment, this also might be possible for your code, but I am not sure why our outputs vary.

I also try to generally try to make most of my code visible in the available posting space. However, I have not found any guidance/ recommendations on this matter and opinions vary. I did achieve this aim for my MiniZinc posting, but it gets increasingly difficult to achieve when page widths for posting decrease with replies to postings.

I noticed there was quite a lot of your output code hidden from the available posting window on your first posting after my posting. I reformatted this last section of code to show that some simple adjustments to code can keep code readable in the window – please don’t mind me doing this as I am just making a comment.

% Frits part teaser code only - formatting comment % The moved digit 'd' is not in the account number constraint toNum10(row(t, 1), PIN * N[1]) /\ PIN * N[1] > 9999999; constraint toNum10(row(t, 2), PIN * N[2]) /\ PIN * N[2] > 9999999; constraint toNum10(row(t, 3), PIN * N[3]) /\ PIN * N[3] > 9999999; constraint forall( [row(t, 1)[i] != d | i in 1..8] ) \/ forall( [row(t, 2)[i] != d | i in 1..8] ) \/ forall( [row(t, 3)[i] != d | i in 1..8] ); output ["PIN = " ++ show(PIN)]; output ["\nACC No. = " ++ show(N[j] * PIN) ++ " " | j in 1..3 where fix(forall( [row(t, j)[i] != d | i in 1..8]))]; output ["\nThree 2-digit numbers are " ++ show(N)]; output["\nSum = " ++ show(sum(N))];

@Jim: Any comments about maximising available windows for posting code?

LikeLike

]]>A bit easier with div and mod.

% A Solution in MiniZinc include "globals.mzn"; % Product of three 2-digit numbers can't be a 7-digit number % % Smallest 8-digit number divided by biggest 2-digit number: % 10,000,000 // 99 = 101,010 so PIN has to be a 6-digit number var 100000..999999: PIN; % Array of sort codes array[1..3] of var int: N; % Array of possible accounts array[1..3, 1..8] of var int: t; % Using others similar analysis for the moving digit % d = initial digit, n = remaining number and N = power var 1..9: d; var 10000..99999:n; constraint d * 100000 + n == d * (10*n + d); constraint PIN = d * 100000 + n; % Three two digit numbers multiplied to give the PIN % N[1] <= 97 as 98^3 > 900,000 and 98^4 > 90,000,000, both containing a 9 % N[2] >= 32 as 31*31*99 < 100,000 % N[3] >= 47 as 46^3 < 100,000 constraint N[1] > 10 /\ N[1] < 98; constraint N[2] > 31 /\ N[2] < 100; constraint N[3] > 46 /\ N[3] < 100; % Increasing constraint N[3] > N[2] /\ N[2] > N[1]; constraint N[1] * N[2] * N[3] == PIN; % Smallest sum of three 2-digit numbers solve minimize(sum(N)); % The moved digit 'd' is not in the account number constraint forall( [(PIN * N[1] div pow(10,i-1) mod 10) != d | i in 1..8] ) \/ forall( [(PIN * N[2] div pow(10,i-1) mod 10) != d | i in 1..8] ) \/ forall( [(PIN * N[3] div pow(10,i-1) mod 10) != d | i in 1..8] ); % Account consists of 8 digits constraint forall( [PIN * N[i] > 9999999 | i in 1..3] ); output ["PIN = " ++ show(PIN)]; output ["\nACC No. = " ++ show(N[j] * PIN) ++ " " | j in 1..3 where fix(forall( [(PIN * N[j] div pow(10,i-1) mod 10) != d | i in 1..8]))]; output ["\nThree 2-digit numbers are " ++ show(N)]; output["\nSum = " ++ show(sum(N))];

LikeLike

]]>Based on GeoffR’s program. I didn’t reduce the account range to limit run-time, runs in half a second.

Removing lines 56-58 gives the same result but is probably not a good idea in combination with the minimize function.

% A Solution in MiniZinc include "globals.mzn"; % Product of three 2-digit numbers can't be a 7-digit number % % Smallest 8-digit number divided by biggest 2-digit number: % 10,000,000 // 99 = 101,010 so PIN has to be a 6-digit number var 101011..922082: PIN; % 97*97*98 % Array of sort codes array[1..3] of var int: N; % Array of possible accounts array[1..3, 1..8] of var int: t; predicate toNum(array[int] of var int: a, var int: n, int: base) = let { int: len = length(a) } in n = sum(i in 1..len) ( ceil(pow(int2float(base), int2float(len-i))) * a[i] ) /\ forall(i in 1..len) (a[i] >= 0 /\ a[i] < base) ; predicate toNum10(array[int] of var 0..9: a, var int: n) = toNum(a, n, 10); % Using others similar analysis for the moving digit % d = initial digit, n = remaining number and N = power var 1..9: d; var 10000..99999:n; constraint d * 100000 + n == d * (10*n + d); constraint PIN = d * 100000 + n; % Three two digit numbers multiplied to give the PIN % N[1] <= 97 as 98^3 > 900,000 and 98^4 > 90,000,000, both containing a 9 % N[2] >= 32 as 31*31*99 < 100,000 % N[3] >= 47 as 46^3 < 100,000 constraint N[1] > 10 /\ N[1] < 98; constraint N[2] > 31 /\ N[2] < 100; constraint N[3] > 46 /\ N[3] < 100; % Increasing constraint N[3] > N[2] /\ N[2] > N[1]; constraint N[1] * N[2] * N[3] == PIN; % Smallest sum of three 2-digit numbers solve minimize(sum(N)); % The moved digit 'd' is not in the account number constraint toNum10(row(t, 1), PIN * N[1]) /\ PIN * N[1] > 9999999; constraint toNum10(row(t, 2), PIN * N[2]) /\ PIN * N[2] > 9999999; constraint toNum10(row(t, 3), PIN * N[3]) /\ PIN * N[3] > 9999999; constraint forall( [row(t, 1)[i] != d | i in 1..8] ) \/ forall( [row(t, 2)[i] != d | i in 1..8] ) \/ forall( [row(t, 3)[i] != d | i in 1..8] ); output ["PIN = " ++ show(PIN)]; output ["\nACC No. = " ++ show(N[j] * PIN) ++ " " | j in 1..3 where fix(forall( [row(t, j)[i] != d | i in 1..8]))]; output ["\nThree 2-digit numbers are " ++ show(N)]; output["\nSum = " ++ show(sum(N))];

LikeLike

]]>Labelling the centres of the medallions (smallest to largest) are *A, B, C*, and the corresponding radii are 4, 9, *R*.

Then the horizontal displacement between *B* and *C*, *h(B, C)*, is given by:

h(B, C)² = (R + 9)² – (R – 9)²

h(B, C)² = 36R

h(B, C) = 6r, where: r = √R

And the horizontal displacement between *A* and *C*, *h(A, C)*, is given by:

h(A, C)² = (R + 4)² – (R – 4)²

h(A, C)² = 16R

h(A, C) = 4r

And the difference in these displacements is the horizontal displacement between *A* and *B*, *h(A, B)*:

h(A, B) = h(B, C) – h(A, C)

h(A, B) = 2r

h(A, B)² = 4r² = 4R

The vertical displacement between *A* and *B*, *v(A, B)*, is given by:

v(A, B) = 2R – 13

Then we have:

h(A, B)² + v(A, B)² = 13²

4R + (2R – 13)² = 13²

4R + 4R² – 52R = 0

4R(R – 12) = 0

So *R* = 12.

**Solution:** The radius of the largest medallion is 12cm.

LikeLike

]]>LikeLike

]]>AB could also have 11 as minimum.

CD could also have as minimum:

mi = max(AB, round(round(100000 / 99 + 0.5) / AB + 0.5))

LikeLike

]]>It can’t be run with PyPy yet (due to the walrus operator).

# product of three 2-digit numbers can't be a 7-digit number # # smallest 8-digit number divided by biggest 2-digit number: # 10,000,000 // 99 = 101,010 so PIN has to be a 6-digit number # # moving digit formula (pin1 = first digit, pin2_6 = the remaining digits): # # pin1.(100000 - pin1) / (10.pin1 - 1) = pin2_6 sol = [] # increasing sort codes AB, CD and EF for AB in range(10, 100): for CD in range(AB, 100): mi = max(CD, round(100000 / (AB * CD) + 0.5)) for EF in range(mi, 100): pin = AB * CD * EF pin1 = pin // 100000 pin2_6 = pin % 100000 # moving digit formula if pin1 * (100000 - pin1) == pin2_6 * (10 * pin1 - 1): sol.append([pin, AB + CD + EF, AB, CD, EF]) prev = -1 for s in sorted(sol): if s[0] != prev: # only do once for a minimal sum # account number has eight digits, none equal to the PIN's first digit accounts = [a for x in s[2:] if str(s[0])[0] not in (a := str(x * s[0])) and len(a) == 8] if len(accounts) > 0: print(f"account number(s): {', '.join(str(x) for x in accounts)}") prev = s[0]

LikeLike

]]>LikeLike

]]>@Frits: Yes. I’d forgotten PIN is (*k* + 1) digits.

And in fact we can see that PIN can only have 6 digits (so *k* = 5).

LikeLike

]]>@Jim, Very concise.

I think you can limit k to 3,4,5 as PIN cannot be a 7 digit number.

LikeLike

]]>A – Any news on better searching in WordPress sites?

B – Is it possible to have a link to more than eight “Recent Comments”?

C – As I normally spend 6 months in SE Asia and don’t always have internet access I am wondering if you could put somewhere a big zip file with all Enigmatic Code and S2T2 puzzles (for manual solving). I don’t want to overload your sites by using a website copier.

I wish you a Merry Christmas

LikeLike

]]>LikeLike

]]>