Assuming the 5 years of birth are all before 2000, we see that their sum must be a 4-digit number, i.e. 9685. And there is no carry into the sum of the day and month digits, which are 882. So we can deal this these three columns separately.

As there are 5 birthdays they cannot all be in the same month (as the final digit of the day/month sum would end in 5 or 0). In order for the final digit to be 2, twins birthday must at the beginning of a month, Jane at the end of the previous month, and John and Mum the day before that.

So the twins could be born on:

1st Mar:sum = 13 + 13 + 282 + 272 + 272 = 852 (non leap-year)

1st Mar:sum = 13 + 13 + 292 + 282 + 282 = 882 (leap year)

1st May:sum = 15 + 15 + 304 + 294 + 294 = 922

1st Jul:sum = 17 + 17 + 306 + 296 + 296 = 932

1st Sep:sum = 19 + 19 + 318 + 308 + 308 = 972

1st Oct:sum = 111 + 111 + 3110 + 3010 + 3010 = 9352

And only 1st Mar (twins), preceded by 29th Feb (Jane), and 28th Feb (John, Mum), gives the sum 882.

If the twins were born (on 1st Mar) in year *y*, then the year of Jane’s marriage is 4 years and 1 day earlier. And Jane is married on her birthday, which is 29th Feb, so *(y – 4)* (and hence *y*) must be a leap year.

If Jane is married at age *x* years, then her birth date must be *(y – x – 4)* (also a leap year, so *x* must be a multiple of 4).

And John is born on 28th Feb, 3 years earlier, i.e. in year *(y – x – 7)*.

Jane’s Mum was married exactly 1 year before that, i.e. in year *(y – x – 8)*.

So she was born in year: *(y – 2x – 8)*.

And the sum of the 5 birth years is 9685:

2y + (y – x – 4) + (y – x – 7) + (y – 2x – 8) = 9685

5y – 4x – 19 = 9685

x = (5y – 9704) / 4

Looking at leap years, going backwards from 1960:

y = 1960:x = 24

y = 1956:x = 19

y = 1952:x = 14

So the only viable candidate for *(y, x)* is (1960, 24) (as *x* must be a multiple of 4).

So the dates we are interested in are:

1960-03-01 = twins born

1956-02-29 = Jane and John’s wedding; Jane’s 24th birthday

1932-02-29 = Jane born

1929-02-28 = John born

1928-02-28 = Jane’s Mum’s wedding; Mum’s 24th birthday

1904-02-28 = Jane’s Mum born

Adding the 5 birth dates converted to integers we get:

131960 + 131960 + 2921932 + 2821929 + 2821904 = 8829685

**Solution:** Jane’s wedding day was: 29th February 1956.

LikeLike

]]>I was curious about the possibility of other viable maps giving different solutions (and cross that I did not come up with the map that seems to work). Several weeks and a crash-course in graph theory later, I developed a routine that generates all possible combinations of 18 allowed borders and then applies constraints until it produces a unique solution. My conclusions are as follow:

Combinations of 18 from the set of 26 allowed borders gives a long-list of 1562275 possibilities.

Only 564 of these contain all 9 regions with 4 borders each.

Only 9 of these are planar. I used Pyladder, which is a Python implementation of the graph planarity algorithm sourced from ‘Efficient Planarity Testing’ by John Hopcroft and Robert Tarjan. See https://github.com/haraldujc/pyladder

As far as I can see, there is no reason why any of these 9 might not, in principle, be valid. They do not all give the same result for Angler. However, if we apply a couple more constraints to the coastal regions, we do get Jim’s solution uniquely:

1 – one of the 9 only has 2 coastal-coastal borders, so the ‘island’ would be two islands.

2 – only one of the 9 has exactly 2 coastal borders per coastal region. All of the others have at least one coastal region with either 1 or 3 coastal borders. If we assume that the coastal regions represent quadrants of the island (which makes sense) then they will indeed have two coastal neighbours each. Hence the unique solution has that form.

LikeLike

]]># check if sequence <s> contains different digits (except 1) def diffdgts(s, no1s=0): s1 = "".join(str(x).zfill(2) for x in s) s2 = s1.replace("1", "") if len(set(s2)) != len(s2): return False else: if no1s and s1.count("1") != no1s: return False # F needs to contain 2 or 3 so don't allow both in A, B, C if len(s) == 3 and "2" in s2 and "3" in s2: return False return True # form angles into 2 groups of 3, each group summing to 360 for A in range(1, 18): # F needs to contain 2 or 3 so limit B to 19 for B in range(max(11, A + 1), 20): C = 60 - A - B if not diffdgts([A, B, C]): continue # second group for D in range(max(11, A + 1), 19): for E in range(D + 1, 20): F = 60 - D - E s = [A, B, C, D, E, F] if not diffdgts(s, 4): continue # either 100 + C or 100 + F has to be a power as A, B, C and D < 20 if len([x for x in [C, F] if x in {21, 25, 28}]) != 1: continue # list contains two prime numbers (prime numbers < 149) if len([x for x in s if x in {1, 3, 7, 9, 13, 27, 31, 37, 39}] ) != 2: continue ans = [100 + x for x in s] print(f"min={min(ans)} max={max(ans)} {ans}")

LikeLike

]]>from enigma import SubstitutedExpression, is_prime, unpack, mgcd, prime_factor # check a number is _not_ an exact power (see Brain-Teaser 683) is_no_power = lambda n, gcd=unpack(mgcd): gcd(e for (p, e) in prime_factor(n)) == 1 # main checks for sequence <s> def check(s): # only one digit is missing so we have four 1's and eight others s1 = "".join(str(x).zfill(2) for x in s) if len(set(s1)) != 9 or s1.count("1") != 4 : return False # list contains two prime numbers if len([x for x in s if is_prime(100 + x)] ) != 2: return False # list contains one power if len([x for x in s if is_no_power(100 + x)]) != 5: return False return True p = SubstitutedExpression( [ # 1AB + 1CD + 1?? = 360 ?? = 60 - AB - CD "AB < CD", "CD < 60 - AB - CD", # 1GH + 1IJ + 1?? = 360 ?? = 60 - GH - IJ "GH < IJ", "IJ < 60 - GH - IJ", # limit duplicate solutions "AB <= GH", # main check "check([AB, CD, 60 - AB - CD, GH, IJ, 60 - GH - IJ])", ], answer="(100 + AB, 100 + CD, 160 - AB - CD, \ 100 + GH, 100 + IJ, 160 - GH - IJ)", d2i=dict([(0, "CG")] + [(2, "AG")] + [(k, "ACGI") for k in range(3, 10)]), env=dict(check=check), distinct="", verbose=0, ) # print answer for (_, ans) in p.solve(): print(f"min={min(ans)} max={max(ans)} {ans}")

LikeLike

]]>Additionally the alternating angles of a *cyclic hexagon*, must sum to 360°, so the angles must be able to be formed into 2 groups of 3, each group summing to 360°. (In general for *cyclic 2n-gon* the alternating angles must have the same sum).

These constraints, along with the other conditions give two possible sets of angles, but they have the same largest and smallest values.

I assumed the angles were all different (although 111° could potentially be repeated, but there are no solutions where it is).

This Python 3 program runs in 122ms.

**Run:** [ @replit ]

from enigma import irange, mgcd, unpack, prime_factor, subsets, multiset, nsplit, printf # check digits have no repeats (other than 1) def digits(ns, ds=None): ds = (multiset() if ds is None else ds.copy()) for n in ns: ds.update_from_seq(nsplit(n)) # check the digits if all(v == 1 or k == 1 for (k, v) in ds.items()): return ds # determine if a number is a prime or an exact power from its prime factorisation is_prime = lambda fs: len(fs) == 1 and fs[0][1] == 1 is_power = lambda fs, gcd=unpack(mgcd): gcd(e for (p, e) in fs) > 1 # collect candidate primes, powers and others (primes, powers, others) = (list(), list(), set()) for n in irange(100, 179): if not digits([n]): continue fs = list(prime_factor(n)) if is_prime(fs): primes.append(n) elif is_power(fs): powers.append(n) else: others.add(n) # decompose <t> into <k> numbers in range [m, M] # that are not in primes or powers def decompose(t, k, m=100, M=179, ns=[]): # are we done? if k == 1: if not(t < m or t > M) and t in others: yield ns + [t] else: # choose the next number k_ = k - 1 for n in irange(m, min(M, t - k_ * m)): if n in others: yield from decompose(t - n, k_, n + 1, M, ns + [n]) # choose the two primes for (b, c) in subsets(primes, size=2): ds1 = digits([b, c]) if ds1 is None: continue # choose the power for a in powers: ds2 = digits([a], ds1) if ds2 is None: continue # find the remaining angles for xs in decompose(720 - (a + b + c), 3): ds3 = digits(xs, ds2) if ds3 is None: continue # only one of the 10 digits is missing if len(ds3.keys()) != 9: continue # and the sum of alternate angles must be 360 degrees ns = sorted([a, b, c] + xs) if any(sum(ss) == 360 for ss in subsets(ns, size=3)): printf("min={ns[0]} max={ns[-1]} {ns}")

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

LikeLike

]]>Wales scored 8 goals, with scores of 2-3 vE, 1-2 vE, 1-1, 2-2, 1-3 and 1-4.

And so the scores in England’s other games were 1-0 vI, 2-0, 3-0 and 5-0.

The other two matches were between Ireland and Scotland with scores of 0-0 and 4-0.

It is interesting to know when these puzzles started. Some of the later ones were much more convoluted

LikeLike

]]>5a^2 + 88a -4992 = 0. Then a = -8.8 +/- 32.8

LikeLike

]]>LikeLike

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

from enigma import int2words, irange, seq_all_different, printf N = 5 # how many numbers in the sequence T = 100 # sum must be less than this # map numbers to their initial letter m = dict((i, int2words(i)[0]) for i in irange(1, T - 1)) # add <k> numbers to <ns> in decreasing numerical order, # but increasing alphabetical order, that sum less than <t> def solve(ns, k, t): # are we done? if k == 0: yield ns else: # add in a lower number n0 = ns[-1] for n in irange(min(n0 - 1, t - k + 1), k, step=-1): # that is later alphabetically if m[n] > m[n0]: yield from solve(ns + [n], k - 1, t - n) # check the numbers in ns all start with different letters when offset by i check = lambda ns, i=0: seq_all_different(m[n + i] for n in ns) # start with the largest number for n in irange(5, 89): for ns in solve([n], N - 1, T - n): # check the starting letters are all different when offset by 1 if check(ns, 1): # output a solution printf("{ns} sum={t}", t=sum(ns))

**Solution:** The five numbers are: 18, 15, 9, 7, 3.

So the initial letters are: E, F, N, S, T (which are in alphabetical order).

Adding one to each term we get: 19, 16, 10, 8, 4; with initial letters: N, S, T, E, F.

And we can also increment each term again to get: 20, 17, 11, 9, 5; initial letters: T, S, E, N, F.

LikeLike

]]>We are told that the sum of Scotland and Ireland’s “goals against” values is 20, which means the total of the “goals against” column must be 38. And so the sum of Scotland and Ireland’s “goals for” values must be 38 – (16 + 8) = 14.

The following Python program uses the [[ Football() ]] helper class from the **enigma.py** library. It runs in 1.22s.

**Run:** [ @replit ]

from itertools import product from enigma import Football, ordered, chunk, subsets, irange, multiset, join, printf # scoring system football = Football(games="wdl", points=dict(w=2, d=1)) # identify matches with the same scoreline keys = lambda ss: list(ordered(*s) for s in ss) # check a sequence of scores, all different and in ordered pairs check = lambda ss, ps: len(set(ss)) == len(ss) and all(x > y for (x, y) in chunk(ps, 2)) # margin margin = lambda ss: ss[0] - ss[1] # record scores in the S vs I matches rs = multiset() # scorelines for E (who have won all their matches) (ew1, ew2, es1, es2, ei1, ei2) = mes = 'w' * 6 for ssE in football.scores(mes, [0] * 6, 16, 3): # check scorelines ss0 = keys(ssE) if not check(ss0, ss0): continue # E wins E vs W by same margin, same as exactly one of the E vs I (EW1, EW2, ES1, ES2, EI1, EI2) = ssE d = margin(EW1) if not(d == margin(EW2) and (margin(EI1), margin(EI2)).count(d) == 1): continue # W have 2 draws and 2 losses remaining for mws in subsets("ddll", size=len, select="mP"): for ssW in football.scores(mws, [0] * 4, 8, 15, [EW1, EW2], [1, 1]): ss1 = keys(ssW) if not check(ss0 + ss1, ss1): continue # calculate current goals for/against S and I (so far) (WS1, WS2, WI1, WI2) = ssW (fS, aS) = football.goals([ES1, ES2, WS1, WS2], [1, 1, 1, 1]) (fI, aI) = football.goals([EI1, EI2, WI1, WI2], [1, 1, 1, 1]) # goals against S and I sum to 20 (and goals for S and I sum to 14) (ga, gf) = (20 - aS - aI, 14 - fS - fI) if ga < 0 or gf < 0: continue # choose outcomes for S vs I matches (ws1, ws2, wi1, wi2) = mws for (si1, si2) in football.games(repeat=2): S = football.table([es1, es2, ws1, ws2, si1, si2], [1, 1, 1, 1, 0, 0]) I = football.table([ei1, ei2, wi1, wi2, si1, si2], [1, 1, 1, 1, 1, 1]) if not(12 >= S.points >= I.points >= 2): continue # chose remaining "for" and "against" goals for S for (x, y) in product(irange(0, gf), irange(0, ga)): # look for scorelines for (SI1, SI2) in football.scores([si1, si2], [0, 0], x, y): ss2 = keys([SI1, SI2]) if not check(ss0 + ss1 + ss2, ss2): continue # and check goals "for"/"against" I (x_, y_) = football.goals([SI1, SI2], [1, 1]) if not(x + x_ == gf and y + y_ == ga): continue # check S is second and I is third if not(S.points > I.points or fS - aS + x - y > fI - aI + x_ - y_): continue if not(I.points > 2 or fI - aI + x_ - y_ > -7): continue printf("[EW = {EW1} {EW2}; ES = {ES1} {ES2}; EI = {EI1} {EI2}; WS = {WS1} {WS2}; WI = {WI1} {WI2}; SI = {SI1} {SI2}]") rs.add((SI1, SI2)) # output solution for (k, v) in rs.most_common(): printf("S vs I = {k} [{v} solutions]", k=join(k, sep=", "))

**Solution:** The scores in the Scotland vs. Ireland matches were 4-0 and 0-0.

There are many scenarios which lead to this solution. One of them is:

E vs W = 3-2, 2-1

E vs S = 3-0, 2-0

E vs I = 5-0, 1-0

W vs S = 2-2, 1-3

W vs I = 1-4, 1-1

S vs I = 4-0, 0-0

LikeLike

]]>LikeLike

]]>I mixed up the tenses in my comment above. Please read the following before the above:

I cannot be W, N or E. If I were M, I would have to be in [w] after the swap to live west of E and W. Then N must be in [w] before the swap, which is invalid as he lived east of S. And so I am South. E, M and W did not change positions.

LikeLike

]]>M lived west of W and E, and so M lived at [w]

N lived east of S, and so N lived at [e]

N turned right to visit E, and so E lived at [n]

I lived north of W, and I cannot be E, and so I, South, lived at [m] and W lived at [s].

Swapping S and N gives the current map, as shown by Jim. Logically it is the only possible arrangement.

LikeLike

]]>I initially took: “I could go straight ahead when visiting North”, to mean that the journey I→N involved passing straight through the centre (i.e. was one of: n→s, e→w, s→n, w→e), but with this condition there are no solutions. So instead I used the condition, that the journey I→N does not involve making a turn at the centre, and that gives a unique solution.

For each of the directions we can use equivalent statements, for example:

“X is north of Y” ⇔ “(X is northernmost) ∨ (Y is southernmost)”

This Python program runs in 47ms.

**Run:** [ @replit ]

from enigma import subsets, printf # map slots to locations m = { 0: "centre", 1: "north", 2: "east", 3: "south", 4: "west" } # possible turns at the centre right = {(1, 4), (2, 1), (3, 2), (4, 3)} #straight = {(1, 3), (2, 4), (3, 1), (4, 2)} # choose current positions for (N, S, E, W, M) for (N, S, E, W, M) in subsets((0, 1, 2, 3, 4), size=len, select="P"): # "W is east of M" if not(W == 2 or M == 4): continue # "M is west of E" if not(M == 4 or E == 2): continue # "N (was I) is north of W" if not(N == 1 or W == 3): continue # choose current position for I (not N; not W) for I in (S, E, M): oldN = I oldI = N oldS = (N if I == S else S) oldE = (N if I == E else E) # "N was east of S" if not(oldN == 2 or oldS == 4): continue # "N -> E was a right turn" if not((oldN, oldE) in right): continue # "I -> N was straight ahead" #if not((oldI, oldN) in straight): continue if (oldI, oldN) in right or (oldN, oldI) in right: continue # output solution printf("I={I}; N={N} S={S} E={E} W={W} M={M}", I="NSEWM"[(N, S, E, W, M).index(I)], N=m[N], S=m[S], E=m[E], W=m[W], M=m[M], )

**Solution:** You are South. The current map is as shown:

(Previously North and South were swapped over).

And it can be seen that the journey from South (I) to North does not involve making a turn at the centre, but it also doesn’t involve passing through the centre.

LikeLike

]]>LikeLike

]]>This time not using the fact that XY must be 27 or 37.

#!/usr/bin/env python3 -m enigma -r # Harry = 0.(EFG)... EFG = HA * PQ # Dick = 0.(JKL)... JKL = DI * PQ # Tom = 0.(RST)... RST = TO * PQ # # where XY * PQ = 999 (skip XY = 9 as 1/9 = 0.111111...) # # each gets an integer share of XY (< 50) SubstitutedExpression --distinct="" # HA is maximal if Dick and Tom have values HA + 1 and HA + 2 # so HA < 16 and TO < 30 --invalid="2|3|4|5|6|7|8|9,H" --invalid="3|4|5|6|7|8|9,T" --invalid="0|5|6|7|8|9,X" # XY * PQ = 999 "div(999, XY) = PQ" # exact division # HA is minimal if Dick and Tom have values 2 * HA - 2 and 2 * HA - 1 # HA is maximal if Dick and Tom have values HA + 1 and HA + 2 "3 * XY + 9 <= 15 * HA <= 5 * XY - 15" # TO > DI means TO > XY - HA - TO so 2 * TO > XY - HA # Tom got less then twice as much as Harry # TO is maximal if Dick is HA + 1 so TO <= XY - 2 * HA - 1 "XY - HA < 2 * TO <= min(4 * HA - 2, 2 * XY - 4 * HA - 2)" # together they give the entire amount "XY - HA - TO = DI" # EFG, JKL and RST must use different digits "len(set(str(HA * PQ) + str(DI * PQ) + str(TO * PQ))) == 9" # (Total, Tom, Dick, Harry) --answer="XY, TO, DI, HA" --verbose=16 --reorder=0

LikeLike

]]>In order to have a score of 1 there must be a “1” sector. This Python program considers sets of three sectors of the form *(1, a, b)*, and calculates the lowest score not achievable with 3 darts, until it finds a value of 36. It runs in 54ms.

**Run:** [ @replit ]

from enigma import union, subsets, irange, inf, peek, printf # find the lowest score not achievable with k darts and values vs def lowest(vs, k): # scores with 1 dart scores = union((v, 2 * v, 3 * v) for v in vs) # scores with up to k darts ss = set(sum(s) for s in subsets(scores, max_size=k, select="R")) # find the lowest score that cannot be made return peek(n for n in irange(0, inf) if n not in ss) # find 3 sectors with lowest impossible score of x def solve(x, k=3): # consider sectors [1, a, b], where a + b = t for t in irange(5, inf): for a in irange(2, (t - 1) // 2): vs = [1, a, t - a] if lowest(vs, k) == x: yield vs for vs in solve(36): printf("sectors = {vs}") break

**Solution:** The sector labels are: 1, 5, 22.

LikeLike

]]>The fractions are of the form of abc / 999 = abc / (27 * 37)

The three digits are 2, 3 and 4, with one of each in each column.

If the sum were £27, abc = 0 mod 37, but 9 * 37 = 333

And so the sum = £37, abc = 0 mod 27.

And so, by inspection, 9 * 27 = 243, and then 12 * 27 = 324 and 16 * 27 = 432

The solution follows.

LikeLike

]]>The sum of the “abc”s is 999. The individual digits are 0 or 9, and 1 to 8.

The “a”s must be 2, 3 and 4. Then the “b”s are 0, 1 and 7, and the “c”s are 5, 6 and 8.

If the sum were £27, abc = 0 mod 37, but 5 * 37 = 185 and 15 * 37 = 535.

And so the sum = £37, and abc = 0 mod 27

And so, by inspection, 15 * 27 =405, and then 8 * 27 = 216 and 14 * 27 = 378

The solution follows.

LikeLike

]]>LikeLike

]]>In fact we can just change the selection criteria at line 21 of my original program for **Teaser 2785** to give a program that solves this puzzle.

This Python program runs in 64ms.

**Run:** [ @replit ]

from enigma import irange, divc, divf, recurring, subsets, arg, printf M = arg(49, 0, int) # consider the total amount for t in irange(6, M): # look for amounts that give 3 recurring digits r = dict() for n in irange(divc(t, 5), min(t - 3, divf(2 * t, 3))): (i, nr, rr) = recurring(n, t) if nr or len(rr) != 3: continue r[n] = rr # now find three amounts that sum to t for (H, D, T) in subsets(sorted(r.keys()), size=3): if H + D + T != t: continue # "Tom gets less than twice as much as Harry" if not(T < 2 * H): continue # and check the recurring digits are all the same if not(set(r[H]) == set(r[D]) == set(r[T])): continue printf("total={t}; Harry={H} [0.({rH})...], Dick={D} [0.({rD})...], Tom={T} [0.({rT})...]", rH=r[H], rD=r[D], rT=r[T])

**Solution:** Tom got £ 16, Dick got £ 12, and Harry got £ 9. In total they received £ 37.

And the fractions are:

T:16 / 37 = 0.(432)…

D:12 / 37 = 0.(324)…

H:9 / 37 = 0.(243)…

And, again, we can find further solutions, using the same fractions, at multiples of these amounts.

The next essentially different solution occurs at £ 111, where we have the original solution multiplied by 3, but also:

T:47 / 111 = 0.(423)…

D:38 / 111 = 0.(342)…

H:26 / 111 = 0.(234)…

LikeLike

]]># calculate the number of dead-right's correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys)) # ceil a positive number ceil = lambda n: int(n) if n == int(n) else int(n) + 1 # convert from integer to any base number def int2base(n, b, D="0123456789abcdefghijklmnopqrstuvwxyz"): return D[0] if not n else (int2base(n // b, b)).lstrip(D[0]) + D[n % b] # return all entries where f(x) occurs only once # or where f(x) occurs more than once def filter_unique(seq, f=lambda x: x, mode="unique"): d = dict() for s in seq: d[f(s)] = f(s) in d if mode == "unique": # occuring only once return [s for s in seq if not d[f(s)]] else: # occuring more than once return [s for s in seq if d[f(s)]] li = [] # 3-digit numbers with digits increasing by 1 from first to last for n in (123, 234, 345, 456, 567, 678, 789): # limit the used bases so they result in a 3 digit number for b in range(ceil(n ** (1 / 3)), int(n ** 0.5) + 1): N = int2base(n, b) # Sam eventually produced a 3-digit answer if not N.isdigit(): continue # if 2 digits are correct the other must be incorrect if correct(str(n), N) == 2: li.append((str(n), N, b)) f = filter_unique(li, lambda s: [i for i, x in enumerate(s[0]) if x not in s[1]][0]) if len(f) == 1: print(f"correct answer = {f[0][0]}, Sam's base = {f[0][-1]}")

LikeLike

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

from enigma import irange, divc, divf, recurring, subsets, arg, printf M = arg(49, 0, int) # consider the total amount for t in irange(6, M): # look for amounts that give 3 recurring digits r = dict() for n in irange(divc(t, 5), min(t - 3, divf(2 * t, 3))): (i, nr, rr) = recurring(n, t) if nr or len(rr) != 3: continue r[n] = rr # now find three amounts that sum to t for (H, D, T) in subsets(sorted(r.keys()), size=3): if H + D + T != t: continue # "Tom gets less than twice as much as Harry" if not(T < 2 * H): continue # and check the recurring digits are all different if len(set(r[H] + r[D] + r[T])) != 9: continue printf("total={t}; Harry={H} [0.({rH})...], Dick={D} [0.({rD})...], Tom={T} [0.({rT})...]", rH=r[H], rD=r[D], rT=r[T])

**Solution:** Tom got £ 15, Dick got £ 14, and Harry got £ 8. In total they received £ 37.

The fractions are:

T:15 / 37 = 0.(405)…

D:14 / 37 = 0.(378)…

H:8 / 37 = 0.(216)…

There are further solutions at multiples of these amounts (the fractions, of course, remain unchanged).

The next essentially different solution occurs with a total of £ 333, where we can have the original solution multiplied by 9, but also:

T:136 / 333 = 0.(408)…

D:125 / 333 = 0.(375)…

H:72 / 333 = 0.(216)…

T:135 / 333 = 0.(405)…

D:106 / 333 = 0.(318)…

H:92 / 333 = 0.(276)…

T:136 / 333 = 0.(408)…

D:105 / 333 = 0.(315)…

H:92 / 333 = 0.(276)…

LikeLike

]]>Mt’ = Mt-3x

This is because the triangles are congruent and have the same centroid, the centroid being 1/3 of the way along the vertical height.

For each of the six triangles in the hexagon, the inner triangle has vertical height:

Mh’ = Mh-x

This is because we only need to have a border for the circular disc at the outer edge of the hexagon, or the ‘bottom’ of each triangular sector.

The area of each triangle is proportionate to the square of any relevant length, so we can ignore all constants of proportionality and state that:

(Mt’/Mt)^2 = (Mh’/Mh)^2

implies ((Mt-3x)/Mt)^2 = ((Mh-x)/Mh)^2

This condition is satisfied if we make the substitution Mt=3Mh (or just take square roots of each side of the equation and rearrange).

To find the solution, I just looped over possible values for h out of the set of even triangular numbers, setting the limit assuming the ‘across’ distance was measured from flat to flat, like a spanner.

h_lim = 1000/3**(1/3)#limit width of a horizontal tile i_lim = int(((8*h_lim+1)**(1/2)-1)/2)+1#implied limit of possible triangular numbers result=[h for h in [member for item in [[i*(i+1)/2,(i+2)*(i+1)/2] for i in range(3,i_lim,4)] for member in item] if ((h*24+1)**(1/2)-1)%2==0] print("The triangles have side",result[0]*3,"mm; the hexagons",result[0],"mm")

LikeLike

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

from datetime import date, timedelta from enigma import Rational, sprintf, first, arg, printf F = Rational() # rational implementation # find "special" days def solve(): m = 11061 # start mileage d = date(1961, 10, 1) # start date i = F(178, 7) # daily mileage increment j = timedelta(days=1) # daily date increment while m < 311299: # calculate mileage at the end of the day m_ = m + i # write the date as a string s = sprintf("{dd}{mm}{yy:02d}", dd=d.day, mm=d.month, yy=d.year % 100) # turn them integers (a, b, x) = map(int, (m, m_, s)) # is x in [a, b]? if a <= x <= b: printf("{d}: {x} in [{a}, {b}]") yield (d, a, b) (m, d) = (m_, d + j) # find the first n solutions n = arg(2, 0, int) first(solve(), n)

**Solution:** Sunday, 17th June 1962.

There is one more 5-digit mileage that occurs on a “special” day:

21162 on Friday, 2nd November 1962

And then there are three 6-digit mileages that occur on “special” days in the 1990s:

281090 on Sunday, 28th October 1990

291191 on Friday, 29th November 1991

301292 on Wednesday, 30th December 1992

LikeLike

]]>For a regular *n*-gon with a side length of *s*, we can consider a 1/*n* sector.

The angle at the centre is 2θ = 360°/*n* ⇒ θ = 180°/*n*

And the area of the entire sector is:

A = s² / 4tan(θ)

Reducing the height of the sector by the radius of the disc *x* gives:

a = (s/2tan(θ) – x)² tan(θ) = (s – 2x tan(θ))² / 4 tan(θ)

And the probability of the disc landing entirely within the complete tile is *P = na/nA = a/A*

P = ((s – 2x tan(θ)) / s)² = (1 – (2x/s) tan(θ))²

For the triangle tan(θ) = tan(60°) = √3, and the side length is *t*.

For the hexagon tan(θ) = tan(30°) = 1/√3, and the side length is *h*.

And the probabilities are equal when:

(2x/t) tan(60°) = (2x/h) tan(30°)

h tan(60°) = t tan(30°)

3h = t

Here’s a graphical demonstration of the probabilities when *3h = t*.

On the left, the hexagonal tile (green) fits exactly in the triangular tile (red), and we see that if each small triangle has area *Z*, then the hexagonal tile has an area *6Z* and the triangular tile has an area *9Z*.

On the right there is also a smaller version of each tile, that is one radius of the disc away from the perimeter of the corresponding actual tile. If the small triangles have area *z*, then the small version of the hexagonal tile has an area *6z* and the small version of the triangular tile has an area of *9z*.

The probability then of the disc falling in the hexagonal tile is *6z / 6Z = z/Z*, and the probability of the disc falling in the triangular tile is *9z / 9Z = z/Z*. Hence the probabilities are the same for each tile.

LikeLike

]]>@Frits: It looks like the size of the disc doesn’t matter, as long as *t = 3h* the probabilities will be the same. I’ll look again at my equations to see why *x* didn’t drop out. (Line 9 is just a restatement of the inradius conditions).

John Crabtree pointed me towards a better derivation of the relationship:

If the triangular tiles have a side *t*, then the area of one tile is: *A(t) = T⋅t*², where *T* = (√3)/4

And the inradius is: *r = t/(2√3)*, so the disc has a radius less than this: *x < r*

We make a smaller triangle with inradius = *(r – x)*, it has sides of length: *u = (t – 2(√3)x)*

If the centre of the disc lands within this triangle the entire disc will be inside the tile.

So the area of this smaller triangle is: *A(u) = T⋅u*²

And the probability of the disc falling entirely within the triangle (*P*) is the ratio of these areas:

P = A(u) / A(t) = (u / t)²

If the hexagonal tiles have a side *h*, then the area of the hexagon is: *B(h) = H⋅h*², where *H* = (3/2)(√3) (as it is composed of 6 equilateral triangles).

And the inradius is: *s = h(√3)/2*, so the radius of the disc is also less than this: *x < s*

We make a smaller hexagon with inradius = *(s – x)*, it has sides of length: *i = (h – 2x/√3)*, and the area is: *B(i)*

So the probability of the disc falling entirely within the hexagon (*Q*) is the ratio of these two areas:

Q = B(i) / B(h) = (i / h)²

The probabilities are equal when their square roots are equal:

P = Q

→ (u / t) = (i / h)

→ i⋅t = h⋅u

→ (h – 2x/√3)t = h(t – 2(√3)x)

→ ht – 2xt/√3 = ht – 2(√3)xh

→ 2xt/√3 = 2(√3)xh

→ t = 3h

Hence the solution is found when *t = 3h* (and *0 < x < (√3)h/2*), and we just need to find two even triangular numbers in the required range, where one is 3 times the other:

from enigma import irange, inf, sqrt, divf, printf # max tile size M = divf(2000, sqrt(3)) # collect even triangular numbers ts = set() t = 0 for i in irange(1, inf): t += i if t > M: break if t % 2 == 0: (h, r) = divmod(t, 3) if r == 0 and h in ts: printf("t={t}mm h={h}mm") ts.add(t)

LikeLike

]]>@Jim,

Could you explain the line 9 formula (which actually says if t == 3 * h)?

If we forget about triangular numbers and we choose t=300 and h=100 and a disc so that exactly 3 discs fit in the triangle (touching the sides) do you now say that both chances are not the same?

LikeLike

]]>[**Note:** See my next comment for a better way of approach the problem and deriving the relationship between *t* and *h*].

Wolfram Alpha [ link ] simplifies the calculation of *x* to:

x = (√3)ht / (3h + t)

Which gives a faster program:

from itertools import product from enigma import irange, inf, sqrt, fdiv, printf # root 3 r3 = sqrt(3) # find the radius of a disc that makes the probabilities equal def solve(t, h): if 3 * h <= t and t <= 3 * h: # output solution printf("t={t}mm h={h}mm") # generate even triangular numbers def tris(f): t = 0 for i in irange(1, inf): t += i if t * f > 1000: break if t % 2 == 0: yield t # collect possible sides for the triangular tiles ts = list(tris(0.5 * r3)) # collect possible sides for the hexagonal tiles hs = list(tris(r3)) # select t and h, and look for a solution for (t, h) in product(ts, hs): solve(t, h)

LikeLike

]]>@Jim, I didn’t see that (about even).

I calculated that for the same inner circle the side length of the (smallest outer) triangle must be three times the side length of the (smallest outer) hexagon. I hoped that would answer the probability question as well.

LikeLike

]]>@Frits: I don’t think that can be correct, because the answers have to be *even* numbers (that are also triangular).

LikeLike

]]># get triangular root tri = lambda n: 0.5 * ((8 * n + 1) ** 0.5 - 1.0) # For the same inner circle the side length of the (smallest outer) triangle # must be three times the side length of the (smallest outer) hexagon. # check triangular numbers for i in range(1, 50): lHex = (i * (i + 1)) // 2 lTri = 3 * lHex # both sides must be even if lHex % 2 or lTri % 2: continue # the tiles being less than 1m across. # length across triangle: lTri, length across hexagon: lHex * sqrt(3) if lTri > 999: break if tri(3 * lHex) % 1 == 0 : print(f"lengths of the sides of the triangular and hexagonal tiles: " f"{lTri} mm and {lHex} mm")

LikeLike

]]>If the triangular tiles have a side *t*, then the area of one tile is: *A = (t*²*√3)/4*

And the inradius is: *r = t/(2√3)*

The radius of the disc cannot be larger than the inradius of the triangle.

So the smaller version of the triangle has an inradius of: *(r – x)*

And the corresponding area is: *a = (3√3)(r – x)*²

The probability of the disc landing entirely within a single tile is then: *P = a / A*

Similarly for a hexagon with side *h*:

The area of one hexagonal tile is: *B = 3(h*²*√3)/2* (it is composed of 6 equilateral triangles).

And the inradius is: *s = h(√3)/2*

Again the radius of the disc cannot be larger than the inradius of the hexagon.

We make a smaller hexagon with inradius = *(s – x)*

And the corresponding area is: *b = (2√3)(s – x)*²

And the probability of the disc landing entirely within a single tile is: *Q = b / B*

This is enough to permit a programmed solution.

The following program finds possible sides for the triangular and hexagonal tiles, and then looks for pairs that can be solved to give a disc that makes the probabilities the same.

It runs in 73ms.

**Run:** [ @replit ]

from itertools import product from enigma import irange, inf, sqrt, fdiv, find_zero, printf # root 3 r3 = sqrt(3) # find the radius of a disc that makes the probabilities equal def solve(t, h): # area of a triangular tile A = t * t * r3 * 0.25 r = fdiv(t, 2 * r3) # hexagonal tile B = h * h * r3 * 1.5 s = h * r3 * 0.5 # find the difference between the probabilities def f(x): a = 3 * r3 * (r - x) ** 2 b = 2 * r3 * (s - x) ** 2 P = fdiv(a, A) Q = fdiv(b, B) return P - Q # find a zero of f try: r = find_zero(f, 1, min(r, s)) except ValueError: return # output solution printf("t={t}mm h={h}mm [x={r.v:.2f}mm]") # generate even triangular numbers def tris(f): t = 0 for i in irange(1, inf): t += i if t * f > 1000: break if t % 2 == 0: yield t # collect possible sides for the triangular tiles ts = list(tris(0.5 * r3)) # collect possible sides for the hexagonal tiles hs = list(tris(r3)) # select t and h, and look for a solution for (t, h) in product(ts, hs): solve(t, h)

**Solution:** The triangular tiles have sides of 630mm. The hexagonal tiles have sides of 210mm.

A bit more analysis (see below), shows us that the probabilities are the same when the triangular tiles have sides that are 3 times the sides of the hexagonal tiles. And as long as the disc is small enough to fit inside the tiles its size does not matter.

So we are just looking for a pair of even triangular numbers *(t, h)* where *t = 3h*, which can be found manually or with a simple program.

LikeLike

]]>The spiders wish to traverse from one corner of the floor to the diagonally opposite corner, but without crossing the floor.

Beth walks along one of the 5 shortest routes (which presumably means that there are 5 shortest routes that have the same distance travelled).

Two of these routes (*p1, p2*) are along the edges of the floor:

To see the other routes we can “unfold” the barn (imagine it is a cardboard box), and the shortest paths will appear as straight lines.

We get a route that crosses the front, ceiling and left wall (*p3*):

And routes that cross the right wall, ceiling, back wall (*p4*), and right wall, ceiling, left wall (*p5*).

So the following expressions all give the square of the minimal path length:

[p1, p2] (x + y)² = x² + y² + 2xy

[p5] (x + 2z)² + y² = x² + y² + 4z² + 4xz

[p3, p4] (x + z)² + (y + z)² = x² + y² + 2z² + 2xz + 2yz

So, given values for *x* and *y*, we can calculate *z*, and then look for diagonals of the cube (= *hypot(x, y, z)*) that are within 5cm of a whole number of metres, to give Sam’s path.

This Python program runs in 47ms.

**Run:** [ @replit ]

from enigma import irange, inf, quadratic, hypot, first, arg, printf # generate solutions def solve(verbose=1): # consider increasing lengths for y in irange(2, inf): # and widths for x in irange(1, y - 1): # compute corresponding z (using p1,p2 & p5) for z in quadratic(4, 4 * x, -2 * x * y, domain="Z"): if not(z > 0): continue # check against p3,p4 if not((x + y) ** 2 == (y + z) ** 2 + (x + z) ** 2): continue # calculate the length of diagonal through the cuboid h = hypot(x, y, z) d = abs(h - int(h + 0.5)) # check it is within 5cm of a whole number of metres if d > 0.05: continue # lengths of paths for Beth and Sam (b, s) = (x + y, h + z) if verbose: printf("z={z} [x={x} y={y}; h={h:.3f} d={d:.3f}, beth={b} sam={s:.3f}]") yield (x, y, z) # find the first n solutions n = arg(1, 0, int) first(solve(), n)

**Solution:** The barn in 4 metres high.

In fact there is a family of solutions, the program stops after the first (smallest) solution, which is the only reasonable one, where the barn is less 25m high (and the spiders journeys are each less than 100m).

Analytically:

Equating the sum of the first two of the expressions with twice the third, we get:

2x² + 2y² + 4z² + 2xy + 4xz = 2x² + 2y² + 4z² + 4xz + 4yz

2xy = 4yz

x = 2z

Then, substituting for *x* in the first 2 expressions, and equating them:

4z² + y² + 4yz = 16z² + y²

4yz = 12z²

y = 3z

Hence the barn has dimensions *(2z, 3z, z)*, and the shortest paths have length *5z*.

The diagonal across the barn is: √(*x*² + *y*² + *z*²) = √(14*z*²) = (√14)*z*.

And we want to know when this is within 5cm if a whole number of metres.

Here is a shorter and faster program to generate solutions:

from enigma import irange, inf, sqrt, first, arg, printf def solve(): r14 = sqrt(14) for z in irange(1, inf): h = z * r14 d = abs(h - int(h + 0.5)) if d > 0.05: continue (x, y) = (2 * z, 3 * z) (b, s) = (x + y, h + z) printf("z={z} [x={x} y={y}; h={h:.3f} d={d:.3f}; beth={b} sam={s:.3f}]") yield (x, y, z) # find the first n solutions n = arg(1, 0, int) first(solve(), n)

LikeLike

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

from enigma import SubstitutedExpression, join, seq_all_different, subsets, diff, update, map2str, printf # set denest=1 if not using PyPy SubstitutedExpression.set_default(denest=1, verbose=0) # ann gwe lin pam ros # joe: A B C D E += 15 # dan: F G H I J += 15 # xxx: K L M N P += 15 # yyy: Q R S T U += 15 # zzz: V W X Y Z += 15 # marks from the judges (joe, dan, xxx, yyy, zzz) = ("ABCDE", "FGHIJ", "KLMNP", "QRSTU", "VWXYZ") # marks for the contestants (ann, gwe, lin, pam, ros) = ("AFKQV", "BGLRW", "CHMSX", "DINTY", "EJPUZ") # indices for top girls tops = "abcde" xset = lambda x: join(x, sep=", ", enc="{}") xseq = lambda x: join(x, sep=", ", enc="()") xsum = lambda x: join(x, sep=" + ", enc="()") top = lambda t: t.index(max(t)) p = SubstitutedExpression( [ # we know the marks from each judge (but not the order) f"{xset(joe)} == {{0, 1, 2, 3, 9}}", f"{xset(dan)} == {{1, 2, 3, 4, 5}}", f"{xset(xxx)} == {{0, 1, 2, 4, 8}}", f"{xset(yyy)} == {{0, 1, 3, 4, 7}}", f"{xset(zzz)} == {{0, 2, 3, 4, 6}}", # Joe placed G above L "B > C", # P finished ahead of R ... f"{xsum(pam)} > {xsum(ros)}", # ... but didn't win f"{xsum(pam)} < max({xsum(gwe)}, {xsum(lin)}, {xsum(ann)})", # each judge chose a different top girl f"top({xseq(joe)}) = {{a}}", f"top({xseq(dan)}) = {{b}}", f"top({xseq(xxx)}) = {{c}}", f"top({xseq(yyy)}) = {{d}}", f"top({xseq(zzz)}) = {{e}}", # whichever of xxx, yyy, zzz placed G top, also placed R bottom f"implies(max({xseq(xxx)}) == L, min({xseq(xxx)}) == P)", f"implies(max({xseq(yyy)}) == R, min({xseq(yyy)}) == U)", f"implies(max({xseq(zzz)}) == W, min({xseq(xxx)}) == Z)", ], # each judge allocates different marks to each girl # no girl had the same marks from different judges distinct=(joe, dan, xxx, yyy, zzz, ros, gwe, lin, ann, pam, tops), # Joe gave 9 to R; Dan gave 5 to A s2d=dict(E=9, F=5), # so the remaining digits are... digits=(0, 1, 2, 3, 4, 6, 7, 8), # P had no 0 from any judge ... d2i={ 0: pam + dan, 1: zzz, 2: yyy, 3: xxx, 4: joe, 6: joe + dan + xxx + yyy + tops, 7: joe + dan + xxx + zzz + tops, 8: joe + dan + yyy + zzz + tops, }, env=dict(top=top), ) # order of contestants girls = (A, G, L, P, R) = (0, 1, 2, 3, 4) # order girls by scores <s> (highest to lowest) order = lambda s: sorted(girls, key=(lambda g: s[g]), reverse=1) # check a candidate solution def check(s): # calcuate the total score for each contestant ms = list(list(s[x] for x in xs) for xs in (ann, gwe, lin, pam, ros)) pts = list(sum(m) for m in ms) if not seq_all_different(pts): return pos = order(pts) # assign xxx, yyy, zzz to B, S, T BST = (xxx, yyy, zzz) for (B, S, T) in subsets(BST, size=len, select="P"): # Tom gave 1 more point to A than Sam if s[S[A]] + 1 != s[T[A]]: continue # Brian placed the girls in the correct order if order(list(s[x] for x in B)) != pos: continue # output solution w = pos[0] ws = ms[w] js = "JD" + update("???", (BST.index(x) for x in (B, S, T)), "BST") ss = list(list(s[x] for x in xs) for xs in (joe, dan, xxx, yyy, zzz)) printf("winner = {w} {ws}\n A G L P R\n {m}\n", w="RGLAP"[w], ws=map2str(zip(js, ws)), m=map2str(zip(js, ss), sep="\n ", enc=""), ) # run the solver p.run(check=check)

LikeLike

]]>With a bit of analysis we can get a neater program (although it’s not really any faster – I timed it at 805ms, so it is still under a second).

Looking at the decompositions of 15 into 5 different numbers we see that the maximum possible mark in any decomposition is 9, and there is only one decomposition corresponding to this maximum, so Joe’s marks must be (in some order):

0, 1, 2, 3, 9

Similarly there is only one decomposition where the marks are consecutive, so this must be Dan’s:

1, 2, 3, 4, 5

So Dan gives out the only 5 (to Ann), and that is his maximum mark. This means we know where the 5 is, and don’t have to worry about tracking it in the code.

And for the three remaining judges they must give maximum marks of 6, 7, 8 and for each maximum there is only one decomposition for each that does not include 5. So the marks from the remaining judges (Sam, Tom and Brian) are (in some order):

0, 2, 3, 4, 6

0, 1, 3, 4, 7

0, 1, 2, 4, 8

The following program awards Joe’s 9 to Rose, and Dan’s 5 to Ann and fills out the remaining marks recursively.

from itertools import permutations from enigma import irange, diff, update, seq_all_different, map2str, printf # available scores (highest to lowest) # each sums to 15, and has a different max score = [ (9, 3, 2, 1, 0), # this is J's (5, 4, 3, 2, 1), # this is D's (8, 4, 2, 1, 0), (7, 4, 3, 1, 0), (6, 4, 3, 2, 0), ] # indices for BST (we don't know which is which, yet) BST = (2, 3, 4) # labels for the contestants (and number of contestants) girls = (A, G, L, P, R) = (0, 1, 2, 3, 4) N = 5 # check map <m>, up to row <k> def check(m, k, gs): mk = m[k] # P has no zeros if mk[P] == 0: return s = score[k] # if G is first, R must be last if mk[G] == s[0] and mk[R] != s[4]: return if k == 0: # check for Joe: G > L if not(mk[G] > mk[L]): return else: # no girl receives the same mark from 2 different judges if any(x in y for (x, y) in zip(mk, gs)): return # looks OK return 1 # associate with each score: m[score-index][contestant] = marks # <gs> = marks for each contestant so far # <fs> = contestants that have been awarded a judges highest mark def solve(m, k=0, gs=[[]] * N, fs=[]): # are we done? if k == N: yield (m, gs) else: # consider possibilities for score k mk = m[k] s = score[k] mx = max(s) # find assigned contestants cs = set(i for i in girls if mk[i] is None) # map the unassigned scores to the unassigned contestants for ms in permutations(diff(s, mk)): # make the updated row mk_ = update(mk, cs, ms) # check the highest score is to a new contestant f = mk_.index(mx) if f in fs: continue # update the map m_ = update(m, [(k, mk_)]) if not check(m_, k, gs): continue gs_ = list(xs + [x] for (xs, x) in zip(gs, mk_)) yield from solve(m_, k + 1, gs_, fs + [f]) # initial map m0 = list([None] * N for _ in irange(N)) m0[0][R] = 9 # J's highest score goes to R m0[1][A] = 5 # D's 5 goes to A # order girls by scores <s> (highest to lowest) order = lambda s: sorted(girls, key=(lambda g: s[g]), reverse=1) # consider possible completed maps for (m, gs) in solve(m0): # calculate the total score for each contestant pts = list(sum(g) for g in gs) if not seq_all_different(pts): continue # P finished ahead of R, but didn't win if not(pts[P] > pts[R] and pts[P] != max(pts)): continue pos = order(pts) # Brian placed the girls in the correct order for B in BST: if order(m[B]) != pos: continue # Tom gave 1 more point to A than Sam for (S, T) in permutations(diff(BST, [B])): if m[S][A] + 1 != m[T][A]: continue # output solution w = pos[0] ws = tuple(x[w] for x in m) js = update("JD???", (B, S, T), "BST") printf("winner = {w} {ws}\n A G L P R\n {m}\n", w="AGLPR"[w], ws=map2str(zip(js, ws)), m=map2str(zip(js, m), sep="\n ", enc=""), )

In my first program I wrote the [[ `assign()`

]] stuff to avoid permutations that would fail (which did make it run a bit faster). I didn’t bother for this program.

LikeLike

]]>from itertools import permutations # do all columns have different values in the multidimensional array <s> ? diffcols = lambda s: all(len(set(c)) == len(c) for c in list(zip(*s))) # are list a and b ordered the same? def same_order(a, b): s1 = sorted((x, i) for i, x in enumerate(a)) s2 = sorted((x, i) for i, x in enumerate(b)) return all(x[1] == y[1] for x, y in zip(s1, s2)) # decompose <t> into <k> increasing numbers, minimum <m> def decompose(t, k, m=0, ns=[]): if k == 1: if not(t < m): yield ns + [t] else: k_ = k - 1 for n in range(m, t + 1 - k_ * m): yield from decompose(t - n, k_, n + 1, ns + [n]) def check(s, mult, m, ind, imult): if ind in imult: # the judges chose a different girl to be top return False if sum(x[0] == 5 for x in mult) > 1: # Ann scored the only five return False if s[1] == m and s[4] != min(s): # the judge who put Gwen first put Rose last return False if not diffcols(mult): # no girl had two identical marks in her score return False return True judges = ["Joe", "Dan", "Sam", "Tom", "Brian"] girls = ["Agnes", "Gwen", "Linda", "Pam", "Rose"] scores = list(x for y in [list(permutations(s)) for s in decompose(15, 5)] for x in y) # Ann scored the only five. Pam had no zero in her score scores = [x for x in scores if 5 not in x[1:] and 9 not in x[:4] and x[3] != 0] scores9 = [x for x in scores if x[4] == 9] scoresnot9 = [(x, max(x)) for x in scores if x[4] != 9] for D in [x[0] for x in scoresnot9]: # Dan placed the girls as closely together as he could s = sorted(D) if not all(y == (x + 1) for (x, y) in list(zip(s, s[1:]))): continue mD = max(D) # the judge who put Gwen first put Rose last if D[1] == mD and D[4] != min(D): continue iD = D.index(mD) for J in scores9: # Joe gave maximum to Rose iJ = 4 # last position if iJ == iD: continue if J[1] < J[2]: continue # Joe placed Gwen above Linda if not diffcols([D, J]): continue # no identical marks per girl for S in [x[0] for x in scoresnot9 if x[1] not in {mD}]: mS = max(S) iS = S.index(mS) if not check(S, [D, J, S], mS, iS, {iD, iJ}): continue for T in [x[0] for x in scoresnot9 if x[1] not in {mD, mS}]: if T[0] != S[0] + 1: continue # Tom gave Ann one more than Sam mT = max(T) iT = T.index(mT) if not check(T, [D, J, S, T], mT, iT, {iD, iJ, iS}): continue for B in [x[0] for x in scoresnot9 if x[1] not in {mD, mS, mT}]: mB = max(B) iB = B.index(mB) if not check(B, [D, J, S, T, B], mB, iB, {iD, iJ, iS, iT}): continue sumcols = list(map(sum, zip(D, J, S, T, B))) if len(set(sumcols)) != 5: continue # totals must be different winning = max(sumcols) # Pam finished ahead of Rose but she didn’t win. if sumcols[3] < sumcols[4] or sumcols[3] == winning: continue # Brian succeeded in putting them all in their correct final order if not same_order(sumcols, B): continue # Ann scored the only five if [J[0], D[0], S[0], T[0], B[0]].count(5) != 1: continue ind = sumcols.index(winning) print(girls[ind], "has won") print(*[judges[i] + ": " + str(x[ind]) for i, x in enumerate([J, D, S, T, B])])

LikeLike

]]>It would be nice if an intermediate assignment could be added to SubstitutedExpression (for the sumcols list). It is now calculated multiple times.

from enigma import SubstitutedExpression ''' An Gw Li Pa Ro Joe=[A B C D E] Dan=[F G H I J] Sam=[K L M N O] Tom=[P Q R S T] Brian=[U V W X Y] ''' # column totals sumcols = lambda s: list(map(sum, zip(*s))) # are list a and b ordered the same? def same_order(a, b): s1 = sorted((x, i) for i, x in enumerate(a)) s2 = sorted((x, i) for i, x in enumerate(b)) return all(x[1] == y[1] for x, y in zip(s1, s2)) # the alphametic puzzle p = SubstitutedExpression( [ # row totals must be 15, choose RHS the variable with the most candidates "15 - (A + C + D + E) = B", "15 - (F + G + I + J) = H", "15 - (K + L + N + O) = M", "15 - (P + Q + S + T) = R", "15 - (U + V + X + Y) = W", # Joe placed Gwen above Linda. "B > C", # Dan placed the girls as closely together as he could # Dan's scores has to involve a 5 and may not include maximums 6, 7, 8 or 9 "sorted([G, H, I, J]) == [1, 2, 3, 4]", # the judge who put Gwen first put Rose last. "L != max([K, L, M, N, O]) or O == min([K, L, M, N, O])", "Q != max([P, Q, R, S, T]) or T == min([P, Q, R, S, T])", "V != max([U, V, W, X, Y]) or Y == min([U, V, W, X, Y])", # Tom gave Ann one more than Sam did. "K + 1 = P", # Pam finished ahead of Rose "sum([D, I, N, S, X]) > sum([E, J, O, T, Y])", # the judges each gave a different maximum mark "len(set([9, 5, max([K,L,M,N,O]), max([P,Q,R,S,T]), \ max([U,V,W,X,Y])])) == 5", # the judges chose a different girl to be top (Dan in col 0, Joe in col 4) "len(set([0, 4, \ [i for i, x in enumerate([K,L,M,N,O]) if x == max([K,L,M,N,O])][0],\ [i for i, x in enumerate([P,Q,R,S,T]) if x == max([P,Q,R,S,T])][0],\ [i for i, x in enumerate([U,V,W,X,Y]) if x == max([U,V,W,X,Y])][0]])) == 5", # the aggregate marks for each girl decided the issue "len(set(sumcols([[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]]))) == 5", # Pam didn't win "max(enumerate(sumcols([[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]])), key=(lambda x: x[1]))[0] != 3", # Brian succeeded in putting them all in their correct final order "same_order(sumcols([[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]]), [U,V,W,X,Y])", ], answer="[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],\ max(enumerate(sumcols([[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]])), key=(lambda x: x[1]))[0]", # no girl had two identical marks in her score distinct=("ABCDE", "FGHIJ", "KLMNO", "PQRST", "UVWXY", "AFKPU", "BGLQV", "CHMRW", "DINSX", "EJOTY" ), env=dict(sumcols=sumcols, same_order=same_order), # Pam had no zero in her score, Joe gave maximum to Rose (E = 9) # Ann scored the only five (in first column, F = 5)) # first and last column may not contain maximums 6, 7 and 8 s2d=dict(E=9, F=5), digits=(0, 1, 2, 3, 4, 6, 7, 8), # remaining digits d2i=dict([(0, "EFGHJBPDINSX")] + [(k, "EF") for k in range(1, 4)] + [(4, "EFJOTYK")] + [(k, "FGHIJEOTYAKPU") for k in range(6, 8)] + [(8, "FGHIJEOTYKAPUC")]), denest=1, # use denest=1 if not run with PyPy verbose=0 ) judges = ["Joe", "Dan", "Sam", "Tom", "Brian"] girls = ["Agnes", "Gwen", "Linda", "Pam", "Rose"] for (_, ans) in p.solve(): print(girls[ans[-1]], "has won") print(*[judges[i] + ": " + str(x[ans[-1]]) for i, x in enumerate(ans[:-1])])

LikeLike

]]>For 2x^2 = y^2 +/- 1, y/x = 1/1, 3/2, 7/5, 17/12, 41/29, 99/70, 239/169, 577/408, 1393/985 etc

x(k) = x(k-1) + y(k-1). and y(k) = 2 * x(k-1) + y(k-1).

169 = 13^2, and so there were 239^2 = 57121 men in the army.

As a check, 2 * 169^2 = 239^2 + 1

LikeLike

]]>Wow, 142 lines of code.

LikeLike

]]>And the recent Puzzle 102 in New Scientist. I call that plagiarism!

LikeLike

]]>But here is a Python program that solves the puzzle in 851ms.

The program groups together the possible sets of scores by the maximum number of marks given. And then selects scores that give a different maximum for each judge. We know Joe gives the absolute maximum, and Dan gives scores that as close together as possible. The distribution of scores is chosen such that no contestant receives the same number of marks from two judges. And then we check the other conditions as we go along.

**Run:** [ @replit ]

from enigma import irange, group, Accumulator, diff, update, flatten, tuples, seq_all_different, find, map2str, printf # labels for the contestants, and number of contestants (N) (A, G, L, P, R, N) = irange(0, 5) # decompose <t> into <k> increasing numbers, minimum <m> def decompose(t, k, m=0, ns=[]): if k == 1: if not(t < m): yield ns + [t] else: k_ = k - 1 for n in irange(m, t - k_ * m): yield from decompose(t - n, k_, n + 1, ns + [n]) # collect possible scores, by maximum marks given scores = group(decompose(15, N), by=max) # find scores containing the minimim possible difference (for Dan) r = Accumulator(fn=min, collect=1) for s in flatten(scores.values()): r.accumulate_data(max(b - a for (a, b) in tuples(s, 2)), s) mds = r.data # check a (partial) collection of scores def check(*ss): # consider the scores for the judges: # each judge places a different contestant first fs = set() for s in ss: f = s.index(max(s)) if f in fs: return # G first -> R last if f == G and s.index(min(s)) != R: return fs.add(f) # consider the scores for the contestants: for (i, s) in enumerate(zip(*ss)): # A scores the only 5 if i == A and len(s) == N and 5 not in s: return if i > A and 5 in s: return # P has no zeros if i == P and 0 in s: return # looks OK return 1 # empty scores s0 = [None] * N # generate scores with max marks in <mms> # <f5> current value of the 5 flag def generate(mms, f5): for (k, vs) in scores.items(): if k in mms: for ss in vs: # is a 5 involved? s5 = (5 in ss) if not(f5 + s5 > 1): yield (k, s5, ss) # assign marks <ms> to score <s> starting at index <k>, respecting <gs> def _assign(s, gs, ms, k=0): # are we done? if not ms: yield s elif s[k] is not None: yield from _assign(s, gs, ms, k + 1) else: # fill out index k for (i, v) in enumerate(ms): if v not in gs[k]: yield from _assign(update(s, [(k, v)]), gs, ms[:i] + ms[i + 1:], k + 1) # assign marks <ms>, respecting (i, v) pairs, and current sequences <ss> def assign(ms, ss, ivs=()): # find current marks per contestant gs = (list(zip(*ss)) if ss else [[]] * N) # assign any given values s = list(s0) for (i, v) in ivs: if v in gs[i]: return s[i] = v # and fill out remaining values yield from _assign(s, gs, diff(ms, s)) # select marks for Joe, must include the maximum possible score mxj = max(scores.keys()) for (_, j5, js) in generate([mxj], 0): # Joe gave the max score to R for J in assign(diff(js, [mxj]), [], [(R, mxj)]): # Joe gave G more marks than L if not(J[G] > J[L]): continue if not check(J): continue # select marks for Dan, as close together as possible for ds in mds: mxd = max(ds) if mxd == mxj: continue # is a 5 involved? d5 = (5 in ds) if j5 + d5 > 1: continue for D in assign(ds, [J]): if not check(J, D): continue # select marks for Sam kss = diff(scores.keys(), {mxj, mxd}) for (mxs, s5, ss) in generate(kss, j5 + d5): for S in assign(ss, [J, D]): if not check(J, D, S): continue # select marks for Tom kst = diff(kss, [mxs]) for (mxt, t5, ts) in generate(kst, j5 + d5 + s5): # Tom gave A 1 point more than Sam i = find(ts, S[A] + 1) if i == -1: continue for T in assign(diff(ts, [ts[i]]), [J, D, S], [(A, ts[i])]): if not check(J, D, S, T): continue # select marks for Brian ksb = diff(kst, [mxt]) for (mxb, b5, bs) in generate(ksb, j5 + d5 + s5 + t5): if d5 + j5 + s5 + t5 + b5 != 1: continue for B in assign(bs, [J, D, S, T]): if not check(J, D, S, T, B): continue # find the totals for the contestants pts = list(sum(s) for s in zip(J, D, S, T, B)) if not seq_all_different(pts): continue # P finished ahead of R, but didn't win if not(pts[P] > pts[R] and pts[P] != max(pts)): continue # Brian chose the correct order order = lambda s: sorted(irange(N), key=(lambda i: s[i]), reverse=1) pos = order(pts) if not(order(B) == pos): continue # output solution w = pos[0] ws = tuple(x[w] for x in [J, D, S, T, B]) printf("winner={w} {ws}\n A G L P R\n J={J}\n D={D}\n S={S}\n T={T}\n B={B}\n", w="AGLPR"[w], ws=map2str(zip("JDSTB", ws)), )

**Solution:** Linda won. Her marks were: Joe = 0, Dan = 3, Sam = 4, Tom = 2, Brian = 8.

The full scores are:

1st:Linda = 17; (J = 0, D = 3, S = 4, T = 2, B =8)

2nd:Pam = 16; (J = 3, D = 2, S = 1, T =6, B = 4)

3rd:Rose = 15; (J =9, D = 1, S = 0, T = 3, B = 2)

4th:Gwen = 14; (J = 2, D = 4, S =7, T = 0, B = 1)

5th:Ann = 13; (J = 1, D =5, S = 3, T = 4, B = 0)

LikeLike

]]>And if we drop the requirement that digits cannot be reused, we can see that any left prefix of a *polydivisible number* [ @wikipedia ] must also be polydivisible.

This program generates all possible polydivisible numbers in a given base (and with a given set of digits):

from enigma import irange, int2base, args, printf ds = args([10], 0, int) base = ds.pop(0) if not ds: ds = list(irange(base)) printf("[base = {base}; digits = {ds}]") (k, ns) = (1, list(ds)) while ns: (k_, ns_) = (k + 1, list()) for n in ns: # output current numbers printf("{k} -> {n}", n=int2base(n, base=base)) # can we extend this number? if n > 0: for d in ds: n_ = base * n + d if n_ % k_ == 0: ns_.append(n_) (k, ns) = (k_, ns_)

And we find that the longest base 10 polydivisible number, using the digits 0-9 (although 1 and 9 do not appear), has 25 digits:

3608528850368400786036725

In base 16 there are 3 maximal length (39-digit) polydivisible numbers:

34E4A468166CD8604EC0F8106AB4326098286CF;

AA44CE207C78FC30003C3CC0D8382E2078D07EF;

FAE06678C2E884607EB8B4E0B0A0F0603420342

LikeLike

]]>Thanks Frits: now I know about enumerate and reduce. As I’m getting the hang of looping over iterables I had forgotten it is still possible to loop with a counter, so I would probably choose your second approach to lines 16-19. I was trying to avoid using itertools once I realised I only needed to deal with pairs.

LikeLike

]]>Hi Tony,

A couple of recommendations:

index() is expensive so line 35 may be written to :

if sum([x * 10**(2 - i) for i, x in enumerate(vec[5:8])]) % 8 == 0 and \ sum([x * 10**(6 - i) for i, x in enumerate(vec[:7])]) % 7 == 0:

You can also use something like “dgts2nbr” as in puzzle [[ https://enigmaticcode.wordpress.com/2017/04/10/enigma-1120-assorted-numbers/ ]]

– line 16-19 can be rewritten to:

for j, B in enumerate(Bs): vec[1] = B vec[7] = Bs[1 - j]

or

for j in range(2): vec[1] = Bs[j] vec[7] = Bs[1 - j]

or

for (B, S) in permutations(Bs): vec[1] = B vec[7] = S

LikeLike

]]>See my comment on **Enigmatic Code** for the solution.

Here are the results for an *n*-digit (decimal) number, consisting of some arrangement of the digits *1..n*, such that the leftmost *k* digits are divisible by *k*, for *k = 1..n*:

[1] = 1

[1..2] = 12

[1..3] = 123; 321

[1..4] = none

[1..5] = none

[1..6] = 123654; 321654

[1..7] = none

[1..8] = 38165472

[1..9] = 381654729

[1..0] = 3816547290

And taking the final entry, a 10-digit number in base 10, using all 10 digits, such that the leftmost *k* digits are divisible by *k*, for *k = 1..10*; we can extend this to other bases:

base 2: 10

base 4: 1230; 3210

base 6: 143250; 543210

base 8: 32541670; 52347610; 56743210

base 10: 3816547290

base 12: none

base 14: 9C3A5476B812D0

base 16-30: none

(see: OEIS A11456 [ @oeis ]).

LikeLike

]]>I used a few shortcuts with the aim of minimising wasted iterations. Running my code on Replit takes about 3.5ms, compared with 1.13s for Frits’ sledgehammer approach. On the other hand, mine takes a few more lines!

Hope I manage to paste this in an appropriate format:

evens=list(range(2,9,2)) Xs=list(range(2,9,4)) odds=list(range(1,10,2)) odds.remove(5) #Exploit the fact that the three-digit and six-digit numbers must be divisible by 3, and that the 6-digit number can be expressed as the sum of ABC*1000 and XYZ, so the digits of both ABC and XYZ must sum separately to 3. Let the last three digits be RST. #For a four-digit number to be divisible by four, the last two digits must make a number divisible by four. Therefore if the third digit is odd, the fourth must be an odd multiple of 2 for X in Xs: vec=[None for i in range(9)] vec[4]=5#no zeros allowed, and the five-digit number divisible by 5 vec[3]=X Z=10-X#consequence of sum(X,Y,Z) divisible by three vec[5]=Z Bs=[i for i in evens if i not in vec]#remaining evens go to 2nd and 8th for B in Bs: vec[1]=B S=Bs[(Bs.index(B)-1)%2] vec[7]=S #Try each pair of odd numbers (other than 5) in positions 1 and 3, subject to requiring that ABC divisible by 3 (requiring A+B+C divisible by 3) for A in odds: Cs=odds.copy() Cs.remove(A) for C in Cs: if sum([A,B,C])%3==0: vec[0]=A vec[2]=C #Complementary odds then fall into positions 7 and 9 Rs=Cs.copy() Rs.remove(C) for R in Rs: vec[6]=R if sum([i*10**(6-vec.index(i)) for i in vec[:7]])%7==0 and sum([i*10**(7-vec.index(i)) for i in vec[:8]])%8==0: Ts=Rs.copy() Ts.remove(R) for T in Ts: vec[8]=T print("The telephone number is",sum([i*10**(8-vec.index(i)) for i in vec[:9]]))

LikeLike

]]>When the army size is doubled, the remaining number is 1 more than a square:

2(k^4) – 1 = n^2

We can consider possible values of *k* until *(2(k^4) – 1)* exceeds 1 million (at *k = 27*).

This Python program runs in 49ms.

**Run:** [ @replit ]

from enigma import irange, inf, is_square, printf # consider k values for k in irange(1, inf): n = 2 * pow(k, 4) - 1 if n > 1000000: break if is_square(n): printf("k={k} -> n={n}")

**Solution:** There were 57121 men in the army marching into battle.

Each company consisted of 13² = 169 men, which could be formed into a 13×13 square or a 1×169 line.

The 169 companies can then be formed into a 169×169 square = 28561 men in total.

The army size is doubled to 57122 = 239² + 1. And one of these is removed.

So, the army of 57121 men marched into battle as a 239×239 square.

LikeLike

]]>@Hugh: It does seem to be. I suppose it is hard to come up with over 3000 puzzles without some overlap. Or to check that there is no overlap!

LikeLike

]]>That one at least spared us George and Martha, who get dragged in again and again for no good reason.

LikeLike

]]>from itertools import permutations print(*["".join(x) for x in permutations("123456789") if all(int("".join(x[:i])) % i == 0 for i in range(2, 9))])

LikeLike

]]>Here’s a solution using the [[ `SubstitutedExpression`

]] solver from the **enigma.py** library. It runs in 96ms.

#!/usr/bin/env python3 -m enigma -r SubstitutedExpression --digits="1-9" "ABCDEFGH % 8 = 0" "ABCDEFG % 7 = 0" "ABCDEF % 6 = 0" "ABCDE % 5 = 0" "ABCD % 4 = 0" "ABC % 3 = 0" "AB % 2 = 0" --answer="ABCDEFGHI"

**Solution:** The number is 381654729.

LikeLike

]]>mistaken for 20 minutes, 58 and 106/143 seconds (20 and 140/143 minutes) after 2.

There’s a lot to be said for digital clocks, especially in the middle of the night!

LikeLike

]]>If we suppose the difference (in minutes) between the times is *d*, then the hour hand has moved a distance *d / 12* sectors (which is roughly 1/6 of the circle), and the minute hand has moved distance *d* sectors (and roughly twice – 1/6 times around the circle):

d = 120 – (d / 12)

d = 1440 / 13

d = 110 + 10/13

So the difference between the times is nearly 111 minutes, about what we would expect.

If we suppose the actual time is *m* minutes after 4 o’clock (i.e. *240 + m* minutes), then the minute hand will be showing *m* minutes, which is *m* sectors around the circle.

And at the mistaken time = *(240 + m – d)* the hour hand would be showing the same point:

(240 + m – d) / 12 = m

240 – d = 11m

m = (240 – d) / 11

m = 1680 / 143

m = 11 + 107/143

**Solution:** The exact time would be 11 + 107/143 minutes after 4 o’clock.

LikeLike

]]>I’ve added the [[ `SubstitutedExpression.split_sum()`

]] method to **enigma.py**, so you don’t have to split out the columns manually:

from enigma import SubstitutedExpression # split the sum into columns args = SubstitutedExpression.split_sum("DRANK + GLASS = LAGER") # none of the symbols in the original sum can be zero args.d2i.update((0, x) for x in args.symbols) # solve the sum, with additional condition: (G > D) SubstitutedExpression( args.exprs + ["{G} > {D}"], distinct=args.symbols, d2i=args.d2i, answer="{ALE}", template=args.template + " ({G} > {D}) ({ALE})", solution=args.symbols, ).run()

LikeLike

]]>2. AC = B – 1 mod 43

3. AB = 3C / 2 mod 43 with C even and C < 30

Combining Equations 1 and 3 leads to B^2 = 23 mod 43

Equations 1, 2 and 3 give ABC = A^2 = B^2 – B = 23 – B = 3C^2 / 2

One can also show that (A ^2 – 23)^2 = 23 mod 43 and (C^2 – 1)^2 = 15 mod 43

The easiest way forward is to start with (C^2 – 1)^2 = 15 mod 43

(C^2 – 1) = 12 mod 43 (C = 20 or 23), or (C^2 – 1) = 31 mod 43 with no value of C

And so C = 20

B = 23 – 3C^2 / 2 mod 43 = 25

A = BC mod 43 = 27

This would make for a simple and fast program. It can also be done by hand.

Starting with B^2 = 23 mod 43 gives:

B = 18, A^2 = 5 mod 43, with no possible values of A < 22

Also B = 43 – 18 = 25, when A^2 = -2 = 41 mod 43

..A = 16 gives 3C / 2 = 13 which is invalid (and C = 23 mod 43)

..A = 43 – 16 = 27 gives 3C / 2 = 30, ie C = 20.

Ann is 27, Bill is 25 and Cuthbert is 20.

The information that C is the youngest is redundant, although it does reduce the solution space for a brute force search.

This is an interesting teaser.

LikeLike

]]>from enigma import is_prime alldiff = lambda seq: len(seq) == len(set(seq)) # EIGHT is a cube for i in range(22, 47): s3 = str(i ** 3) E = int(s3[0]) I = int(s3[1]) T = int(s3[-1]) if T == 0: continue if E % 2 == 0: continue # E may not be even (due to PRIME) if not alldiff(s3): continue # NINE is a multiple of 9 # sumIE plus 2*N must be equal to k*9 (k is 1,2 or 3) sumIE = I + E N = (9 + 18 * (sumIE > 9) - sumIE) // 2 if sumIE % 2 else (18 - sumIE) // 2 if N == 0 or not alldiff(s3 + str(N)): continue # TRIPLE is a multiple of 3 LMPR = [x for x in range(10) if str(x) not in s3 + str(N)] candsM = [x for x in LMPR if (sumIE + T + sum(LMPR) - x) % 3 == 0] for M in candsM: LPR = [x for x in LMPR if x != M] for L in LPR: # PRIME may not be a multiple of 3 if (sumIE + M + sum(LPR) - L) % 3 == 0: continue PR = [x for x in LPR if x != L] # consider permutations of PR for (P, R) in zip(PR, PR[::-1]): if P == 0: continue PRIME = 10000*P + 1000*R + 100*I + 10*M + E if not is_prime(PRIME): continue print(f"TIME={T}{I}{M}{E}")

LikeLike

]]>`SubstitutedExpression`

]] solver from the The following run file executes in 122ms.

**Run:** [ @replit ]

#!/usr/bin/env python3 -m enigma -r SubstitutedExpression "TRIPLE % 3 = 0" "is_cube(EIGHT)" "NINE % 9 = 0" "is_prime(PRIME)" --answer="TIME"

**Solution:** **TIME** = 3901.

There are 4 solutions to the alphametic expressions, but the value for **TIME** is the same in all of them.

LikeLike

]]>… so it takes 1/4s to import numpy and 5ms for the rest of my program to run. Now I know why people bother with the math library.

LikeLike

]]>I found you can get away with approximating away the arctan term and I eliminated the need to set a tolerance by finding the valid V/L ratios closest to the target prime. However, my code takes 40 times longer to run than yours Jim so I have some other work to do!

LikeLike

]]>LikeLike

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

from enigma import irange, printf # H's age H = 43 # choose C's age (the youngest) for C in irange(10, H - 3, step=2): # choose B's age for B in irange(C + 1, H - 1): # calculate A's age (using A's statement) A = (B * C) % H if not(A > C and A != B): continue # check B's and C's statements if not(B - 1 == (A * C) % H): continue if not(3 * C == 2 * ((A * B) % H)): continue # output solution printf("A={A} B={B} C={C}")

**Solution:** Ann is 27. Bill is 25. Cuthbert is 20.

LikeLike

]]>As every schoolboy knows, a direct object goes in the accusative: porcus imitat gryphem (for example).

Better might be porci volent, though I’m not sure how far we can trust the googly translator.

LikeLike

]]>I think the derivations of the sea areas are:

Angler → Fischer

Back → Forth

Catter → Dogger

Dace → Sole

Eighties → Forties

Forkpoynt → Tyne

Greigh → Wight

Hegroom → Hebrides

Intarsia → Fair Isle [knitting]

LikeLike

]]>The area of white space must mean before the piglet has flown in. Then it’s a quick back-of-the-envelope calculation — no computer needed. Teasers are seldom so easy.

LikeLike

]]>@Robert: If we didn’t have the “equal area” constraint what would stop a solution like this:

V/L ≈ 31%

L = 125cm, V = 39cm

L = 13in, V = 4in

L = 16in, V = 5in

I think we need the additional constraint to eliminate such solutions (in this one the areas differ by about 16%).

But you don’t need to be completely accurate. A rough estimate will get you to the answer.

LikeLike

]]>LikeLike

]]>This Python routine calculates the **V**/**L** ratio where the areas are equal numerically, and then looks for permissible **L** values for the shield measured in centimetres.

We then look for integer **V** values within 10% of the “ideal” value, that have 2 different prime factors, and give a rounded percentage that is a prime.

We then look for multiple shields that have an **L** value less than 24 inches, and a corresponding **V** value that give the same rounded percentage.

This Python program runs in 50ms.

**Run:** [ @replit ]

from math import pi, sqrt, atan2 from enigma import find_value, intf, intc, Accumulator, irange, divf, divc, fdiv, group, item_from, is_prime, factor, iroot, powers, printf # calculate the difference between the upper and lower areas def areas(V, L): # lower area: triangular bit t = L - V T = 0.5 * t * t # lower area: semi-circular bit a = 0.5 * L b = a - V a2 = a * a b2 = b * b # solve: x^2 + y^2 = a^2, y = x - b for x, y r = sqrt(2 * a2 - b2) x = 0.5 * (r + b) y = 0.5 * (r - b) theta = pi - atan2(y, x) # calculate the areas upper = a * L lower = T + 0.5 * (a2 * theta + b * y) # return the difference in areas return fdiv(2 * abs(upper - lower), upper + lower) # find the V/L ratio when the white areas are equal r0 = find_value((lambda v: areas(v, 1)), 0, 0.25, 0.50).v printf("[r0 = {r0}]") # generate (V, L, rounded V/L percentage) values def generate(Ls): for L in Ls: # calculate exact V v = r0 * L # allow a tolerance of 10% for V in irange(intc(0.9 * v), intf(1.1 * v)): # calculate the rounded V/L percentage p = divf(200 * V + L, 2 * L) if not is_prime(p): continue # return values yield (V, L, p) # group (V, L, p) values by the percentage, for the shields measured in inches d = group(generate(irange(2, 23)), by=item_from("p", "V, L, p")) # find the shield measured in centimetres M = iroot(divc(100, 0.9 * r0), 3) for (V, L, p) in generate(powers(3, M, 3, step=2)): # V is an odd, 2-digit number if V % 2 == 0 or V < 10 or V > 99: continue # V should have 2 prime factors fs = factor(V) if not(len(fs) == 2 and len(set(fs)) == 2): continue # look for shields measured in inches vs = d.get(p) if not(vs) or len(vs) < 2: continue # output solution r = Accumulator(fn=min) inch = lambda x: fdiv(x, 2.54) printf("V/L ~ {p}%") printf("-> 1: L = {L}cm ({Li:.2f}in), V = {V}cm ({Vi:.2f}in) [error = {r:.6f}]", Li=inch(L), Vi=inch(V), r=areas(V, L)) r.accumulate(inch(L)) for (Vi, Li, _) in vs: printf("-> 2: L = {Li}in, V = {Vi}in [error = {r:.6f}]", r=areas(Vi, Li)) r.accumulate(Li) printf("=> min L = {r:.2g}in", r=r.value) printf()

**Solution:** The shortest L value is: 17 inches.

The measurements for each of the three shields are:

L= 125cm (49.2in),V= 51cm (20.1in)

L= 17in,V= 7in

L= 22in,V= 9in

V/L≈ 41%

The program calculates a more precise ratio for equal areas: **V**/**L** = 0.408083900721218.

In order to calculate the area of the lower white area on the shield, I started with a polynomial approximation based on the three values at *V=0, V=L/2, V=L*.

This is sufficient to find the answer (as, I suspect, are many other rough estimates).

But for the program above I came up with a a way of calculating the area exactly, based on the following diagram:

The shield is turned upside down and we place *x,y* axes through the centre of the semicircle.

Then using: *a = L/2, b = L/2 – V*

The semicircle is part of the circle: *x*² + *y*² = *a*²

The (now) upper line of the stripe is the line: *y = x – b*

And these meet at: *(x, y) = ((r + b)/2, (r – b)/2)*, where: *r = √(2a*² – *b*²*)*

So we can calculate the angle *θ = arctan(y, x)*

And the area of the yellow triangle = *(1/2)ab sin(θ) = by/2*

The orange region is then the area of the sector of the circle that subtends the angle *θ*, less the area of the yellow triangle.

And once we know the area of the orange region the area of the (formerly) lower region of the shield can be easily determined.

LikeLike

]]>@Frits

I do not recall the congruence sign from when I was first introduced to modulo arithmetic nearly 50 years ago. I have always used the equals sign (perhaps incorrectly) followed by mod n.

X = 0 mod 3 means that X is divisible by 3. And so, yes, X is divisible by 3, 7 and 11. As it also ends in 7 the solution space becomes very small. HTH

LikeLike

]]>I’m sure that an integer solution is intended, so no rounding is necessary.

The original estimate must therefore be an integer multiple of 7.

The result is even, so the original estimate (with its digits in reverse order) must start with 2: any larger value would yield a result with more digits.

The result is a multiple of 11, so the original estimate (using the same digits) must be too.

Admittedly, that still means a lot of trial & error if we don’t use a computer.

22/7 is a lousy approximation to pi! Much better is 355/113, but I think that can work only if the original estimate is allowed to have a leading zero.

LikeLike

]]>@John,

Maybe you used the congruent sign in your analysis and it was replaced by equal signs.

If so you might try the enclosure of text as mentioned in Teaser 2756.

If not the analysis is wrong as 0 mod 11 equals 0.

Please elaborate as I am not an expert in modular arithmetic (I gather X must be a multiple of 3, 7 and 11?).

LikeLike

]]>LikeLike

]]>A must beat B by 2 goals, and score an odd number of goals. That fixes the score in A vs B, and the remaining scores follow.

LikeLike

]]>and so 2e = 7a mod 10, and so a = 2 and e = 7

By digital roots, the sum of the digits in X = 0 mod 3. X = Y = 0 mod 3

X = 0 mod 7. Y = 0 mod 11, and so X = 0 mod 11

And so X = 231*M, Y = 726*M, and M = 7 mod 10

Or X = 1617 + 2310*N and Y = 5082 + 7260*N

By inspection X cannot have 2, 3 or 4 digits

70,000 < Y < 80,000 and so N = 9 or 10, which is invalid by inspection.

N = 9 gives X = 22407 and Y = 70422, which is valid.

If X 6 digits, there are only 14 possible values of N, ie 96 to 109, to check.

LikeLike

]]>We are told that there is enough information to determine the scores in each match (even though on the face of it it seems that we have not), so that fact it is possible to determine the scores means that there must be some ambiguous situations that cannot arise.

We have no information at all about C and D, so any solution that we find will be equally applicable if C and D’s labels are swapped over. But as we are told we *can* determine the scores in the matches, this must mean swapping the labels of C and D would have no effect on the scores, so C and D must have performed identically.

From this we can see that the C vs D match cannot be won by either of the teams, so it must be a draw, or not yet played. If it were a draw, then it could be 0-0, 1-1, 2-2, 3-3 and we don’t know which. So the C vs D match must be not yet played.

We know A and B have both played each of the other teams (as they have played 3 matches each), so all 6 matches involving A and B must have been played. And so the A vs B and A vs C matches must have identical scores, as must the B vs C and B vs D matches.

So we only need to calculate three scorelines: A vs B (win), A vs (C and D) (draw), B vs (C and D) (win).

This Python program runs in 48ms.

**Run:** [ @replit ]

from itertools import product from enigma import Football, printf # possible scores (no more than 7 goals scored per match) win = [ (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (3, 2), (4, 2), (5, 2), (4, 3), ] draw = [(0, 0), (1, 1), (2, 2), (3, 3)] # for computing goals for/against football = Football() # CD is not yet played CD = None # A has 2 draws (which must be AC = AD) and a win (which must be AB) # B has 2 wins (which must be BC = BD) for (AB, AX, BX) in product(win, draw, win): # check goals for/against if not(football.goals([AB, AX, AX], [0, 0, 0]) == (7, 5)): continue if not(football.goals([AB, BX, BX], [1, 0, 0]) == (5, 5)): continue # output solution printf("AB={AB} AC={AX} AD={AX} BC={BX} BD={BX} CD={CD}")

**Solution:** A vs B = 3-1; A vs C = 2-2; A vs D = 2-2; B vs C = 2-1; B vs D = 2-1; C vs D = not yet played.

LikeLike

]]>LikeLike

]]>If we round down (simply ignoring any fractional part) in the result, then we *can* get a solution to the original puzzle:

£185 × 22/7 = £581.43 ≈ £581

And we get the same result if we round to the nearest pound.

If we round up:

£29 × 22/7 = £91.14 ≈ £92

Raising the limit to £100,000 then we get the following additional solutions:

£22407 × 22/7 = £70422

Rounding to nearest:

£30769 × 22/7 = £96702.57 ≈ £96703

Which we also get if we round up, along with:

£29429 × 22/7 = £92491.14 ≈ £92492

So the only way we can get a unique solution with the revised upper limit is to assume that, before rounding, the new estimate was a whole number of pounds, and so no rounding was necessary. In this case only the pair £22407 → £70422 remain as a candidate solution, and this was the required answer.

This makes a manual solution easier (and a programmed solution a little faster), as we know the original estimate must be a multiple of 7.

The following Python program lets you play with various rounding methods.

**Run:** [ @replit ]

from enigma import irange, divf, divc, div, nreverse, printf # the new estimate is the original estimate multiplied by 22/7 # choose an appropriate function from the following #estimate = lambda x: divf(x * 22, 7) # rounded down #estimate = lambda x: divc(x * 22, 7) # rounded up #estimate = lambda x: divf(x * 44 + 7, 14) # rounded to nearest integer estimate = lambda x: div(x * 22, 7) # exact division # consider the original estimate x for x in irange(1, 99999): # multiply by 22/7 for new estimate n n = estimate(x) if n is None or n % 10 == 0: continue # new estimate is reverse of the original if nreverse(n) == x: printf("original {x} -> {n}")

**Solution:** The original estimate was: £22,407.

If the estimates had been constrained to be between £100,000 and £1,000,000 then there is a single solution whichever rounding method is chosen:

£246,477 × 22/7 = £774,642

LikeLike

]]>And so Sum(D) = – 2 mod 9 = 7 and Sum(W) = 4 * 7 = 28

The overdraft on the statement = 28 + 749 = £777

LikeLike

]]>@Hugh: Removing “AUG” from the list in my program finds 6 sets of dice that can make the remaining 11 months.

Also removing “FEB” has 30 sets of dice that can make the remaining 11 months, and 2 of the sets also have 4 vowels on one die.

LikeLike

]]>And one needs to be a mind-reader to deduce that “aggregate of the figures” means sum of the digits! Why can’t the setters of puzzles say what they mean?

LikeLike

]]>Same results as Jim (using same adjacency map).

from itertools import permutations regions = "HFADBGICE" d = dict() # coastal regions low/mid/high = 1/2/3 d["H"] = [2, 1, 2, 2, 2] d["F"] = [2, 2, 2, 3, 1] d["A"] = [3, 2, 2, 2, 2] d["D"] = [3, 2, 1, 2, 2] # offshore regions low/mid/high = 1/2/3 d["B"] = [2, 3, 2, 3, 2] d["G"] = [2, 2, 1, 2, 3] d["I"] = [1, 2, 2, 2, 2] d["C"] = [2, 3, 3, 2, 2] d["E"] = [2, 2, 2, 1, 1] # the adjacency map credit: Jim Randell adj = [ (1, 3, 6, 8), # 0 (0, 2, 4, 6), # 1 (1, 3, 4, 5), # 2 (0, 2, 5, 8), # 3 (1, 2, 6, 7), # 4 (2, 3, 7, 8), # 5 (0, 1, 4, 7), # 6 (4, 5, 6, 8), # 7 (0, 3, 5, 7), # 8 ] # generate "not bordering" dictionary nb = {r: [] for r in regions} # init dictionary for i, r1 in enumerate(regions): for j, r2 in enumerate(regions): if j == i: continue # check for opposite extremes if any(x * y == 3 for (x, y) in zip(d[r1], d[r2])): nb[r1] += [r2] # check which region can be the outer region outer = [r for r in regions[4:] if not any(x for x in nb[r] if x in regions[4:])] if len(outer) == 1: outer = outer[0] cdict = {outer: 7} else: outer = "" cdict = {} # check offshore regions (except outer) for p in permutations("BCEGI".replace(outer, "")): o2no = dict(zip(p, (4, 5, 6, 8))) # regions 4, 6 share a border if p[0] in nb[p[2]]: continue # regions 5, 8 share a border if p[1] in nb[p[3]]: continue # check coastal regions for q in permutations("ADFH"): c2no = dict(zip(q, (0, 1, 2, 3))) # F shares a border with A, also D and H have to share a border if q[0] + q[2] in {"AF", "FA", "DH", "HD"} : continue # A and D don't share a border if abs(c2no["A"] - c2no["D"]) != 2: continue # C shares a border with A if c2no["A"] not in adj[o2no["C"]]: continue # B doesn't share a border with A if c2no["A"] in adj[o2no["B"]]: continue # B doesn't share a border with H if c2no["H"] in adj[o2no["B"]]: continue # C doesn't share a border with H if c2no["H"] in adj[o2no["C"]]: continue # merge dictionaries (not using | as PyPy doesn't support it yet) comb = {**o2no, **c2no, **cdict} print("".join(k for k, v in comb.items() if v in adj[c2no["A"]]), end = ": ") print(", ".join(str(v) + "->" + k for k, v in sorted(comb.items(), key=lambda x: x[1])))

LikeLike

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

from collections import defaultdict from enigma import irange, dsum, div, printf # record number by digit sum ns = defaultdict(list) # consider increasing numbers for n in irange(1, 10000): ds = dsum(n) ns[ds].append(n) # if n is the total amount withdrawn # then the total amount deposited is less than this # and has a digit sum that is ds/4 dds = div(ds, 4) if dds is None: continue for t in ns[dds]: # balance (is in red, so is negative), after the cheque is paid in red = -(t - n + 749) # is the same as the digit sum of the withdrawals if red == ds: # output solution od = ds + 749 printf("withdrawn = {n}, deposited = {t}, red = {red}; overdraft = {od}")

**Solution:** The overdraft on the statement is £777.

There are 27 different *(widthrawn, deposited)* totals, but they all lead to an overdraft on the statement of £777.

LikeLike

]]>This program finds the 5 odd solutions given above, and larger ones (by default it outputs the first 10 it finds), by examining the two squares surrounding each odd power of 2:

from enigma import irange, inf, isqrt, div, first, arg, printf # generate candidate solutions def generate(): # consider increasing odd powers of 2 for i in irange(3, inf, step=2): p = 2 ** i # and the square below n = isqrt(p) # look for candidate solutions n2 = n * n n21 = n2 + 2 * n + 1 p2 = 2 * p # and check viability for x in [div(p2 + n2, 3), p2 - n2]: if x and x % 2 == 1 and x - n2 < n21 - x: printf("[{x} -> dist_p2 = {dp2}, dist_sq = {dsq}]", dp2=abs(p - x), dsq=x - n2) yield x for x in [div(p2 + n21, 3), p2 - n21]: if x and x % 2 == 1 and n21 - x < x - n2: printf("[{x} -> dist_p2 = {dp2}, dist_sq = {dsq}]", dp2=abs(p - x), dsq=n21 - x) yield x # find the first N solutions N = arg(10, 0, int) first(generate(), N)

% time python3.9 teaser/teaser3050d.py 10 [7 -> dist_p2 = 1, dist_sq = 2] [32775 -> dist_p2 = 7, dist_sq = 14] [134223231 -> dist_p2 = 5503, dist_sq = 11006] [2147479015 -> dist_p2 = 4633, dist_sq = 9266] [549756110751 -> dist_p2 = 296863, dist_sq = 593726] [8796091840375 -> dist_p2 = 1181833, dist_sq = 2363666] [140737493172567 -> dist_p2 = 4817239, dist_sq = 9634478] [2251799795854807 -> dist_p2 = 17830441, dist_sq = 35660882] [36028797113301975 -> dist_p2 = 94338007, dist_sq = 188676014] [576460752294331351 -> dist_p2 = 9092137, dist_sq = 18184274] python3.9 teaser/teaser3050d.py 10 0.03s user 0.01s system 92% cpu 0.048 total

LikeLike

]]>It took me a bit of doodling to come up with a map of the sea areas that satisfied the description. (I’ll put up a diagram of it later – [link]). But once that is done the program is relatively straightforward.

I don’t know that it is the only possible map though, but I assume the setter must have checked that the any viable map will give the same solution.

This Python program runs in 58ms.

**Run:** [ @replit ]

from enigma import subsets, update, peek, join, map2str, printf # suppose the coastal regions are: 0, 1, 2, 3 # and the offshore regions are: 4, 5, 6, 7, 8 # the adjacency map adj = [ (1, 3, 6, 8), # 0 (0, 2, 4, 6), # 1 (1, 3, 4, 5), # 2 (0, 2, 5, 8), # 3 (1, 2, 6, 7), # 4 (2, 3, 7, 8), # 5 (0, 1, 4, 7), # 6 (4, 5, 6, 8), # 7 (0, 3, 5, 7), # 8 ] # the forecast, region -> (dir, str, sea, wea, vis) forecast = dict( # coastal H=('E', 6, 'rough', 'drizzle', 'moderate'), F=('E', 7, 'rough', 'rain', 'good'), A=('NE', 7, 'rough', 'drizzle', 'moderate'), D=('NE', 7, 'smooth', 'drizzle', 'moderate'), # offshore B=('E', 8, 'rough', 'rain', 'moderate'), G=('E', 7, 'smooth', 'drizzle', 'poor'), I=('SE', 7, 'rough', 'drizzle', 'moderate'), C=('E', 8, 'high', 'drizzle', 'moderate'), E=('E', 7, 'rough', 'fair', 'good'), ) # extremes (in the same order) extreme = ({'SE', 'NE'}, {6, 8}, {'smooth', 'high'}, {'fair', 'rain'}, {'good', 'poor'}) # check forecast for <k> can be placed at region <r> without giving an extreme pair # return an updated map, or None def check(k, r, m): for (i, (v0, xs)) in enumerate(zip(forecast[k], extreme)): # is there an extreme in the forecast? if v0 in xs: # check the other extreme is not in any adjacent areas for r1 in adj[r]: s = m.get(r1, None) if s: v1 = forecast[s][i] if v1 != v0 and v1 in xs: return None # looks good, map r -> k return update(m, [(r, k)]) # attempt to map regions <rs> to keys <ks> def assign(rs, ks, m): for (r, k) in zip(rs, ks): m = check(k, r, m) if m is None: break return m # assign the coastal regions (0, 1, 2, 3) -> "ADFH" for ks in subsets("ADFH", size=len, select="P"): m0 = assign((0, 1, 2, 3), ks, dict()) if m0 is None: continue # assign the offshore regions (4, 5, 6, 7, 8) -> "CBEGI" for ks in subsets("CBEGI", size=len, select="P"): m = assign((4, 5, 6, 7, 8), ks, m0) if m is None: continue # which region is A? r = peek(k for (k, v) in m.items() if v == 'A') # output the regions adjacent to A adjA = sorted(m[x] for x in adj[r]) printf("adj[A] = {adjA} {m}", adjA=join(adjA, sep=",", enc="()"), m=map2str(m, arr="->", enc="[]"))

**Solution:** Angler adjoins Catter, Eighties, Forkpoynt, Hegroom.

The program finds 4 ways to assign the areas to regions of the map. But the map can be drawn to have two axes of symmetry, so there is essentially only one solution (and the other 3 found are just reflections of this).

My initial stetch, with the required connectivity looked like this:

Which I redrew using straight lines to get this map:

Which I think looks more like a map of sea areas. (The point where areas 2, 4, 5, 7 meet could be small outcrop with a lighthouse perhaps).

But it is topologically equivalent to the following diagram, which has horizontal and vertical symmetry:

The four solutions found by the program correspond to the reflections of a single solution:

LikeLike

]]>A B C S U V, D F J M O P, E L N R T Y

With two further cubes we can make the numbers 01 to 31:

0 1 2 3 4 5, 0 1 2 6 (which also serves as 9), 7, 8.

LikeLike

]]>LikeLike

]]>I’m sure I’ve come across a variant of this puzzle, possibly by Martin Gardner. I have a vague recollection that all the months could be done except AUG which wasn’t needed because it was during the university long vacation.

LikeLike

]]>@Hugh: It doesn’t let us get to DEC (we can’t fit a D in), but it means we don’t need a C to make OCT, so we free up a letter, which can be V, so we can get as far as NOV.

If we can also use an upside down A to make a V, then we can fit the D in and get to DEC.

I think there is scope for using specialised fonts that would allow some symbols to serve as multiple letters.

LikeLike

]]>LikeLike

]]>This Python program finds sets of dice that can make the maximum number of months, and then looks for sets that have a die with 4 vowels on. It runs in 1.18s.

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

from enigma import subsets, Accumulator, first, diff, join, printf vowels = set('AEIOU') months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] # merge the symbols <ss> to the dice <ds> def merge(ds, ss): rs = list() for (d, s) in zip(ds, ss): if s in d: rs.append(d) elif len(d) == 6: return else: rs.append(d + [s]) return rs # add words from <ws> to dice in <ds> # return (<dice>, <number-of-words-remaining>) def solve(ds, ws): # result so far yield (ds, len(ws)) if ws: # put letters for the next word on the dice for ss in subsets(ws[0], size=len, select="mP"): ds_ = merge(ds, ss) if ds_: yield from solve(ds_, ws[1:]) # start with the first month ds0 = list([x] for x in months[0]) # find dice with the fewest remaining words r = Accumulator(fn=min, collect=1) for (ds, n) in solve(ds0, months[1:]): r.accumulate_data(n, ds) # output result printf("months = {ms} [{n} sets of dice]", ms=join(first(months, len(months) - r.value), sep=" ", enc="[]"), n=len(r.data), ) # look for solutions with 4 vowels on one die fmt = lambda d: join(d, sep=" ", enc="[]") # format a die for ds in r.data: ds = sorted(sorted(d) for d in ds) # find dice with (exactly) 4 vowels vs = list(d for d in ds if len(vowels.intersection(d)) == 4) if not vs: continue printf("dice = {ds}", ds=join((fmt(d) for d in ds), sep=" ")) for d in vs: printf("-> {d} has 4 vowels; consonants = {c}", d=fmt(d), c=fmt(diff(d, vowels)))

**Solution:** (a) The last month you would be able to display is OCT. (b) The consonants on the die with 4 vowels are R and Y.

There are 108 sets of dice that can make JAN .. OCT, but only 4 of them have a die with 4 vowels on:

[A B L N S T] [A E O R U Y] [C F G J M P]

[A B C L N S] [A E O R U Y] [F G J M P T]

[A E O R U Y] [A F L N S T] [B C G J M P]

[A C F L N S] [A E O R U Y] [B G J M P T]

In each case the die with 4 vowels is: [A E O U] + [R Y]

We can see that the vowel I does not occur in any of the words, so the four vowels must be [A E O U], and setting the middle die in our initial set of dice (line 31) to have these 4 symbols on to start with finds just the four sets of dice given above, and runs in 401ms.

However, if we were to use lower case letters, then there are useful symbols that can be used for more than one letter:

b / q

d / p

n / u

And by replacing lines 3-4 with:

vowels = set('aeion') months = ['jan', 'feb', 'mar', 'adr', 'may', 'jnn', 'jnl', 'ang', 'sed', 'oct', 'nov', 'dec']

We find there are 10 sets of dice that can make all twelve months. (But none of them have a die containing 4 vowel symbols, so remove line 50 if you want to see them).

LikeLike

]]>From chemistry, CM + CMP = 200 – 66 = 134, with CM <= 22

And so CMP = 121 and CM = 13.

From maths, MP = 200 – 22 – CMP = 77

From physics, P = 230 – MP – CP – CMP = 32 – CP, and so CP <= 32

From all eight variables, 318 – CP + X = 440, ie X = 122 + CP

And so X = 144 and CP = 22

The answers follow directly.

LikeLike

]]># suppose p2 (2 ** p) is the power of 2 is the nearest power # let s^2 be the nearest square smaller equal to p2 # so that s^2 <= p2 <= (s + 1)^2 # example: 484 <= 512 <= 529 with s = 22 and p2 = 2 ** 9 # +--- 2s-1 ---+----- 2s + 1 ----+--- 2s + 3 --+ # | | | | # ---- (s - 1)^2 ---- s^2 --- p2 ---- (s + 1)^2 --- (s + 2)^2 --- # # even power exponents are also squares so ignore them for p in range(5, 21, 2): p2 = 2 ** p s = int(sqrt(p2)) s2 = s ** 2 # can x be between (s - 1)^2 and s^2 ? # then ds is maximal s - 1 and d2 is maximal (s - 1) / 2 minx = min(p2 - ((s - 1) // 2), s2) # make sure minx is odd and ... minx = minx + (minx % 2 == 0) # ... ds is not a multiple of 4 and minx = minx + 2 * (minx % 4 == 1) # if s2 is even then first entries have an odd ds if s2 % 2 == 0: minx += s - 2 # can x be between (s + 1)^2 and (s + 2)^2 ? # then ds is maximal s + 1 and d2 is maximal (s + 1) / 2 maxx = max(p2 + ((s + 1) // 2), s2 + 2 * s + 1) # if s2 is odd then last entries have an odd ds if s2 % 2 : maxx -= s + 1 # consider odd x's so that ds is not a multiple of 4 for x in range(minx, maxx + 1, 4): # determine nearest square y = int(sqrt(x)) y2 = y ** 2 ds = min(x - y2, y2 + 2 * y + 1 - x) # determine nearest power of 2 y2min1 = 2 ** (x.bit_length() - 1) d2 = min(2 * y2min1 - x, x - y2min1) # distance from x to power of 2 is always odd # so ds = 2 * d2 (and not d2 = 2 * ds) if ds == 2 * d2: print(f"number is {x}; distance to power of 2: {d2}; to square: {ds}") exit() # we only need the first

LikeLike

]]>Mon = m

Tue = f⋅m

Wed = (f^2)m

Thu = (f^3)m

Fri = (f^4)m

And the units of weight on each day are therefore:

Mon = m

Tue = f⋅m

Wed = 2(f^2)m

Thu = 2(f^3)m

Fri = 3(f^4)m

And on Monday rice of weight *w* cost 243, and on Friday the same weight cost 256:

Mon= w / m = 243

Fri= w / (3(f^4)m) = 256

w = 243m = 256×3(f^4)m

f^4 = 243/768 = 81/256

f = 3/4

So, how much did the rice cost on Tuesday?

Tue= w / f⋅m =Mon/ f

Tue= 243 / (3/4) = 324

**Solution:** On Tuesday the rice cost 324 cowries.

The costs for each day are:

Mon= 243

Tue= 324

Wed= 216

Thu= 288

Fri= 256

avg= 265.4

LikeLike

]]>My original version of `isqrt()`

used a float square root to provide an initial guess, and then applied Newton’s method until the result settled down to an integer. (See this comment on **Enigma 1759**).

But I switched that to a binary technique I found on Wikipedia [ @wikipedia ] which performed better for numbers that overflowed floats.

But looking at it again it turns out that that was because I was choosing a poor initial guess in the overflow case. It turns out you can do a much better guess using the fact that `int.bit_length()`

behaves roughly like `log2()`

.

So I think I’ll switch back to using Newton’s method for cases where `math.isqrt()`

is not available.

BTW: For a very fast implementation of `isqrt()`

check out `gmpy2.isqrt()`

.

LikeLike

]]>@Jim,

Talking about isqrt:

have you considered using math.sqrt in enigma.py’s isqrt() if math.isqrt is not available (like in an up-to-date PyPy) for numbers under a certain limit? See Brian Gladman’s number_theory.py.

LikeLike

]]>It can easily be seen that MA has to be 9 (square 121) and CF has to be 19 or 44 (squares 169 and 144).

LikeLike

]]>@Jim, the generated code is not efficient as possible as there is a loop for O and Q when CF is already known.

from enigma import SubstitutedExpression, is_square # the alphametic puzzle p = SubstitutedExpression( [ # X M P C MP MC PC MPC #XYZ + MA + PDE + CF + HI + KL + OQ + STU = 440 #"352 - XYZ - HI - STU = PDE", # substitute MA + KL = 22 and CF + OQ = 66 "352 - XYZ - HI - STU >= 0", # CF + KL + OQ + STU = 200 "112 + MA = STU", # substitute CF + KL = 88 - MA - OQ #XYZ + MA + CF + KL = 210 "188 - CF = XYZ", # MA + HI + KL + STU = 220 "198 - STU = HI", # substitute KL = 22 - MA # CF + OQ = 66 "66 - OQ = CF", # MA + KL = 22 "22 - MA = KL", "is_square(STU)", "is_square(XYZ)", ], answer="STU, HI, KL, MA, OQ, 352 - XYZ - HI - STU, CF, XYZ", d2i=dict([(0, "SX")] + [(k, "M") for k in range(3, 7)] + [(k, "MCO") for k in range(7, 10)]), distinct="", verbose=0 ) for (_, ans) in p.solve(): print("pupils failed in all three subjects =", ans[0]) print("Of those who passed in chemistry", ans[0] + ans[4], "also passed in physics")

LikeLike

]]>@Frits: I suppose it goes to show that different timing strategies produce different results.

For a piece of Python code that is only going to be executed once I think that a 3ms internal runtime is perfectly fine. This run time is dwarfed by the amount of additional time the Python interpreter takes getting ready to run it, so in my preferred measure of execution time both the programs give an external runtime of 50ms.

For code that is going to be executed thousands of times it may be worth shaving a few milliseconds off it, but code that is only executed once I’m not so sure.

I wanted my programs to show two different approaches. My first program defines functions that calculate the two distances and then compares them. But there are nearly 500,000 candidate numbers, so this could involve a lot of calculation.

The second program makes a set of markers, and then looks at numbers that are fixed distances from the markers, so it is essentially a searching strategy. In this case the distances involved in the solution aren’t very large, so the searching strategy wins.

LikeLike

]]>@Jim, I have tested with the Brian Gladman’s Timer function (which uses perf_counter_ns ) doing 200 runs (jra3 is my variation on your jra2 and jra4 a variation on jra2 the other way around (looking at distances from squares and then checking distances from powers of two)).

PyPy: jra1 1.579 ms jra2 2.629 ms jra3 146.700 us jra4 539.500 us Python 3.9.0: jra1 23.978 ms jra2 5.238 ms jra3 2.547 ms jra4 4.525 ms

I would have been surprised in jra4 was faster than jra3 as the distances from squares generates many elements (like 1900).

LikeLike

]]>@Frits: I thought that a “hybrid” approach of my two programs would probably be faster. Although my second program has an internal runtime of 2.74ms, so I didn’t think it was worth the extra code.

Interestingly when I timed the code you posted it ran slightly slower than my version (3.03ms). It might be faster the other way around (generating distances from squares and then calculating distances from powers of two), as I would assume that [[ `int.bit_length()`

]] is probably faster than [[ `math.isqrt()`

]] (certainly the code in **enigma.py** for [[ `isqrt()`

]] *starts* by calling [[ `int.bit_length()`

]], and then does more stuff. (Although if [[ `math.isqrt()`

]] is available it just uses that, which should be implemented by native code).

LikeLike

]]>