The published solution shows that this is how the setter intended it to be solved. I find the phrase “without overharvesting, the crop is able to replenish itself” confusing, because it suggests that harvesting prevents replenishing. My solution was identical to your fist solution (with the answer 15).

The original problem appears in Newton’s “Universal Arithmetick” (“Problem XI” on page 79) and is stated as follows:

*If the number of oxen a eat up the meadow b in the time c; and the number of oxen d eat up as good a piece of pasture e in the time f, and the grass grows uniformly; to find how many oxen will eat up the like pasture g in the time h.*

*Example: If 12 oxen eat up 3⅓ acres of pasture in 4 weeks, and 21 oxen eat up 10 acres of like pasture in 9 weeks; to find how many oxen will eat up 24* acres in 18 weeks.*

[*There is a typo in the text, it says “36 acres”, but it is clear from the solution that follows that it was meant to be 24.]

The empty square is always at the bottom right, so we can determine if a position is possible by counting the number of “inversions” (pairs of tiles that have the wrong ordering). If the number of inversions is even the pattern is possible. If it is odd the pattern is impossible.

The only wrinkle here is that there are two **S** tiles, and we don’t know (or care) which is which in the target positions. And the **S**‘s are separated by an odd number of other tiles. So any impossible position has a corresponding position which is possible, made by swapping the **S** tiles around. And vice versa.

Which means every position is possible, if we are allowed to choose the ordering of the **S**‘s.

**Solution:** All three arrangements are possible.

In the following Python program I differentiate the two **S**‘s by labelling the second one **Z**, and we then try target patterns with **S** and **Z** in both positions. Unsurprisingly we find that each target is possible in one of the orderings.

The program can also be used to output a command line suitable for the graphical solver I wrote for **Enigma 1444**.

**Run:** [ @replit ]

from enigma import (subsets, join, printf) # posible positions (with S/Z in both positions) pos = [ # initial position ("COST", "FUND", "ZALE", "BIG"), ("COZT", "FUND", "SALE", "BIG"), # target 1 ("COST", "FUND", "ZALE", "BGI"), ("COZT", "FUND", "SALE", "BGI"), # target 2 ("COST", "FUND", "ZALE", "GBI"), ("COZT", "FUND", "SALE", "GBI"), # target 3 ("COST", "FUND", "EZAL", "BIG"), ("COZT", "FUND", "ESAL", "BIG"), ] # map letters to numbers in start position (we use Z for the second S) val = dict((x, i) for (i, x) in enumerate(join(pos[0]), start=1)) # count the number of inversions for a position inversions = lambda ps: sum(val[x] > val[y] for (x, y) in subsets(ps, size=2)) # solve the positions for (i, ps) in enumerate(pos): n = inversions(join(ps)) printf("[{i}] {ps} -> {s}", ps=join(ps, sep=" ", enc="[]"), s=['possible', 'impossible'][n % 2]) # command line for the graphical solver #printf("-> python3 sliding-puzzle.py 4 4 {ps}", ps=join((val[x] for x in join(ps)), sep=" "))]]>

If we start with *a* units of crop, and each day *b* units grow, and we need *n* units to feed the population.

Then the crop will eventually be exhausted if *n > b*, but not if *n ≤ b*.

In this case the amount of crop on day *k* is:

d(k) = a + k(b − n)

So:

n = 40, d(20) = 0 ⇒ a + 20(b − 40) = 0

n = 20, d(60) = 0 ⇒ a + 60(b − 20) = 040b − 400 = 0

b = 10

a = 600

So we start with 600 units of crop, and it grows by 10 units per day.

With 40 people it is reducing by 30 per day, so lasts 20 days. With 20 people it is reducing by 10 per day, so lasts 60 days (but it is still being overharvested).

And the largest sustainable population is 10.

However, in this model, with a population of 10 (or fewer) it doesn’t matter how much crop you start with, the amount of new growth each day is always enough to feed the population that day. So they could start with no pre-existing crop at all.

]]>It is not even necessary to make the dictionary more efficient.

P = {3, 5, 7} P = {x for x in range(11, 100, 2) if all(x % p for p in P)} # dictionary of valid adjacent fields d = {i: {(set(str(n)) - {i}).pop() for n in range(10, 100) if i in str(n) and n not in P and 10 * (n % 10) + n // 10 not in P and all(n % k for k in [10, 11]) } for i in "123456789"} for i in "1234": c = str(10 - int(i)) # complement # can <a> go with all adjacent numbers of <b>? if all(all(x in d[a] for x in d[b] if x != a) for (a, b) in [(i, c), (c, i)]): print("answer:", (int(i), int(c)))]]>

**Run:** [ @replit ]

from enigma import (irange, subsets, primes, tuples, update, printf) # adjacent digits cannot form primes invalid = set() for n in primes.between(10, 99): (x, y) = divmod(n, 10) invalid.update([(x, y), (y, x)]) # check a sequence for invalid pairs check = lambda ds: not any(p in invalid for p in tuples(ds, 2, circular=1)) # if the sequence is (A B C D E F G H I) # we require A=1 and B < I to remove duplicates for ds in subsets(irange(2, 9), size=len, select="P", fn=list): if ds[0] > ds[-1]: continue # insert A=1 at index 0 ds.insert(0, 1) if not check(ds): continue # swap two numbers that sum to 10 and check again for (i, x) in enumerate(ds): if x > 4: continue y = 10 - x j = ds.index(y) if check(update(ds, [i, j], [y, x])): # output solution printf("({x}, {y}) in {ds}")

**Solution:** The interchangeable digits are: 2 and 8.

I had to make some (not unreasonable) assumptions to progress with a solution, and so I chose a scenario that seemed like a good fit to the puzzle text, and also would be easy to simulate. But I think the setter could have provided more details on how the situation was meant to work.

I was a bit disappointed that the answer didn’t come out to a whole number (or very close to a whole number). But I was pleased that the Golden Ratio turned up. And the simulations do demonstrate that this scenario fits the puzzle text (or at least my interpretation of it).

]]>I wonder how much this assumption affects the result. Also, depending on the time of the day when they harvest the food, would they be able to squeeze one more person? ]]>

Then, if we have *n* adults, the crop is stable if it produces enough growth each day to feed the *n* people (any more people and the size of the crop will gradually reduce).

(a − n)r ≥ a

n ≤ a(r − 1)/r

If, at the start of each day, enough crop is harvested to feed *n* people, we have the following amount of crop:

Day 0:d(0) = a

Day 1:d(1) = (a − n)r = ar − nr

Day 2:d(2) = (ar − nr − n)r = ar² − nr² − nr

Day 3:d(3) = (ar² − nr² − nr − n)r = ar³ − nr³ − nr² − nr

…

Dayd(k) = a.r^k − n(r + r² + r³ + … + r^k) = a.r^k − nr(r^k – 1)/(r − 1)k:

And we are told for *n = 40* the crop is exhausted after 20 days and for *n = 20* the crop is exhausted after 60 days.

Let’s suppose there is exactly enough to feed the population for the required time:

n = 40, d(20) = 0

n = 20, d(60) = 0a.r^20 = 40r(r^20 − 1)/(r − 1)

a.r^60 = 20r(r^60 − 1)/(r − 1)

Writing *x = r^20*, (and we expect *r* to be a little greater than 1, so we are looking for *r^20* greater than 1):

ax = 40r(x − 1)/(r − 1)

ax³ = 20r(x³ − 1)/(r − 1)n = a(r − 1)/r = 40(x − 1)/x

n = a(r − 1)/r = 20(x³ − 1)/x³

Equating values for *n*:

40(x − 1)/x = 20(x³ − 1)/x³

x³ − 2x² + 1 = 0

(x − 1)(x² − x − 1) = 0

Which gives solutions of:

x = 1

x = (1 ± √5)/2

Giving us a viable solution of:

x = (1 + √5) / 2 (i.e. the Golden Ratio ≈ 1.618… )

We can then calculate *n*:

n = 40(x − 1)/x

n = 20(3 − √5) ≈ 15.279…

So :

**Solution:** The maximum sustainable population is 15 adults.

We can also calculate values for *r* and *a*:

r^20 = (1 + √5) / 2

r ≈ 1.02435…

a ≈ 642.677…

Here is a program to simulate the scenario:

**Run:** [ @replit ]

from enigma import (sqrt, root, fdiv, intf, printf) # x = r^20 is the golden ratio x = 0.5 + sqrt(5, 4) r = root(x, 20) a = fdiv(40 * r * (x - 1), x * (r - 1)) n = fdiv(a * (r - 1), r) printf("[x = {x}; r = {r}; a = {a}; n = {n}]") printf() # run a simulation with <n> adults, and <a> crop with rate <r> def run(n, a, r): printf("[n = {n}; a = {a}]") k = 0 while True: printf("day {k}: crop = {a:.2f}") # extract the food for n adults a_ = a - n if not(a_ > -1): printf("crop exhuasted on day {k}") break # grow the remaining crop a_ *= r if a_ > a and k > 7: printf("population size {n} is sustainable") break a = a_ k += 1 printf() # run some simulations run(40, a, r) run(20, a, r) run(intf(n), a, r)

And here is a graph of the amount of crop remaining for the first 50 days for populations of 40, 20, 16, 15:

]]>The following Python program runs in 62ms.

**Run:** [ @replit ]

from enigma import (divisors, cproduct, uniq, printf) # odd divisors of x odds = lambda x: (d for d in divisors(x) if d % 2 == 1) # successors of n succ = lambda n: uniq(a * b for (a, b) in cproduct([odds(n - 1), odds(n + 1)])) # find shortest path from <src> to <tgt> def solve(src, tgt): s = (src,) if src == tgt: return s (ss, seen) = ({s}, set()) while ss: seen.update(ss) ss_ = set() for s in ss: for x in succ(s[-1]): s_ = s + (x,) if x == tgt: return s_ if s_ not in seen: ss_.add(s_) ss = ss_ for (src, tgt) in [(13, 19), (19, 13)]: s = solve(src, tgt) printf("{s} [{n} steps]", s=list(s), n=len(s) - 1)

**Solution:** (13, 21, 55, 189, 19) is a path from 13 to 19 in 4 steps. (19, 45, 253, 1143, 13) is a path from 19 to 13 in 4 steps.

13 → (3×7) = 21

21 → (5×11) = 55

55 → (27×7) = 189

189 → (1×19) = 19

]]>19 → (9×5) = 45

45 → (11×23) = 253

253 → (9×127) = 1143

1143 → (1×13) = 13

This is not a general solution. As 40 can’t be the sum of four remaining prime numbers the answer is hardcoded as the product of two prime numbers.

]]>I had to make a dummy loop to set the ps variable in order to avoid

SyntaxError: assignment expression cannot be used in a comprehension iterable expression

print(*[p * (113 - sum(ps) - p) for _ in [1] if (ps := set(x for m in [418, 651] for x in range(2, m) if m % x == 0 and all(x % i for i in range(2, x)))) for p in range(3, (113 - sum(ps)) // 2, 2) if all(y not in ps and all(y % i for i in range(2, y)) for y in [p, 113 - sum(ps) - p]) ])]]>

The following Python program runs in 276ms.

**Run:** [ @replit ]

from enigma import (tuples, update, printf) # generate successors of s def succ(s): n = len(s) for (i, ts) in enumerate(tuples(s, 3, circular=1), start=1): # consider replacements for r in "RBY": if r not in ts: # replace index i with r yield update(s, [(i % n, r)]) # starting from <src> look for successors in <tgts> def solve(src, tgts): assert src not in tgts (ss, seen, k) = (set([src]), set(), 1) while tgts and ss: seen.update(ss) ss_ = set() for s in ss: for s_ in succ(s): if s_ in tgts: printf("({t}) in {k} steps", t=tgts[s_]) del tgts[s_] if s_ not in seen: ss_.add(s_) (ss, k) = (ss_, k + 1) for t in tgts.values(): printf("({t}) not possible") # construct the source and target strings prep = lambda x: x.replace(" ", "") src = prep("RBY RBY RBY RBY RBY BRY RBY RBY RBY") tgts = dict(zip((prep(x) for x in [ "RBY RBY RBY RBY RBY RBY RBY RBY RBY", "RYR BYR BYR BYR BYR BRY RBY RBY RBY", "RBY RBY RBY RBY RBY BRY BRY RBY RBY", ]), "123")) # solve the puzzle solve(src, tgts)

**Solution:** (ii) can be produced. (i) and (iii) cannot.

Below is the picture with all solutions (if it doesn’t load, just follow the link):

]]>Hmm.. so it does work. Weird.

]]>Here is what I get:

void foo(int bar) {}

It does work when I’m trying to post comment in my own sandbox wordpress site.

]]> one line cannot be created by merging multiple sections (that would probably fall under “devious rule bending”)

all lines are either vertical, horizontal or parallel to the grid’s diagonals

#include <iostream> #include <ctime> using namespace std; // directions const int Empty = 0; const int U = 1; const int UR = 2; const int R = 4; const int DR = 8; const int D = 16; const int DL = 32; const int L = 64; const int UL = 128; // Puzzle parameters const int MAX_LINES = 10; const int ROWS = 6; const int COLS = 6; int grid[ROWS][COLS] = {Empty}; int trace[(ROWS-1)*(COLS-1)*MAX_LINES] = {Empty}; int traceIndex = 0; int startRow; int startCol; int counter = 0; void printTrace() { cout << "S" << startRow << "," << startCol; for (int i = 0; i < traceIndex; i++) { switch (trace[i]) { case U: cout << " U"; break; case UR: cout << " UR"; break; case R: cout << " R"; break; case DR: cout << " DR"; break; case D: cout << " D"; break; case DL: cout << " DL"; break; case L: cout << " L"; break; case UL: cout << " UL"; break; default: cout << " err"; break; } } cout << endl; } // recursive search starting from row/column void search(int row, int col, int outDir, int newRow, int newCol, int inDir, int lineCount, int dotCount) { if (newRow >= ROWS || newRow < 0) return; if (newCol >= COLS || newCol < 0) return; // check if new line section in this direction can be added int current = grid[row][col]; int next = grid[newRow][newCol]; // check if already have a line in this direction going out of the current dot if (current & outDir) return; // check if already have a line in the opposite direction going out of the next dot if (next & inDir) return; // check if new section will start a new line if (traceIndex == 0 || trace[traceIndex-1] != outDir) lineCount++; if (lineCount > MAX_LINES) return; // count new dots if (next == Empty) dotCount++; // add new line section grid[row][col] = current | outDir; grid[newRow][newCol] = next | inDir; trace[traceIndex++] = outDir; if (dotCount == ROWS*COLS) { // Bingo, all dots reached, print the trace printTrace(); } else { if (lineCount == MAX_LINES) { // optimization for the last line - continue current direction switch (outDir) { case U: search(newRow, newCol, U, newRow - 1, newCol, D, lineCount, dotCount); break; case UR: search(newRow, newCol, UR, newRow - 1, newCol + 1, DL, lineCount, dotCount); break; case R: search(newRow, newCol, R, newRow, newCol + 1, L, lineCount, dotCount); break; case DR: search(newRow, newCol, DR, newRow + 1, newCol + 1, UL, lineCount, dotCount); break; case D: search(newRow, newCol, D, newRow + 1, newCol, U, lineCount, dotCount); break; case DL: search(newRow, newCol, DL, newRow + 1, newCol - 1, UR, lineCount, dotCount); break; case L: search(newRow, newCol, L, newRow, newCol - 1, R, lineCount, dotCount); break; case UL: search(newRow, newCol, UL, newRow - 1, newCol - 1, DR, lineCount, dotCount); break; } } else { // not the last line - can go in (almost) any direction if (inDir != U) search(newRow, newCol, U, newRow - 1, newCol, D, lineCount, dotCount); if (inDir != UR) search(newRow, newCol, UR, newRow - 1, newCol + 1, DL, lineCount, dotCount); if (inDir != R) search(newRow, newCol, R, newRow, newCol + 1, L, lineCount, dotCount); if (inDir != DR) search(newRow, newCol, DR, newRow + 1, newCol + 1, UL, lineCount, dotCount); if (inDir != D) search(newRow, newCol, D, newRow + 1, newCol, U, lineCount, dotCount); if (inDir != DL) search(newRow, newCol, DL, newRow + 1, newCol - 1, UR, lineCount, dotCount); if (inDir != L) search(newRow, newCol, L, newRow, newCol - 1, R, lineCount, dotCount); if (inDir != UL) search(newRow, newCol, UL, newRow - 1, newCol - 1, DR, lineCount, dotCount); } } grid[row][col] = current; grid[newRow][newCol] = next; traceIndex--; } void startSearch(int row, int col) { cout << endl << "Starting at " << row << " " << col << endl; startRow = row; startCol = col; search(row, col, U, row - 1, col, D, 0, 1); search(row, col, UR, row - 1, col + 1, DL, 0, 1); search(row, col, R, row, col + 1, L, 0, 1); search(row, col, DR, row + 1, col + 1, UL, 0, 1); if (row != col) { // exclude some symmetrical variants search(row, col, D, row + 1, col, U, 0, 1); search(row, col, DL, row + 1, col - 1, UR, 0, 1); search(row, col, L, row, col - 1, R, 0, 1); } search(row, col, UL, row - 1, col - 1, DR, 0, 1); } int main() { for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++){ // Use only half of a quadrant for starting points // to exclude most rotational/symmetrical repeats. if (col < (COLS + 1)/2 && row < (ROWS + 1)/2 && (col >= row)) { startSearch(row, col); } } } return 0; }

It runs for about 1 hour on my PC and produces 151 routes.

Another version of this program reduces the number of variants to 36 by first extending the first and the last line as much as possible, and then filtering for all possible permutations like rotations, reflections and reversions.

The final list is as follows:

S0,0 R R R R R D D D D D L L L L L U U U U R R R R R DL DL DL U U U DR DR DR L L L L U U U U S0,0 R R R R R D D D D D L L L L L U U U U R R R R R DL DL DL DL U U U U U DR DR DR DR L L L U U U U S0,0 R R R R R D D D D D L L L L L U U U U R R R R R DL DL DL DL U U U R R R D D D UL UL UL UL S0,0 R R R R R D D D D D L L L L L U U U U DR DR DR DR U U U L L L D D D UR UR UR UR L L L L L S0,0 R R R R R D D D D D L L L L L U U U U R R R R R DL DL DL DL U U U DR DR DR U U U L L L L S0,0 R R R R R D D D D D L L L L L U U U R R R R D D D UL UL UL D D D UR UR UR UR L L L L L S0,0 R R R R R D D D D D L L L L L U U U U R R R R D D D D UL UL UL R R R DL DL DL U U U U U S0,0 R R R R R D D D D D L L L L L U U U U R R R R D D D D UL UL UL D D D UR UR UR L L L L S0,0 R R R R R D D D D D L L L L L U U U U DR DR DR DR U U U U L L L D D D D UR UR UR L L L L S0,0 R R R R R D D D D D L L L L L U U U R R R R D D D UL UL UL UL R R R R R DL DL DL DL U U U U U S0,0 R R R R R D D D D D L L L L L U U U R R R R DL DL DL U U U DR DR DR U U U U L L L L S0,0 R R R R R D D D D D L L L L L U U U R R R R DL DL DL U U U U R R R D D D D UL UL UL UL S0,0 R R R R R D D D D D L L L L L U U U U R R R R D D D L L L L UR UR UR D D D UL UL UL S0,0 R R R R R D D D D D L L L L L U U U U DR DR DR U U U DL DL DL R R R R U U U L L L L S0,0 R R R R R D D D D D L L L L L U U U U R R R D D D L L L UR UR UR UR D D D D D UL UL UL UL S0,0 R R R R R D D D D D L L L L L U U U U DR DR DR DR U U U U U DL DL DL DL R R R U U U L L L S0,0 R R R R R D D D D D UL UL UL UL UL D D D D D R R R R UL UL UL D D D UR UR UR UR L L L DR DR DR S0,0 DR DR DR DR DR U U U U U L L L L L D D D D D R R R R UL UL UL D D D UR UR UR UR L L L DR DR DR S0,0 R R R R R D D D D UL UL UL R R R DL DL DL DL U U U DR DR DR L L L L U U U U U DR DR DR DR DR S0,0 R R R R R D D D D D UL UL UL UL UL D D D D D R R R R UL UL UL UL R R R R R DL DL DL DL U U U U U DR DR DR DR S0,0 DR DR DR DR DR U U U U U L L L L L D D D D D R R R R UL UL UL UL R R R R R DL DL DL DL U U U U U DR DR DR DR S0,1 D D D D D UR UR UR UR L L L L L DR DR DR DR L L L L U U U U U DR DR DR DR DR U U U U U L L L L DR DR DR DR S0,0 R R R R R D D D D UL UL UL UL D D D D D UR UR UR UR L L L L L DR DR DR DR L L L L U U U U U DR DR DR DR DR S0,1 DR DR DR DR U U U U L L L L D D D D D UR UR UR UR L L L L L DR DR DR DR L L L L U U U U U DR DR DR DR DR S0,0 R R R R R D D D D UL UL UL R R R DL DL DL DL U U U U DR DR DR DR L L L L L U U U U DR DR DR DR S0,0 R R R R R D D D D UL UL UL UL D D D D D UR UR UR UR L L L L DR DR DR DR L L L L L U U U U DR DR DR DR S0,1 DR DR DR DR U U U U L L L L L D D D D D R R R R UL UL UL D D D UR UR UR UR L L L L DR DR DR DR S0,1 DR DR DR DR U U U U L L L L L D D D D D R R R R R UL UL UL UL R R R R DL DL DL DL U U U DR DR DR S0,1 DR DR DR DR U U U U L L L L L D D D D D R R R R R UL UL UL UL D D D D UR UR UR UR L L L L L DR DR DR DR S0,1 DR DR DR DR U U U U L L L L L D D D D D R R R R UL UL UL UL R R R R R DL DL DL DL U U U U DR DR DR DR S0,1 DR DR DR DR U U U U L L L L L DR DR DR DR DR L L L L L U U U U R R R R R DL DL DL DL U U U DR DR DR S0,1 D D D D D UR UR UR UR L L L DR DR DR U U U U L L L L L DR DR DR DR DR L L L L L U U U U DR DR DR DR S0,0 R R R DL DL DL U U U DR DR DR DR L L L L UR UR UR UR D D D D D L L L L UR UR UR UR UR D D D D D S0,0 D D D UR UR UR L L L DR DR DR DR L L L L UR UR UR UR D D D D D L L L L UR UR UR UR UR D D D D D S0,0 R R R DL DL DL U U U DR DR DR DR DR L L L L L UR UR UR UR UR D D D D L L L L L UR UR UR UR D D D D D S0,0 D D D UR UR UR L L L DR DR DR DR DR L L L L L UR UR UR UR UR D D D D L L L L L UR UR UR UR D D D D D

Many of these are still quite similar. This is particularly the case with routes which contain loops: #3, #13, #15, #17, #21 differ from #4, #14, #16, #18 and #22 only by taking different direction along the loop.

I also created a little python script for drawing the routes. It requires Zelle’s graphics module (which uses Tkinter package).

from graphics import * offsetX = 0 offsetY = 20 marginX = 20 marginY = 20 stepX = 100 stepY = 100 DOT_SMALL_RADIUS = 2 DOT_BIG_RADIUS = 10 START_DOT_RADIUS = 7 rowsNum = 6 colsNum = 6 width = offsetX + marginX + (colsNum - 1) * stepX + marginX height = offsetY + marginY + (rowsNum - 1) * stepY + marginY plotterWin = None plotterLines = [] plotterStartDot = None plotterText = None plotterRow = 0 plotterCol = 0 START_RED = 0xff START_GREEN = 0x00 START_BLUE = 0x00 END_RED = 0x00 END_GREEN = 0x00 END_BLUE = 0xff def plotterColor(i, n): r = START_RED + i * (END_RED - START_RED) / (n - 1) g = START_GREEN + i * (END_GREEN - START_GREEN) / (n - 1) b = START_BLUE + i * (END_BLUE - START_BLUE) / (n - 1) return color_rgb(int(r), int(g), int(b)) def plotterInit(win): global plotterWin plotterWin = win def plotterSetText(text): global plotterMessage, plotterText if plotterText != None : plotterMessage.undraw() plotterMessage = Text(Point(plotterWin.getWidth()/2, offsetY / 2), text) plotterText = text plotterMessage.draw(plotterWin) def plotterReset(): global plotterRow, plotterCol, plotterStartDot, plotterLines for line in plotterLines: line.undraw() plotterLines.clear() if plotterStartDot != None : plotterStartDot.undraw() def plotterPoint(row, col): return Point(col * stepX + offsetX + marginX, row * stepY + offsetY + marginY) def plotterSetStart(row, col): global plotterRow, plotterCol, plotterStartDot plotterRow = row plotterCol = col plotterStartDot = Circle(plotterPoint(row, col), START_DOT_RADIUS) plotterStartDot.setFill("red") plotterStartDot.draw(plotterWin) def plotterDrawMove(dRow, dCol, num): global plotterRow, plotterCol line = Line(plotterPoint(plotterRow, plotterCol), plotterPoint(plotterRow + dRow, plotterCol + dCol)) line.setWidth(3) line.setArrow('last') line.setFill(plotterColor(num, 10)) line.draw(plotterWin) plotterLines.append(line) plotterRow += dRow plotterCol += dCol def plotterDrawNet(startRow, startCol): for row in range(rowsNum): for col in range(colsNum): dot = Circle(plotterPoint(row, col), DOT_BIG_RADIUS) dot.setFill("yellow") dot.draw(plotterWin) dot = Circle(plotterPoint(row, col), DOT_SMALL_RADIUS) dot.setFill("red") dot.draw(plotterWin) def getRoutesNum(fileName): num = 0 routes_file = open(fileName, "r") for route in routes_file : if (not route) or (route[0]=='#') : continue num+=1 return num def drawRouteN(fileName, n): plotterReset() routes_file = open(fileName, "r") num = 0; for route in routes_file : if (not route) or (route[0]=='#') : continue num+=1 if (num == n) : break if (num != n or n == 0) : plotterSetText(f"Route {n} not found") return dirs = route.split() plotterReset() plotterSetText(f"Route {num}") lastDir = "" dRow = 0 dCol = 0 lineNum = 0 for dir in dirs: if (dir != lastDir): if dRow != 0 or dCol != 0: plotterDrawMove(dRow, dCol, lineNum) lineNum += 1 lastDir = dir dRow = 0 dCol = 0 if (dir[0] == "S") : coords = dir[1:].split(',') dRow = 0 dCol = 0 plotterSetStart(int(coords[0]), int(coords[1])) if (dir == "R") : dCol += 1 if (dir == "UR") : dRow -= 1; dCol += 1; if (dir == "U") : dRow -= 1; if (dir == "UL") : dRow -= 1; dCol -= 1; if (dir == "L") : dCol -= 1; if (dir == "DL") : dRow += 1; dCol -= 1; if (dir == "D") : dRow += 1; if (dir == "DR") : dRow += 1; dCol += 1; if dRow != 0 or dCol != 0: plotterDrawMove(dRow, dCol, lineNum) def main(): win = GraphWin("10 line drawing challenge", width, height) win.setCoords(0, height, width, 0) plotterInit(win) plotterDrawNet(0, 0) routesNum = getRoutesNum("routes.txt"); if (routesNum < 1) : plotterSetText('No routes found. Press any key to quit.') k = win.getKey() else : n = 1 drawRouteN("routes.txt", n) while True: k = win.getKey() if k == 'Right' or k == 'space' or k == 'KP_Enter' or k == 'Return': if (n < routesNum) : n+=1 drawRouteN("routes.txt", n) elif k == 'Left': if (n > 1) : n-=1 drawRouteN("routes.txt", n) elif k == 'period' or k == 'Escape': break win.close() main()

Below is a picture of all 36 possible routes.

]]>We know two of the heads started with 418 and 651, which have prime decompositions of (2, 11, 19) and (3, 7, 31), which give a total of 73.

So we need to find a set of unused primes that sum to 40, and their product gives the value on the third head.

This Python program runs in 63ms. (Internal runtime is 175µs).

**Run:** [ @replit ]

from enigma import (primes, factor, multiply, printf) # the numbers on two of the initial heads ns = [418, 651] # the total of the (primes) on the final heads t = 113 # decompose total <t> into different primes, not in <xs>, that sum to <t> def decompose(t, xs, ps=[]): if t == 0: yield ps else: n = (ps[-1] + 1 if ps else 2) for p in primes.irange(n, t): if p not in xs: yield from decompose(t - p, xs, ps + [p]) # collect prime factors from given numbers xs = set() for n in ns: fs = factor(n) assert not xs.intersection(fs) printf("{n} = {fs}") xs.update(fs) # solve for the remaining value n = t - sum(xs) primes.expand(n) for fs in decompose(n, xs): p = multiply(fs) printf("-> {p} = {fs}")

**Solution:** The third number was 391.

The prime decomposition of 391 is (17, 23).

]]>`SubstitutedExpression`

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

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression "AGES + EVERY + YEAR + BY + AS + NEARLY + A + LARGE + AGE + AS = AVERAGE" "2 * R = Y" --answer="AGES"

**Solution:** **AGES** = 1765.

It turns out the hint that **Y** is twice **R** is not necessary (but it is handy for speeding things up).

Another way to speed it up (with or without the hint) is to use the [[ `split_sum`

]] method of the [[ `SubstitutedExpression()`

]] solver.

This Python program runs in 65ms. (The internal run time of the generated program is 164µs).

**Run:** [ @replit ]

from enigma import SubstitutedExpression SubstitutedExpression.split_sum( "AGES + EVERY + YEAR + BY + AS + NEARLY + A + LARGE + AGE + AS = AVERAGE", extra=["2 * R = Y"], answer="AGES", ).run()]]>

You say “a faulty program is one that does not produce what it is supposed to”. Was your first program not supposed to produce a solution to the teaser?

]]>A 1-cycle is already resolved (0 swaps).

With a 2-cycle the elements are resolved by swapping them (1 swap).

For larger *n*-cycles, no swap can resolve 2 elements (otherwise it would be a 2-cycle). So the best we can do is swap one of the elements into place, which results in a 1-cycle (resolved) and an *(n − 1)* cycle.

So an *n* cycle can always be resolved in *(n − 1)* swaps.

You have to consider all possible sets of siblings along with their probabilities, not just sets with 3 sons. So consider for example sets of 3 siblings:

* P(3 sons) = 1/8

* P(2 sons, 1 daughter) = 3/8

* P(1 son, 2 daughters) = 3/8

* P(3 daughters) = 1/8

In a representative population of 8 sets of siblings distributed according to these probabilities, there are in total 12 males and 12 females. One set has 3 males and 0 females, 3 sets have 2 males and 1 female, 3 sets have 1 male and 2 females, and 1 set has 0 males and 3 females. Females have 12 brothers in total, so each female is expected to have 12/12 = 1 brother. Males have 12 brothers in total, so each male is also expected to have 12/12 = 1 brother.

If male birth is twice as likely as female, then

* P(3 sons) = 8/27

* P(2 sons, 1 daughter) = 12/27

* P(1 son, 2 daughters) = 6/27

* P(3 daughters) = 1/27

In a representative population of 27 sets of siblings distributed according to these probabilities, there are in total 54 males and 27 females. Females in total have 36 brothers, so each female is expected to have 4/3 of a brother. Males have in total 72 brothers, so each male is also expected to have 4/3 of a brother.

Generic formula for expected number of brothers (both for males and females) given the family size of n:

E=p(n-1)

where p is the probability of male infant birth (1/2 for Equalis and 2/3 for Fraternis).

To find the expected number of brothers for any family size, we need to know the distribution of family sizes in population. But regardless of this distribution, the expected number of brothers is the same for both males and females, we just don’t know the exact number.

I have two brothers, and each of them can say the same.

But each of my sisters has three brothers: that’s true whether I have only one sister or a dozen of ’em. Would Thomas Bayes really have claimed that the numbers are equal?

But it is known that , because the sex of any infant is independent of other siblings.

From that,

Similarly,

Therefore,

The probabilities of having n brothers is equally distributed for males and females, therefore the expected number of brothers is the same, regardless of the probability of male infants at birth or family size distribution. ]]>

>The results are sensitive to the distribution of family size.

No, it’s not. For any given family size, the number of brothers is the same for both males and females. And it doesn’t depend on the probability of male/female offspring. I have a mathematical proof, but it is just too long and too complex for me to bother to write it down in Latex. It is a bit annoying as I believe there has to be a simpler logical explanation to this fact.

In this case for, a[1..26], first the 12 swaps (1, 25), (2, 24) … (12, 14), and then the 13 swaps (1, 26), (2, 25) … (13, 14)

]]>Oops! There was a mistake in my program (now corrected).

In fact the simulation shows that the numbers are always pretty much the same.

Here’s another program which considers all possible families of a specific size, and produces the mean number of brothers for male and female offspring:

from enigma import (subsets, fdiv, arg, printf) # consider family size n = arg(4, 0, int) ds = arg('MF', 1) printf("[{n} {ds}]") tM = tF = nM = nF = 0 # consider all possibilities for ss in subsets(ds, size=n, select="M"): (M, F) = (ss.count(x) for x in 'MF') if M > 1: tM += M * (M - 1) if M > 0: tF += F * M nM += M nF += F # output solution printf("-> {nM} male offspring; {nF} female offspring") printf("{f:.2f} brothers to male offspring", f=fdiv(tM, nM)) printf("{f:.2f} brothers to female offspring", f=fdiv(tF, nF))

For example:

% python3 puzzle-177b.py 8 MF [8 MF] -> 1024 male offspring; 1024 female offspring 3.50 brothers to male offspring 3.50 brothers to female offspring % python3 puzzle-177b.py 8 MMF [8 MMF] -> 34992 male offspring; 17496 female offspring 4.67 brothers to male offspring 4.67 brothers to female offspring]]>

Right. So, instead of

if len(set(md+res)) != 4: continue

I could use

if len(set(list(md)+res)) != 5: continue

which also take cares of the ‘H’ in ‘WHY’.

Although, this must have been taken care elsewhere, otherwise I should get at least one solution in which ‘H’ is equal to ‘O’ or ‘R’ in ‘WORRY’. We are talking about 720,000 cases examined here!

The results are sensitive to the distribution of family size.For example, iIf more large families are allowed, by changing line 13 to

if random.uniform(0, 1) < 0.05:

then the number of brothers for males and females looks like it is equal for both species.

My intuition is that the number of brothers is the same for males and females regardless of the probability of a male child, but there is an extensive discussion in stackexchange https://math.stackexchange.com/questions/1794123/do-men-or-women-have-more-brothers

that suggests that the result might vary depending on family size.

It wasn’t obvious to me that n-1 swaps is the minimum number to represent an n-cycle. The best argument that I could find is that each swap changes the number of cycles by ±1.

This mathologer video https://www.youtube.com/watch?v=w0mxdo5ur_A (about 9 minutes in from the start) helps to visualise this idea.

]]>@alkisp, I think you also need to add a check that the H of WHY is unequal to O and R in WORRY.

I prefer the for … else: #no break …. construction instead of all the “zero” maintenance

]]>Is there a rational explanation? ]]>

I found it easier to think about getting the boxes into their sorted order (1=A, …, 26=Z) from an initial unsorted position (for the puzzle this is (1=Z, 2=A, … 26=Y). The swaps can then just be applied in reverse order to perform the operation described in the puzzle text.

The *cyclic decomposition* of the unsorted position consists of one 26 cycle, so the minimum number of swaps required to order the sequence is 25.

(In general if the cyclic decomposition of the unsorted position consists of cycles of length *a, b, c, …*, then we can resolve each cycle separately with one fewer swaps that its length to sort the sequence in *(a − 1) + (b − 1) + (c − 1) + …* swaps).

Performing: *swap(1, 2), swap(2, 3), …, swap(25, 26)* will put one element into position (A, B, …, X) with the first 24 steps, and the final (25th) step will swap Y and Z into the correct positions.

This achieves the sort in 25 operations (and only swaps adjacent pairs).

**Solution:** (3) Yes. (4). No.

And if we can perform the sort in 25 swaps we can perform it in 27 swaps, by performing a (swap, swap, swap) step on the same pair of elements, instead of just a (swap). (Or we can introduce a third element and instead of performing *swap(x, y)* in 1 swap, we can do it in 3 swaps: *swap(x, z); swap(x, y); swap(y, z)*).

**Solution:** (1) Yes.

If we consider the number of *inversions* in the sequence. (i.e. the number of pairs that are out of order).

When we swap two *adjacent* letters in a sequence, only those 2 letters change their order in relation to each other. All other pairs remain unchanged. So the pair of letters either move from “inverted” to “ordered” (and the number of inversions reduces by 1) or they move from “ordered” to “inverted” (and the number of inversions increases by 1).

So we certainly can’t solve the puzzle using 26 *adjacent* swaps.

But what about non-adjacent swaps?

When we swap a *non-adjacent* pair (say a pair with *k* elements between them), then as well as the pair we are swapping flipping its inversion state, the inversion states of each element of the pair and the *k* intermediate elements are also flipped. So in all *(2k + 1)* inversions are flipped (i.e. an odd number).

This means with each swap the *parity* (i.e. whether it is even or odd) of the number of inversions changes.

Since we start with 25 inversions (an odd number), and 26 swaps the number of inversions will again be odd (as it alternates: odd, even, odd, even, …) when we finish. So it cannot be zero.

Hence we can only solve the puzzle using an odd number of swaps.

**Solution:** (2) No.

BTW, in his solution, @Jim adds another task that is not requested in the description of the problem: Find all the pairs that yield the largest number of steps. So, I will do that too, for not falling short in my solution …

''' Note: - I call "bases" the pairs of digits with which the process starts and is based - I use the abbreviation LNS for the largest number of steps - I don't use timing. It's virtally zero. ''' # No need to use permutations 10 by 2 w/ repetitions. We'll get numbers 0 to 99 as digit tuples (0,0)-(9,9) # Instead, we can just iterate all numbers from 0 to 99 # Set 'find_all' as follows: # 0 = Ternminate the process when the first LNS is found (What the problem asks) # 1 = Find all the bases that yield the same LNS (What @Jeff has added as task) find_all = 1 print() LNS = 0; max_iters = 100; base = 0 lst = [0]*max_iters # List for controlling re-occurance of steps if find_all: LNS_list = [0]*100 # This will hold the individual LNSes for each base for i in range(100): a,b = int(i/10),i%10 for j in range(max_iters): lst[j] = [a,b]; a = (a*b) % 10; b = (b+a) % 10 if [a,b] in lst[:j+1]: n = j + 1; break # Exit before repetition if LNS < n: LNS = n; base = i # Update overall LNS and base if find_all: LNS_list[i] = n # Store LNS for this base if not find_all: print("Overall LNS: %d - First base obtained with this LNS: [%d]" % (LNS, base)) else: print("Overall LNS:", LNS) # Show all bases that produce the above LNS bases = []; a = 0 #breakpoint() while 1: if not LNS in LNS_list[a:]: break a = LNS_list[a:].index(LNS) + a bases.append([a]); a += 1 print("All the bases producing the above LNS:", str(bases)[1:-1]) ''' Output: Overall LNS: 13 - First base obtained with this LNS: [11] (With 'find_all' = 0) Overall LNS: 13 All the bases producing the above LNS: [11], [16], [61], [66] (With 'find_all' = 1) ''']]>

(It iincludes the “single zero” condition, which in my very first code have not included because I missed it in reading the description of the problem.)

from itertools import permutations, product from time import perf_counter as clock print() t = clock() # Multiplicands - Perms 10 by 3 w/o reps - Formula: n1!/(n1-n2)! = 10!/(10-3)! = 720 md_perms = permutations(list(range(10)), 3) # Multipliers - Perms 10 by 3 w/ reps- Formula: n1^n2 = 10^3 = 1000 # We need a list here because it will repeatedly parsed mr_perms = list(product(list(range(10)), repeat=3)); mr_perms_n = len(mr_perms) # = 10**3 solutions = [] for md in md_perms: if 0 in md: continue # Multiplicand must not contain zeros if len(set(md)) != 3: continue # It must not contain duplicate digits either for i in range(mr_perms_n): mr = mr_perms[i] if 0 in mr: continue # Multiplier must not contain zeros either # Check the multiplication result first mdn = 100*md[0]+10*md[1]+md[2] # Multiplicand as number (WHY) mrn = 100*mr[0]+10*mr[1]+mr[2] # Multiplier as number res = list(map(int, str(mdn*mrn))) # The result of the multiplication as list (WORRY) if len(res) != 5: continue # Visual aid: WHY WORRY if res[0] != md[0] or res[4] != md[2] or res[2] != res[3]: continue if len(set(res)) != 4: continue # Check PPs and single zero zero = 0 for j in range(3): pp = str(mdn*mr[j]) # Checking PP as string is better if len(pp) != 3: zero = 0; break # It must be 1000 > pp > 99 a = pp.find('0') if a != -1: if zero: zero = 0; break # If we already found a zero ... if j == 1: break # It is said that the zero is not 2nd PP if pp.find('0',a+1) != -1: break # There must be a single zero zero = 1 # Indicate zero is found if not zero: continue # Finally! We got a solution! t2 = clock() - t solutions.append([mdn, mrn, res]) # Store all the data that are useful in displaying the solutions print("Solution #%d found in %0.5f seconds" % (len(solutions), t)) t = clock() - t; print("Total time: %0.5f secs\n" % t) n = len(solutions) if not n: quit("No solution found") print(n, "solution(s) found") for i in range(n): resn = int(''.join(map(str, solutions[i][2]))) # Result as number print("Solution #%d: %s x %s = %s" % (i+1, solutions[i][0], solutions[i][1], resn)) ''' Output: Solution #1 found in 0.07980 seconds Total time: 1.43610 secs 1 solution(s) found Solution #1: 134 x 126 = 16884 ''']]>

I assumed that in the *Equalis* monkeys male and female offspring are equally likely, and denote this case: MF.

With the *Fraternis* monkeys I assumed 2/3 of offspring were male and 1/3 female. I denote this case: MMF.

Considering a family with *m* male offspring, and *f* female offspring:

If *m* = 0, then each of the (female) offspring has 0 brothers.

If *m* ≥ 1, then each of the *m* males has *(m − 1)* brothers. And each of the *f* females has *m* brothers.

So, for a collection of families, we can calculate the average number of brothers for the male and the female offspring.

With 1 million trials we find that in both cases male offspring and female offspring on average have the same number of brothers.

Here is the program I used:

import random from enigma import (multiset, irange, fdiv, number, printf) N = number("1_000_000") def siblings(ds): # construct a family ss = multiset() while True: # add a new member to the family ss.add(random.choice(ds)) # are we done if random.uniform(0, 1) < 0.20: return ss def run(ds, n=N): # count number of male and female siblings tM = tF = nM = nF = 0 # construct N random families for _ in irange(N): ss = siblings(ds) (M, F) = (ss.count(x) for x in 'MF') if M > 1: tM += M * (M - 1) if M > 0: tF += F * M nM += M nF += F # output solution printf("[{ds}]") printf("-> {nM} male offspring; {nF} female offspring") printf("{f:.2f} brothers to male offspring", f=fdiv(tM, nM)) printf("{f:.2f} brothers to female offspring", f=fdiv(tF, nF)) printf() # run the scenarios run("MF") run("MMF")]]>

@Jim, Thanks I didn’t see the interchangable remark.

I got twice the number of intermediate solutions so I wondered why.

@Frits: As noted in the comment (B, C) and (G, H) are interchangable. So I only consider them one way. For every solution found you could make another one by swapping them over, but as they never contribute a Taurean to the answer I didn’t bother.

]]>The code I posted was **not faulty**. It did exactly what I intended for it to do and what it was supposed to do. **A faulty program is one that does not produce what it is supposed to**.

What was “faulty” instead was my reading of the puzzle. I missed the “single zero” condition. (This was actually quite obvious from the results which I wouldn’t cinsider as such.)

So here is the new version, in which I have included the “single zero” condition:

from csp import Problem from time import perf_counter as clock t = clock() p = Problem() s = "WHY * abc = WORRY" vars = list(set(c for c in s if c.isalpha())) # ['W', 'H', 'Y', 'a', 'b', 'c', 'O', 'R'] p.addvars(vars, range(10)) lst = ['W','H','Y','O','R']; l = len(lst) for i in range(l-1): for j in range(i+1,l): p.addrule(lst[i]+"!="+lst[j]) p.addrule("(c*Y)%10 == Y") p.addrule("(100*W+10*H+Y)*(100*a+10*b+c) == 10000*W+1000*O+100*R+10*R+Y") # Operation and total product p.addrule("a*(100*W+10*H+Y) < 1000") # Length of the product in each line must be 3 p.addrule("b*(100*W+10*H+Y) < 1000") p.addrule("c*(100*W+10*H+Y) < 1000") # Single zero ... I can't create necessary constaints for the partial products here - I will filter the results later p.notin([0], "WHYOR") max_sols = 20 # Actually, I get only 15 total solutions before "filtering" "single zeros" raw_solutions = p.solution(max_sols) n = len(raw_solutions) print() if not n: print("No solution found!"); exit() # I name them "raw" solutions bacause they are not final solutions = [] # Final/actual solutions # Process the raw solutions for i in range(n): d = raw_solutions[i] md = 100*d['W']+10*d['H']+d['Y'] # Multiplicand s = str(md*d['a']) + str(md*d['b']) + str(md*d['c']) # Easiest way for checking 0s a = s.find('0') if a == -1 or (a>2 and a<6): continue # If no 0 or in 2nd partial product elif s.find('0',a+1) != -1: continue # If more than one 0s found solutions.append(d) t = clock() - t; print("\nTime: %0.5f secs\n" % t) # End of processing # Show the results n = len(solutions) if not n: print("No solution found!") print(n, "solution(s) found") for i in range(n): print("Solution #%d: %s" % (i+1, d)) # No need to sort - We are only interested in replacing letters with values ''' Output: {'Y': 4, 'H': 3, 'W': 1, 'O': 6, 'R': 8, 'c': 6, 'a': 1, 'b': 2} The full operation of the first solution, visually: 134 x 126 --- 804 268 134 ----- 16884 ''']]>

@Jim, shouldn’t the selection of (B, C) and (G, H) be permutations instead of combinations?

]]>def solve(): thirteens = sum( (7<<11)<<(13*i) for i in range(90) ) sevens = sum( (7<<5)<<(7*i) for i in range(90) ) elevens = sum( (7<<9)<<(11*i) for i in range(90) ) sols = thirteens & sevens & elevens for n in range(1000): if sols & 1: if 100000 <= (n*(n+1)*(n+2))//6 <= 999999: print(n,"layers, ", (n*(n+1)*(n+2))//6 , "balls") return sols >>= 1 solve()]]>

Possible “day” values are 10-31, and the corresponding years must be the reverse of these (leading zeros being allowed).

Months can be any single digit value (1-9) and 11.

The puzzle was set in 1996, so I’m assuming all the years are in the range 1900-1995, represented by the final two digits.

The puzzle says “Kevin has still not reached his first birthday” – which suggests he was born in 1995, but that is not possible (as no month has 59 days).

At first I thought the puzzle was flawed, and had many solutions. But I think a trick is being played on us. The puzzle says that Kevin has not yet reached his first birthday. Now, if Kevin was born on 29/2/92, his first *birthday anniversary* would be 29/2/96, several weeks after the puzzle was set, so it could be argued that he hadn’t reached his first birthday. (Although, I have always assumed that people born on 29th February would celebrate their birthday on either 28th February or 1st March in a non-leap year).

And using this interpretation does indeed lead to a unique answer to the puzzle (with many ways to achieve it).

Fixing the date for K gives us the values for N and A. We can then look for viable dates for the remaining 9 royals.

This Python program runs in 288ms.

**Run:** [ @replit ]

import datetime from enigma import ( irange, cproduct, nreverse, catch, nconcat, reverse, nsplit, seq_all_different, subsets, unpack, multiset, join, sprintf as f ) # the circle of signs sign = [ "Capricorn", "Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", ] # boundaries (by month) boundary = [ None, 20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 22, 22 ] # calculate zodiac sign def zsign(ymd): (y, m, d) = ymd return (sign[m - 1] if d < boundary[m] else sign[m]) # calculate year from day, day from year d2y = nreverse y2d = lambda y: (nconcat(reverse(nsplit(y, 2))) if y < 100 else None) # construct possible palindromic dates dates = list() for (d, m) in cproduct([irange(10, 31), list(irange(1, 9)) + [11]]): y = nreverse(d) z = catch(datetime.date, y, m, d) if z is not None: dates.append((y, m, d)) dates.sort() # check dates for different years and different signs def check(*dmys): return ( seq_all_different(y for (d, m, y) in dmys) and seq_all_different(zsign(x) for x in dmys) ) # find pairs of dates that are 1 year, 10 days apart pairs = set() for (y, m, d) in dates: (y1, m1, d1) = (y + 1, m, d + 10) if (y1, m1, d1) not in dates: continue # check they are different zodiac signs (X, Y) = ((y, m, d), (y1, m1, d1)) if zsign(X) != zsign(Y): pairs.add((X, Y)) def main(): # count solutions ss = multiset() # suppose K was born on 29/2/92 K = (yK, mK, dK) = (92, 2, 29) # N was born 79 years, 1 month (+ a few days) earlier yN = yK - 79 mN = mK - 1 dN = y2d(yN) N = (yN, mN, dN) if not(N in dates and 0 < dN - dK < 10): return # A was born in the month of N's 18th birthday yA = yN + 18 mA = mN dA = y2d(yA) A = (yA, mA, dA) if A not in dates: return # A is the eldest ps = set((X, Y) for (X, Y) in pairs if A < X) # find an (I, J) pair (I was born in June) for (I, J) in ps: (yI, mI, dI) = I if mI != 6: continue if not check(N, A, I, J, K): continue # find (B, C) and (G, H) pairs (interchangable; B, G not Aries) ps1 = ((X, Y) for (X, Y) in ps if zsign(X) != "Aries") for ((B, C), (G, H)) in subsets(ps1, size=2): if not check(N, A, B, C, G, H, I, J, K): continue # choose a suitable (D, E) pair for (D, E) in ps: if not check(N, A, B, C, D, E, G, H, I, J, K): continue # choose F for F in dates: if not(A < F): continue if not check(N, A, B, C, D, E, F, G, H, I, J, K): continue # signs kvs = list(zip("ABCDEFGHIJKN", [A, B, C, D, E, F, G, H, I, J, K, N])) z = dict((k, zsign(v)) for (k, v) in kvs) # record the Taurean ss.add(reverse(z)["Taurus"]) # output solution fmt = unpack(lambda y, m, d: f("{d}/{m}/{y:02d}")) print(f("N = {N} [{z}]", N=fmt(N), z=z['N'])) for (k, v) in kvs: if k != 'N': print(f("-> {k} = {v} [{z}]", v=fmt(v), z=z[k])) print() print(f("Taurus = {ts}", ts=join(ss.keys(), sep=", "))) main()

**Solution:** Eric is the Taurean.

There are many possible ways to achieve the answer, here is one:

Nosmo 31/1/13 [Aquarius] -> Airey 13/1/31 [Capricorn] -> Igor 15/6/51 [Gemini] -> John 25/6/52 [Cancer] -> Fred 23/9/32 [Libra] -> Derek 14/4/41 [Aries] -> Eric 24/4/42 [Taurus] -> Barry 16/8/61 [Leo] -> Gary 18/11/81 [Scorpio] -> Harry 28/11/82 [Sagittarius] -> Carey 26/8/62 [Virgo] -> Kevin 29/2/92 [Pisces]

So Nosmo’s 80th birthday was on 31/1/93, when Kevin was 11 months and 2 days old.

]]>@alkisp It is the only solution. Your program (below) is faulty since it does not apply the constraint that there is only one zero digit in the solution (one that is a ‘starred’ digit not in the second partial product).

]]>from csp import Problem from time import perf_counter as clock t = clock() p = Problem() s = "WHY * abc = WORRY" vars = list(set(c for c in s if c.isalpha())); #quit(vars) # ['c', 'Y', 'a', 'R', 'H', 'W', 'b', 'O'] p.addvars(vars, range(10)) #p.alldifferent() # No solution! p.notin([0], "Wa") #p.addrule("W!=H and W! (c*Y)%10 == Y") lst = ['W','H','Y','O','R']; l = len(lst) for i in range(l-1): for j in range(i+1,l): p.addrule(lst[i]+"!="+lst[j]) p.addrule("(c*Y)%10 == Y") p.addrule("(100*W+10*H+Y)*(100*a+10*b+c) == 10000*W+1000*O+100*R+10*R+Y") # Operation and total product p.addrule("a*(100*W+10*H+Y) < 1000") # Length of the product in each line must be 3 p.addrule("b*(100*W+10*H+Y) < 1000") p.addrule("c*(100*W+10*H+Y) < 1000") max_sols = 10 # Set it to 1 to obtain a single (the first) solution faster solutions = p.solution(max_sols) t = clock() - t; print("\nTime: %0.5f secs\n" % t) n = len(solutions) if not n: print("No solution found!"); exit() print(n, "solution(s) found, with maximum solutions set to", max_sols) for i in range(n): print("Solution #%d: %s" % (i+1, solutions[i])) ''' Output: Time: 1.66602 secs (0.67700 secs for first solution only) 10 solution(s) found, with maximum solutions set to 10 Solution #1: 138 x 116 = 16008 Solution #2: 108 x 141 = 15228 Solution #3: 247 x 121 = 29887 Solution #4: 157 x 121 = 18997 Solution #5: 326 x 121 = 39446 Solution #6: 236 x 121 = 28556 Solution #7: 136 x 116 = 15776 Solution #8: 106 x 131 = 13886 Solution #9: 315 x 103 = 32445 Solution #10: 215 x 123 = 26445 The full operation of the first solution, visually: 138 x 116 --- 828 138 138 ----- 16008 ''']]>

What does this specific solution have? There are really a lot of solutions! (See my code)

]]>@ilya: Very comprehensive.

I expect the setter had a particular time in mind. I assumed it was 2:00, but I could be wrong.

(I think I fixed up the degree signs in your comment by enclosing them in `\text{}`

).

Right. Only that we must **divide** the numbers of the sequence (7, 7, 14, 28) by 7, to get (1,1,2,4), not the other way around. And this case is too evident. I’m not sure if we can always do that –considering the millions of different sequences that can be formed, i.e. know the basic number of the sequence and also divide everything by it. Maybe this has to be proven. But don’t count on me! 🙂

OK. No problem with me.

]]>@Frits: I think this approach would also have problems with loops. If there was a long loop then each element of the loop would give a maximal staircase, but I don’t think your program would find them. Sometimes a simple approach is best.

The “longest path problem” in graph theory is NP-hard, so I think there isn’t really going to be a better way than just looking at all possible staircases and finding the longest.

]]>@alkisp: WordPress isn’t perfect, but is just about up to the job. So I would encourage you to check comments thoroughly before posting. As admin I can edit comments, so I can correct minor errors if you post a clear correction as a followup.

]]>To solve a sequence like (7, 7, 14, 28), we can solve (1, 1, 2, 4), and multiply up everything in the solution by 7.

So it still comes down to solving a sequence with a sum that is a power of 2.

]]>Maybe better: **a + a * (2^n -1)** (Makes more sense)

(Sorry, but unfortunately and weirdly enough, one cannot edit or delete or even preview a reply in here! This is a quite unintelligent system, because 1) space is wasted and 2) there’s an unnecessary and sometimes quite confusing “traffic” of messages.)

]]>Correction: It’s about **geometric** series.

The sum of the terms of a geometric series is **a * ((1 – r^n) / (1 – r))**, where ‘a’ = base number (first term), ‘r’ = common ratio and ‘n’ = number of terms. In our case, ‘r’ is always 2. So the simplified formula is **-a * (1 – 2^n)**. To which, we must add another ‘a’ in order to get the sum of the numbers in a layout, i.e. the formula becomes

**a – a * (1 – 2^n)**.

Examples:.

For the solvable layout [1,1,2,4,8,16,32], the sum is 1-1*(1-2**6) = 64..

For the solvable layout [7,7,14,28], the sum is 7-7*(1-2**3) = 56.

What do you know? A little while after I turned my PC off, I just realized that the “power-of-2” condition is not a must! E.g. [5,5], [5,5,10], etc. are solved and their sum is not a power of 2!

The condition seems that it has to do with arithmetic series: **n, n, 2*n, 4*n, 8*n, …** Which also covers the “power of 2” case, using n=1. There must be some formula for this series …

Indeed! They are also solved as part of group ‘3’-‘8’ …

It looks like my code does not like big crouds! 😀

Well, I have no intention to debug such a case. I am personally satified that the code works successfully for **single layouts**, which is actually what is is supposed to do, as the puzzle says (**Enigma 648: Piles of fun**), not hundreds of them together!

Anyway, I’m not willing at all to debug such a case. Besides, as I already said, this puzzle needs not to be solved beyond the “power-of-2” solution.

Thanks for trying though …

Similar.

from itertools import product # dictionary of step combinations d = {(a, b): ((c := (a * b) % 10), (b + c) % 10) for a, b in product(*[range(10), range(10)])} sols = [] # start each stairway with an unreacheable step for k in [k for k in d.keys() if k not in d.values()]: stairway = (k, ) # continue adding new steps while d[stairway[-1]] not in stairway: stairway += ((d[stairway[-1]]), ) sols.append(stairway) # output solution(s) mx = max(len(s) for s in sols) print("max =", mx) for x in [s for s in sols if len(s) == mx]: print(f"-> {x}")]]>

@Jim, OK, I didn’t understand the meaning of reduced.

]]>@Frits:

gcd(3, 3, 3, 3) = 3, so the reduced sequence is (1, 1, 1, 1). Which has a sum of 4 = 2².

And it can be solved as follows:

(1, 1, 1, 1) → (1, 1, 2) → (2, 2) → (4)

You can then map this back up to (3, 3, 3, 3) by multiplying everything by 3:

]]>(3, 3, 3, 3) → (3, 3, 6) → (6, 6) → (12)

If I use ten power of 2 sums only 8 are reported as solvable

layouts = [

[‘0’, [19, 122, 101, 31, 38, 63, 55, 13, 50, 95, 52, 6, 96, 112, 73, 21, 39, 56, 5, 4, 59, 9, 106, 139, 124, 60, 127, 97, 58, 108, 110]],

[‘1’, [121, 43, 29, 130, 139, 121, 11, 104, 100, 47, 145, 66, 58, 138, 63, 113, 121, 31, 117, 45, 119, 143, 88, 95, 44, 139, 51, 93, 92, 147, 73, 7, 2, 5, 106, 42, 60, 3, 136, 18, 78, 15, 105, 117, 37, 18, 130, 110, 93, 110, 78]],

[‘2’, [2, 116, 110, 28]],

[‘3’, [41, 103, 112]],

[‘4’, [99, 12, 64, 7, 20, 111, 18, 133, 53, 128, 5, 120, 33, 46, 91, 84]],

[‘5’, [130, 34, 97, 101, 129, 21]],

[‘6’, [124, 19, 70, 82, 2, 135, 150, 139, 79, 115, 15, 48, 19, 27]],

[‘7’, [40, 132, 62, 22]],

[‘8’, [46, 147, 70, 143, 60, 46]],

[‘9’, [23, 1, 120, 112, 75, 70, 59, 52]]

]

Layout ‘2’: Final state: [256] (Solved in round #3)

Layout ‘0’: Final state: [2048] (Solved in round #4)

Layout ‘4’: Final state: [1024] (Solved in round #4)

Layout ‘7’: Final state: [256] (Solved in round #4)

Layout ‘9’: Final state: [512] (Solved in round #4)

Layout ‘1’: Final state: [4096] (Solved in round #5)

Layout ‘6’: Final state: [1024] (Solved in round #5)

Layout ‘8’: Final state: [512] (Solved in round #6)

Running only ‘3’ and ‘5’ both are reported as solvable.

]]>@Jim,

“a reduced sequence is not solvable unless the sum is a power of 2” is not always true because of the [3, 3, 3, 3] example.

OK. Thanks.

]]>And here’s a recursive version that uses caching (but only calculates the length of the staircase, not the actual sequence of values).

The termination condition is that the next step is the same as the previous step, so it wouldn’t work if there were loops, but (fortunately) it turns out that doesn’t happen. (So the constructive approach is more rigorous).

It runs in 58ms. (Internal runtime is 205µs).

**Run:** [ @replit ]

from enigma import (Accumulator, subsets, irange, cache, printf) # return the length of the staircase above A, B @cache def staircase(A, B): C = (A * B) % 10 D = (B + C) % 10 return (1 if (C, D) == (A, B) else staircase(C, D) + 1) # collect maximal solutions r = Accumulator(fn=max, collect=1) # consider all staircases starting with [00] - [99] for (A, B) in subsets(irange(0, 9), size=2, select="M"): r.accumulate_data(staircase(A, B), (A, B)) # output solutions printf("max = {r.value} {r.data}")]]>

This Python program runs in 61ms. (Internal run time is 2.0ms).

**Run:** [ @replit ]

from enigma import (Accumulator, irange, subsets, printf) # construct a staircase def staircase(A, B): r = [(A, B)] while True: C = (A * B) % 10 D = (B + C) % 10 if (C, D) in r: break r.append((C, D)) (A, B) = (C, D) return r # collect maximal solutions r = Accumulator(fn=max, collect=1) # consider all staircases starting [00] - [99] for (A, B) in subsets(irange(0, 9), size=2, select="M"): s = staircase(A, B) printf("[{n} = {s}]", n=len(s)) r.accumulate_data(len(s), s) # output solution(s) printf("max = {r.value}") for ss in r.data: printf("-> {ss}")

**Solution:** The largest number of steps is 13.

We can start with any of [11], [16], [61] or [66] to achieve the maximum length:

[11] [12] [24] [82] [68] [86] [84] [26] [28] [64] [48] [20] [00]

[16] [62] [24] [82] [68] [86] [84] [26] [28] [64] [48] [20] [00]

[66] [62] [24] [82] [68] [86] [84] [26] [28] [64] [48] [20] [00]

[61] [67] [29] [87] [63] [81] [89] [21] [23] [69] [43] [25] [05]

Starting with [11], [16] or [66] gets us to [24] after two steps, and then after another 10 steps we get [00], which would be followed by [00].

Starting with [61] gets us to [05] after 12 steps, which would be followed by [05].

]]>[3, 3, 3, 3] is solvable but the sum 12 is not a power of 2.

]]>We have to construct the grid using 3-digit triangular numbers, so leading zeros are not allowed.

If you include this condition you should find that there is a single solution.

In general with this type of puzzle leading zeros are not allowed unless explicitly stated. But I think when the puzzle refers to 3-digit triangular numbers it means numbers that are expressed as 3-digits in standard decimal notation.

Here is a solution using the [[ `SubstitutedExpression`

]] solver from the **enigma.py** library. (Which I don’t think existed at the time I originally posted this puzzle):

**Run:** [ @replit ]

#! python3 -m enigma -r SubstitutedExpression --distinct="" "is_triangular(ABC)" "is_triangular(BDE)" "is_triangular(CEF)" "is_square(ADF)"]]>

Note:

1) In Metlod #1, ‘OrderedDict’ is not needed. (It has just been left from a previous version.)

2) In Method #2, there’s no need to create a list of permutations as an intermediate step. Just the **generator** is enouph. Its items can be accessed in the usual way: **`for x in perms`**.

I don’t disagree. What you say is correct. I also thanked you because you gave me the opportunity to find the general, applicable to all cases condition: **the sum of a solvable layout is always a power of 2**. This is much more important. I wonder how comes you missed or ignored that …

When I talk about an optimal solution for a sequence I mean a minimal length collection of operations that reduce the sequence to a single pile. So we certainly can say when an optimal solution for a sequence is found. For the sequences given in this puzzle, **A** can be solved in 13 steps and **C** can be solved in 10 steps. **B** and **D** are not solvable.

My first concern when solving this puzzle was to write a program that is correct, i.e. a program that will find if the sequences given can be solved or cannot. A searching algorithm achieves this by considering all possible transitions that can be made, so if there is a solution it will be found (and we will find the size of the optimal solution). Also it shows sequences **B** and **D** cannot be solved, as there is no possible collection of transitions that result in a single pile. This is a problem for strategic solvers that just consider a single path from the initial sequence. Just because you don’t find a viable path, how can you be sure one does not exist? Without additional analysis, a strategic solver can only give a partial solution to the puzzle, and I prefer to post complete solutions where possible.

I was less worried about the time (and memory) that my searching algorithm used, as it found the answer to the puzzle, but it could do with improvement. My second program, that starts the search from both ends was more than 10× faster at solving the puzzle, and executes in less than 0.5s, so I was happy enough with my solution. But while sufficient to solve the puzzle, it will struggle with sequence that require more steps, as the search space increases quickly as the solution path gets longer.

I do read comments, and I thank you for taking the time to contribute to the site.

Your observation that a sequence with a sum that is a power of two is always solvable is interesting. Well done. I have done some testing, which supports this. And the converse is also supported – a reduced sequence is not solvable *unless* the sum is a power of 2. Which suggests that a sequence is solvable exactly when its reduced equivalent has a sum that is a power of two. A proof of either of these observations would be interesting to see.

I didn’t say all sequences with the pile sum a multiple of four are solvable but all solvable sequences (with at least 3 piles) must have a sum divisible by four.

There are only two options if we have gotten down to a sequence of 2 piles:

(x, x) where x is even; the previous step was either (x, x/2, x/2) –> (x, x) or (x/2, 3/2x) –> (x,x)

or

(y, 3y) which clearly has a sum divisible by four (from (y, 3y) we can transform to (2y, 2y))

If you disagree please show an example with three or more piles which is solvable and where the sum is not divisible by four.

]]>There are many more solutions! (See below)

**Method #1: Using Constraint Satisfaction**

You can download ‘csp.py’ with its pair, ‘constraint.py’ at http://www.fantascienza.net/leonardo/so/csp.zip

The method used here however must work with any constraint satisfaction module/library

from collections import OrderedDict from csp import Problem p = Problem() pvars = "a b c d e f g h i".split() p.addvars(pvars, range(0, 10)) # I have to include 0 as in the doc! p.addrule("a != b"); p.addrule("a != c"); p.addrule("b != c") p.addrule("d != e"); p.addrule("d != f"); p.addrule("e != f") p.addrule("g != h"); p.addrule("g != i"); p.addrule("h != i") p.addrule("b==d"); p.addrule("c==g"); p.addrule("f==h") p.addrule("(800*a+80*b+8*c+1)**0.5 == int((800*a+80*b+8*c+1)**0.5)", ["a","b","c"]) # Needed to handle 'int()'! p.addrule("(800*d+80*e+8*f+1)**0.5 == int((800*d+80*e+8*f+1)**0.5)", ["d","e","f"]) p.addrule("(800*g+80*h+8*i+1)**0.5 == int((800*g+80*h+8*i+1)**0.5)", ["g","h","i"]) p.addrule("(100*a+10*e+i)**0.5 == int((100*a+10*e+i)**0.5)", ["a","e","i"]) solutions = list(p.xsolutions()); n = len(solutions) if not n: quit("No solution found!") print(n, "solution(s) found") for i in range(n): dic = {key:val for key,val in sorted(solutions[i].items(), key=lambda x: x[0])} print("Solution #%d: %s" % (i+1, list(dic.values())))

Output:

3 solution(s) found

Solution #1: [4, 9, 6, 9, 0, 3, 6, 3, 0]

Solution #2: [2, 1, 0, 1, 5, 3, 0, 3, 6]

Solution #3: [0, 4, 5, 4, 0, 6, 5, 6, 1]

The corresp. grids:

4 9 6

9 0 3

6 3 0

etc.

***********************************************************************************************************

**Method #2: Using combinations of triangular nos 0-999**

from itertools import permutations # Break a number to the 3 digits is made of and retutn it as tuple def break_n(n): a = int(n/100) return a, int((n-100*a)/10), n % 10 # Create lists with all triangle numbers and squares between 0 and 999 tnums = []; squares = [] n,n1,n2 = 0,0,0 while n1 < 1000 or n2 < 1000: if n1 < 1000: n1 = (n**2+n)//2 if n1 < 1000: tnums.append(n1) if n2 < 1000: n2 = n**2; if n2 < 1000: squares.append(n2) n += 1 #print("Triangulars:", tnums); print("Squares:", squares) ''' Triangulars: [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231, 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630, 666, 703, 741, 780, 820, 861, 903, 946, 990] Squares: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961] ''' tnums_n = len(tnums) # Create a list with all possible combinations of 'tnums';; perms = list(permutations(list(tnums), 3)); perms_n = len(perms) solutions = [] for i in range(perms_n): x = perms[i] a,b,c = break_n(x[0]); d,e,f = break_n(x[1]); g,h,i = break_n(x[2]) if b == d and c == g and f == h: n = 100*a+10*e+i if n**0.5 == int(n**0.5): solutions.append(x) n = len(solutions) if not n: quit("No sulution found!") print(n, "solition(s) found") lst = [0]*3 for i in range(n): for j in range(3): lst[j] = break_n(solutions[i][j]) print("Solution #%d: %s" % (i+1, lst))

Output:

15 solition(s) found

Solution #1: [(0, 0, 0), (0, 0, 1), (0, 1, 0)]

Solution #2: [(0, 0, 0), (0, 1, 0), (0, 0, 6)]

Solution #3: [(0, 0, 0), (0, 2, 1), (0, 1, 5)]

Solution #4: [(0, 0, 0), (0, 3, 6), (0, 6, 6)]

Solution #5: [(0, 0, 3), (0, 0, 0), (3, 0, 0)]

Solution #6: [(0, 0, 6), (0, 0, 3), (6, 3, 0)]

Solution #7: [(0, 0, 6), (0, 3, 6), (6, 6, 6)]

Solution #8: [(0, 1, 0), (1, 3, 6), (0, 6, 6)]

Solution #9: [(0, 4, 5), (4, 0, 6), (5, 6, 1)]

Solution #10: [(1, 9, 0), (9, 9, 0), (0, 0, 6)]

Solution #11: [(2, 1, 0), (1, 5, 3), (0, 3, 6)]

Solution #12: [(4, 0, 6), (0, 0, 3), (6, 3, 0)]

Solution #13: [(4, 9, 6), (9, 0, 3), (6, 3, 0)]

Solution #14: [(6, 3, 0), (3, 2, 5), (0, 5, 5)]

Solution #15: [(9, 0, 3), (0, 0, 0), (3, 0, 0)]

Corresp. grids:

4 9 6

9 0 3

6 3 0

etc.

BTW, it doesn’t “seem to solve” ‘E’. It **does** solve it, isn’t it? 🙂

Tthank you for reacting positively about my solution. The host of this place, on the other hand, has totally ignored it …

We can never be certain about **any** solution whether it is optimal or not. The only thing we can do is compare solutions based on their efficiency. And my improved solution has been shown not only being able to handle all challencges upt to now, but it is by far the fastest of all. It’s actually instant.

I also found the condition for solvability of a layout (sequence of numbers). (At least, it has not been disproven up to now.)

You should at least **acknowldge** all this, Jim, esp. as a host of this place, and not try only to find weaknesses in or express doubts about other programmers’ code and solutions.

**You must give credits where they are deserved**. Only in this way you can contribute to a better and more efficient programming for the whole programming community.

If the terms of a sequence have GCD > 1, then we can reduce each of the terms by dividing by the GCD, and if we can solve the reduced sequence we can solve the original sequence.

And it seems likely that the reduced sequence is solvable exactly when the sum is a power of 2.

I wonder if this can be exploited to give an optimal solving strategy.

]]>Please try to see if the above is indeed true. Although I tested well, I may have missed some case that the above conditions is not a MUST …

]]>Well, layout ‘B’ gives a sum of 40 and it is not solvable. But your remark gave me the opportunity to check sums against solvability. And I found out that **a layout is solvable when the sum of its numbers is a power of 2**.

So, it’s useless to try to solve a layout if this condition is not met! But, it’s also useless to try solve it, if this condition **is** met! That is, we do not need to solve this problem anyway! (Except of course for verification purposes and dor programmig fun!)

We should know that in the fist place!

]]>prints 154 as an integer solution for n; then n * (n + 1) * (n + 2) / 6 equals 620620.

]]>The sum of piles must not only be even but also a multiple of four.

]]>@Frits: Here is an improved solution for your sequence (22 steps vs. the 28 steps you gave).

[ 1] (1, 2, 5, 5, 10, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29) # (29, 29) → (58) [ 2] (1, 2, 5, 5, 10, 13, 17, 19, 23, 23, 26, 27, 27, 58) # (27, 27) → (54) [ 3] (1, 2, 5, 5, 10, 13, 17, 19, 23, 23, 26, 54, 58) # (23, 23) → (46) [ 4] (1, 2, 5, 5, 10, 13, 17, 19, 26, 46, 54, 58) # (5, 5) → (10) [ 5] (1, 2, 10, 10, 13, 17, 19, 26, 46, 54, 58) # (10, 10) → 20) [ 6] (1, 2, 13, 17, 19, 20, 26, 46, 54, 58) # (13, 19) → (26, 6) [ 7] (1, 2, 6, 17, 20, 26, 26, 46, 54, 58) # (6, 58) → (12, 52) [ 8] (1, 2, 12, 17, 20, 26, 26, 46, 52, 54) # (1, 17) → (2, 16) [ 9] (2, 2, 12, 16, 20, 26, 26, 46, 52, 54) # (26, 26) → (52) [10] (2, 2, 12, 16, 20, 46, 52, 52, 54) # (2, 2) → (4) [11] (4, 12, 16, 20, 46, 52, 52, 54) # (46, 54) → (92, 8) [12] (4, 8, 12, 16, 20, 52, 52, 92) # (12, 92) → (24, 80) [13] (4, 8, 16, 20, 24, 52, 52, 80) # (20, 24) → (40, 4) [14] (4, 4, 8, 16, 40, 52, 52, 80) # (52, 52) → (104) [15] (4, 4, 8, 16, 40, 80, 104) # (40, 104) → (80, 64) [16] (4, 4, 8, 16, 64, 80, 80) # (80, 80) → (160) [17] (4, 4, 8, 16, 64, 160) # (4, 4) → (8) [18] (8, 8, 16, 64, 160) # (8, 8) → (16) [19] (16, 16, 64, 160) # (16, 16) → (32) [20] (32, 64, 160) # (32, 160) → (64, 128) [21] (64, 64, 128) # (64, 64) → (128) [22] (128, 128) # (128, 128) → (256) [==] (256)

The sequence from [5] – [22] is minimal.

]]>Notation:

– ant’s angular speed;

– minute hand’s angular speed, ;

– ant’s starting angle;

– minute hand’s starting angle;

– how long the ant walked clockwise.

When the ant catches up with the hand, he is ahead of it by a full turn:

When the ant meets the handle for the second time, their phase is the same:

Solving a system of equations:

Replace in second equation with . Also let .

From first equation, we get:

It’s not clear how long it took for the ant to spot that the minute hand was edging towards it, but it happened somewhere between 2:00 and 2:10.

Let be the ant’s reaction time. Then the total amount of time T that has passed since 2 o’clock until the ant has stopped moving:

If the ant started walking at 2:00, then he finished walking at 2:40.

If he started walking at 2:10, then he finished walking at 2:55.

v = (50 + m) / m

After the ant has turned around it walks for another 15 minutes in the opposite direction to the minute hand, before encountering it again. The minute hand has moved 15 minutes, and the ant has moved 45 minutes (in the opposite direction). So the ant is moving at 3× the speed of the minute hand. (i.e. it is moving at a speed of 3 minutes distance per minute of time).

v = 3

3m = 50 + m

2m = 50

m = 25

So the ant first encounters the minute hand at the 25 minute mark (i.e. at 2:25), and then turns around at encounters it again 15 minutes later (i.e. at 2:40).

**Solution:** The ant stopped walking at 2:40.

Let and be the 100s, 10s and 1s digits of the knight and bishop number accordingly. Note that the digits of the bishop number must be either all odd, or all even. On the other hand, the digits of the knight number must alternate in parity, i.e. either odd/even/odd, or even/odd/even.

We know that the bishop number plus 27 equals the knight number. Let’s consider adding digits one by one.

1. , so has parity which is opposite to . As and all have the same parity, and must alternate in parity, therefore must have the same parity as , and parity must be opposite to .

2. There should be no carry into the 10th digit, otherwise and parity is opposite to . To avoid carry, .

3. There must be carry into the 100th digit, otherwise and has the same parity as . To have carry into the 100th digit, . However, is impossible, because that gives , which is not a valid digit. So must be 9.

From , bishop can only go to 1 or 5; considering , we get . That gives and .

Bishop can get to from either or . First is not valid, because it gives , from where there is no knight move to . So that leaves only and .

So the answer is 618 for the knight number and 591 for the bishop number.

Note: the information about X digit is redundant. To find X, consider that the knight can jump to 6 from either 7 or 1, and the bishop can jump to 5 from either 1, 3, 7 or 9. So .

]]>Note that for **C** the sum is 384 = 3×(2^7). And as we work backwards from 384 the size of each pile is a multiple of 3. Fortunately the sizes of the initial piles *are* all multiples of 3, and it possible to reach them using backwards steps.

However, for **B** the sum is 40 = 5×(2^3). As we work backwards the size of each pile is a multiple of 5. But not all the initial piles have sizes that are multiples of 5, so they cannot be reached. Hence **B** is not possible.

And as noted above, with **D** we cannot even complete a single backward step, as the total number of matchsticks is odd.

@Frits: Given sufficient time and memory the searching approach will find a minimal solution (if one exists). For the sequences involved in the puzzle it is sufficient, but as the number of steps required increases, so will the time and memory requirements.

The random approach seems to work fine though, and for longer sequences you can increase the number of goes. (Using [[ `G = 250`

]] seems to be sufficient for the sequence you give).

It also seems to solve E=(1, 2, 5, 5, 10, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29) very quickly (round #4).

]]>@Jim,

Your program has run time problems with the following entry

E=(1, 2, 5, 5, 10, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29),

Getting rid of duplicates also doesn’t seem to help a lot (still slow but the program does finish in 2.5 minutes).

Getting rid of pairs of odd numbers does help (9 seconds).

1, 2, 5, 5, 10, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29 (5, 5) --> (10) [1, 2, 10, 10, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29] 1, 2, 10, 10, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29 (10, 10) --> (20) [1, 2, 20, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29] 1, 2, 20, 13, 17, 19, 23, 23, 26, 27, 27, 29, 29 (23, 23) --> (46) [1, 2, 20, 13, 17, 19, 46, 26, 27, 27, 29, 29] 1, 2, 20, 13, 17, 19, 46, 26, 27, 27, 29, 29 (27, 27) --> (54) [1, 2, 20, 13, 17, 19, 46, 26, 54, 29, 29] 1, 2, 20, 13, 17, 19, 46, 26, 54, 29, 29 (29, 29) --> (58) [1, 2, 20, 13, 17, 19, 46, 26, 54, 58] 1, 2, 20, 13, 17, 19, 46, 26, 54, 58 (1, 13) --> (2, 12) [2, 2, 20, 12, 17, 19, 46, 26, 54, 58] 2, 2, 20, 12, 17, 19, 46, 26, 54, 58 (17, 19) --> (34, 2) [2, 2, 20, 12, 34, 2, 46, 26, 54, 58] 2, 2, 20, 12, 34, 2, 46, 26, 54, 58 (2, 2) --> (4) [4, 20, 12, 34, 2, 46, 26, 54, 58] 2, 4, 12, 20, 26, 34, 46, 54, 58 (2, 34) --> (4, 32) [4, 4, 12, 20, 26, 32, 46, 54, 58] 4, 4, 12, 20, 26, 32, 46, 54, 58 (32, 46) --> (64, 14) [4, 4, 12, 14, 20, 26, 54, 58, 64] 4, 4, 12, 14, 20, 26, 54, 58, 64 (26, 58) --> (52, 32) [4, 4, 12, 14, 20, 32, 52, 54, 64] 4, 4, 12, 14, 20, 32, 52, 54, 64 (32, 52) --> (64, 20) [4, 4, 12, 14, 20, 20, 54, 64, 64] 4, 4, 12, 14, 20, 20, 54, 64, 64 (64, 64) --> (128) [4, 4, 12, 14, 20, 20, 54, 128] 4, 4, 12, 14, 20, 20, 54, 128 (4, 20) --> (8, 16) [4, 8, 12, 14, 16, 20, 54, 128] 4, 8, 12, 14, 16, 20, 54, 128 (16, 20) --> (32, 4) [4, 4, 8, 12, 14, 32, 54, 128] 4, 4, 8, 12, 14, 32, 54, 128 (32, 54) --> (64, 22) [4, 4, 8, 12, 14, 22, 64, 128] 4, 4, 8, 12, 14, 22, 64, 128 (8, 12) --> (16, 4) [4, 4, 4, 14, 16, 22, 64, 128] 4, 4, 4, 14, 16, 22, 64, 128 (16, 22) --> (32, 6) [4, 4, 4, 6, 14, 32, 64, 128] 4, 4, 4, 6, 14, 32, 64, 128 (4, 4) --> (8) [4, 6, 8, 14, 32, 64, 128] 4, 6, 8, 14, 32, 64, 128 (8, 14) --> (16, 6) [4, 6, 6, 16, 32, 64, 128] 4, 6, 6, 16, 32, 64, 128 (4, 6) --> (8, 2) [2, 6, 8, 16, 32, 64, 128] 2, 6, 8, 16, 32, 64, 128 (2, 6) --> (4, 4) [4, 4, 8, 16, 32, 64, 128] [4, 4, 8, 16, 32, 64, 128]: yes, it is of the form (x, x, 2x, 4x, 8x, …)

My program has no problem solving this entry but I am not sure if the program can handle all possible sequences correctly.

]]>Timing: 0.6ms !

# Handle duplicate numbers in the layout, according to the rule # It updates global 'layout' as needed and returns its length (to be compared to the previous one) def handle_duplicates(): global layout l0 = 0 while 1: l = len(layout) if l == l0: break # No (other) change l0 = l for i in range(l-1): if layout[i] in layout[i+1:]: a = layout[i]; del layout[i] a = layout.index(a); layout[a] *= 2; break # Repeat the process return l layouts = [ ['A', [17, 4, 5, 5, 1, 2, 3, 15, 12]], # Solved ['B', [17, 4, 5, 5, 9]], ['C', [51, 72, 57, 78, 78, 48]], # Solved ['D', [1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 23, 15]] # Solved ] round = 0 while 1: round += 1; layouts2 = [] # This will hold the states of all layouts that are not solved in this round, to be used in next for i in range(len(layouts)): layout = layouts[i][1] if round == 1: end_states = [] l = handle_duplicates() # Start with handling duplicates first while 1: l0 = l; change = 0 for j in range(l-1): a,b = layout[j],layout[j+1] if a > b: a,b = b,a # Important! layout[j],layout[j+1] = a*2,b-a l = handle_duplicates() if l != l0: change = 1; break if l == 1 or not change: break letter = layouts[i][0] if l == 1: print("Layout '%s': Final state: %s (Solved in round #%d)" % (letter, layout, round)) else: lst = sorted(layout) if not lst in end_states: layouts2.append([letter,layout]); end_states.append(lst) if not layouts2: break # No more changes layouts = layouts2 # Set layouts for a new round

Output:

Layout ‘A’: Final state: [64] (Solved in round #1)

Layout ‘C’: Final state: [384] (Solved in round #2)

Layout ‘D’: Final state: [64] (Solved in round #4)

This Python program runs in 57ms. (Internal run time is 126µs).

**Run:** [ @replit ]

from enigma import (irange, inf, tri, div, nsplit, printf) # record total number of balls t = 0 for n in irange(1, inf): t += tri(n) # find 6 digit pyramidal numbers if t < 100000: continue if t > 999999: break # it needs to be three (different) digits repeated x = div(t, 1001) if x is None: continue # check the digits are different if len(set(nsplit(x))) != 3: continue # output the solution printf("P(3, {n}) = {t}")

**Solution:** There are 620620 balls in the pyramid.

So there are 154 layers.

We can calculate the *n*th *r*-gonal pyramidal number directly(*n* ≥ 0; *r* ≥ 3):

P(r, n) = n (n + 1) ((r − 2)n − r + 5) / 6

>>> P = lambda r, n: div(n * (n + 1) * ((r - 2) * n - r + 5), 6) >>> P(3, 154) 620620]]>

It runs in 103ms, and I have run it many times without seeing it fail to solve one one the solvable sequences.

import random from enigma import printf N = 100 # number of runs to try G = 100 # number of goes at each sequence # sequences to (attempt to) solve seqs = dict( A=(17, 4, 5, 5, 1, 2, 3, 15, 12), B=(17, 4, 5, 5, 9), C=(51, 72, 57, 78, 78, 48), D=(1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 23), X=(1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 23) + (15,), ) # choose a pair from <s> def choose(s): n = len(s) if n > 1: i = random.randrange(n) while True: j = random.randrange(n) if j != i: return (s[i], s[j]) # update sequence <s>, removing items in <ds>, adding items in <xs> def update(s, ds, xs): s = list(s) for d in ds: s.remove(d) s.extend(xs) s.sort() return tuple(s) # solve sequence <s>, in <G> moves (or fewer) def solve(s): k = 0 while k < G: # choose a pair of elements to process n = len(s) if n == 1: return k (x, y) = choose(s) if x == y: s = update(s, [x, y], [x + y]) else: if y < x: (x, y) = (y, x) s = update(s, [x, y], [x + x, y - x]) k += 1 n = 0 while n < N and seqs: for k in sorted(seqs.keys()): s = tuple(sorted(seqs[k])) r = solve(s) if r is not None: printf("[{k}] solved {s} [steps = {r}]") del seqs[k] n += 1 for k in sorted(seqs.keys()): s = tuple(sorted(seqs[k])) printf("[{k}] failed {s}")]]>

Right. ‘D’ is still not solved with my code if I add 15 to it, whereas it is solved with yours. I wonder if the sum of the number of piles can determine whether a layout/sequence is solvable or not. And if so, why?

I also wonder if there are other “solvable” cases in which neither your code can solve …

***

BTW, in trying to improve my code I found three mistakes (Lines 30, 46 and 49). But I won’t re-post it since anyway it is proved not able to handle all the cases …

]]>Well done on your analytical solution. And also on getting the expressions to work first time!

]]>Proposition “person A plays the role of B” is designated as .

All 5 conditions can be written using logical material condition operator, as follows:

Material condition operator can be replaced with disjunction using the formula: . We then get:

Simplified using De Morgan’s law:

From this equation we see that all conjuncts must be true.

The 3rd conjunct, , and cannot be both true at the same time.

because the same person cannot play two roles (), and the same role cannot be played by two different persons ().

The 3rd conjunct must be true, hence must be false. That means we can remove it from 4th conjunct:

Now, we can similarly remove and , because they contradict 4th conjunct.

Now, we can remove , because it contradicts 5th conjunct, and , because it contradicts 1st conjunct.

Now we know that Jane plays Dorothree, so both and are false. Also, second conjunct can be removed by applying absorption law:

And now we can remove because it contradicts .

So we get the answer: Kasey plays Square Crow, Jane plays Dorothree, Nicky plays Ten Man, Leah plays Cowardly Line, and Megan is left with with the role of Wicked Witch of the Word Problems.

Quite interesting and constructive analysis, indeed.

]]>@alkisp: Thanks for posting your code.

I did wonder about looking for a “greedy” strategy that would give accurate results, as it is often a faster approach than searching.

Of course, if such a strategy finds a solution then we know the initial sequence is solvable (although we don’t necessarily find the minimal number of steps). However if it doesn’t find a solution then we don’t know for sure if the sequence is solvable or not.

For example, if we add a pile of 15 matches to sequence **D**, to bring the total number of matches to 64, then the sequence becomes solvable (in 17 steps). But this is not found by the strategy used in your program.

(I did think sequence **D** was a bit strange. Usually I expect the sequences to get progressively “harder” in this type of puzzle. But in this case we can see that **D** is not viable, simply by observing that in total there is an odd number of matches, so the final step is not possible. I wondered if, in fact, an odd term had been accidentally excluded when the puzzle was originally published).

But, in the end I found the breadth-first search working from both ends produces results in a reasonable time, and is guaranteed to be accurate and find the minimal number of steps.

As a side note, for readability you might want to avoid using *l* (= lower case L) as a variable name in programs where possible, as it can be easily confused with *1* (= digit one) in some fonts.

# Handle duplicate numbers in the layout, according to the rule # It updates global 'layout' as needed and returns its length (to be compared to the previous one) def handle_duplicates(): global layout l0 = 0 while 1: l = len(layout) if l == l0: break # No (other) change l0 = l for i in range(l-1): if layout[i] in layout[i+1:]: a = layout[i]; del layout[i] a = layout.index(a); layout[a] *= 2; break # Repeat the process return l layouts = [ ['A', [17, 4, 5, 5, 1, 2, 3, 15, 12]], # Solved ['B', [17, 4, 5, 5, 9]], ['C', [51, 72, 57, 78, 78, 48]], # Solved ['D', [1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 23]] ] round = 0 while 1: round += 1; layouts2 = [] # This will hold the states of all layouts that are not solved in this round, to be used in next for i in range(len(layouts)): layout = layouts[i][1] l = handle_duplicates() # Start with handling duplicates first while 1: l0 = l; change = 0 for j in range(l-1): a,b = layout[j],layout[j+1] if a > b: a,b = b,a # Important! layout[j],layout[j+1] = a*2,b-a l = handle_duplicates() if l != l0: change = 1; break if l == 1 or not change: break letter = layouts[i][0] if l == 1: print("Layout '%s': Final state: %s (Solved in round #%d)" % (letter, layout, round)) else: layouts2.append([letter,layout]) # Add the end state of the layout to the list for a possible next round if len(layouts) == len(layouts2): break # No more changes layouts = layouts2 # Set layouts for a new round]]>