1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from itertools import combinations as cmb p = (3, 5, 7, 11, 13, 17, 19) # primes to 400 (this is overkill) ps = p + tuple(x for x in range(21, 400, 2) if all(x % pp for pp in p)) p12 = set(ps[:12]) # consider Ann's tiles for A in cmb(p12, 4): # is the sum of any 2 of Ann's tiles + starting number prime? if any((3 + sum(a2)) in ps for a2 in cmb(A, 2)): continue # consider tiles played by Beth and Chloe in the first round for BC in cmb(p12 - set(A), 4): # new starting number after Beth and Chloe play 2 tiles each ns = sum(BC) + 3 # are sum of all 4 + start and the sum any two + start prime? if (ns in ps and any((3 + sum(bc)) in ps for bc in cmb(BC, 2)) and # is the sum of any 2 of Ann's tiles + new starting number not prime? all((ns + sum(a2)) not in ps for a2 in cmb(A, 2))): print("Ann's tiles", A, '; Tiles played by Beth and Chloe', sorted(BC)) |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from itertools import combinations # odd primes below 185 prms = {3, 5, 7, 11, 13} prms |= {n for n in range(17, 185, 2) if all(n % p for p in prms)} # the tiles and the initial total tiles = [n for n in range(3, 43, 2) if n in prms] t1 = 3 # choose four tiles for Ann for ta in combinations(tiles, 4): # no two give a prime when their sum is added to the current total if not any(t1 + sum(t) in prms for t in combinations(ta, 2)): # choose two tiles for Beth to play for tb in combinations((tsb := [t for t in tiles if t not in ta]), 2): # which give a prime when their sum is added to the current total if (t2 := t1 + sum(tb)) in prms: # choose two tiles for Chloe to play for tc in combinations((tsc := [t for t in tsb if t not in tb]), 2): # which give a prime when their sum is added to the current total if (t3 := t2 + sum(tc)) in prms: # Ann cannot now play if not any(t3 + sum(t) in prms for t in combinations(ta, 2)): r = sorted(tb + tc) print(f"{r} (A = {ta}, {t1} + B{tb} -> {t2} + C{tc} -> {t3}") |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# return divisors of <n> between <mn> and <mx> (except 1 and n) divs = lambda n, mn, mx: [d for d in range(mn, n // 2 + 1) if n % d == 0 and d <= mx] # - only the integer solution is reported as the non-integer solution # is invalid (less than 148 cm) # - difference between ceiling and floor horizontal jamming point/wall # distances is equal to sqrt((h - 150)^2 - 600) --> h >= 175 # - a low ceiling height, so less than the normal 8 feet (243,8 cm) ? # - formula after analysis: (55 - (10 * h - 625)**.5) * h = 3700 for h in divs(3700, 175, 244): if 10 * h - 625 == (3700 // h - 55)**2: print(f"answer: the cupboard height is {h} cm") |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# <cd> is the integer cupboard depth cd = 0 while True: cd += 1 # <ch> is the integer cupboard height ch = cd + 148 # <hc2w> is the integer horizontal distance from the top # of the cupboard to the wall hc2w = ch - 125 # <vc2c> is the (possibly non-integer) vertical distance # from the point where the cupboard touches the back wall # to the ceiling vc2c = (ch ** 2 - hc2w ** 2) ** (1 / 2) # <vc2f> is the (possibly non-integer) vertical distance # from the point where the cupboard touches the back wall # to the floor vc2f = cd * hc2w / ch # the floor to ceiling distance is 2cm more than the cupboard height if vc2c + vc2f == ch + 2: print(f"Cupboard (d x h) {cd}cm x {ch}cm, room height {ch + 2}cm " f"({vc2c = }cm, {vc2f = }cm).") break |

For those who prefer manual solutions, I have posted mine here.

]]>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
from itertools import product # https://stackoverflow.com/a/47712620, valid for numbers 1...3999 toRoman = lambda n: ("".join(y for x in [ ["MMMDCLXVI"[8 - 2 * len(str(n)):][int(d) + 2 * i] for d in ["", "2", "22", "222", "21", "1", "12", "122", "1222", "20"][int(c)] ] for i, c in enumerate(str(n))] for y in x) ) # Denote cube faces by Up, Down, Front, Back, Left, Right. The face # sequences for the three numbers are then (depending on the rotation # direction): # # (1) FRBL or FLBR # (2) URDL or ULDR # (3) FUBD or FDBU # # We are going to see the L and R faces rotated 0 and 90 degrees so # they have to hold X as the only roman digit that remains valid in # both these orientations. Hence the direction of rotation does not # matter for the first two numbers. # B and D have to be I or X as they reside between L and R, (1) and (2) # a map of the possible letter values for the six faces # U and F can never be C/D/M (less than a hundred requirement), (2) and (3) f_to_r = {'F':'XL', 'U':'XL', 'B':'IX', 'D':'IX', 'L':'X', 'R':'X'} # dictionary of 4-character Roman numbers d = {r: n for n in range(1, 81) if len(r := toRoman(n)) == 4} # test all valid combinations of the front, right, back and left face letters for F, R, B, L in product(f_to_r['F'], f_to_r['R'], f_to_r['B'], f_to_r['L']): if (FRBL := ''.join((F, R, B, L))) not in d: continue r1 = d[FRBL] # test valid letter combinations for the top and bottom faces for U, D in product(f_to_r['U'], f_to_r['D']): if (URDL := ''.join((U, R, D, L))) not in d: continue r2 = d[URDL] # this condition is not given in the teaser but is needed # to avoid multiple solutions if r1 == r2: continue r3 = [] # try to convert the two possible third roman numerals if (FDBU := ''.join((F, D, B, U))) in d: r3.append((d[FDBU], 1)) if (FUBD := ''.join((F, U, B, D))) in d: r3.append((d[FUBD], 2)) # we must have only one valid third number if not r3 or (len(r3) == 2 and r3[0][0] != r3[1][0]): continue if r2 + r3[0][0] < 100: print(f"The third roman numeral was {FDBU if r3[0][1] == 1 else FUBD}") |

I see more shortcuts. B/ D have to reside between L and R (1)/(2) so they can only be I or X.

U and F can never be C/D/M (less than a hundred requirement), (2) and (3). ]]>

1 2 3 |
sol = {(i, j): [] for i in range(8, 11) for j in range(2)} |

solves the ‘defaultdict’, ‘setdefault’, ‘get’ conundrum, makes import of defaultdict unnecessary and results in a speed improvement.

]]>of defaultdict. Now I know this choice is not without consequences so I will

start using defaultdict again. A good find by Brian. ]]>

It is a speed issue. The code above runs in 36 milliseconds on my laptop.

If I use the code:

1 2 3 4 |
k = len(str(prd := p * q * r)), prd % 2 lp2nt[k] = lp2nt.setdefault(k, set()) | {(p, q, r)} |

the run time is then 1.3 seconds!

The reason is that, when the setdefault version is used, the dictionary

on the left is a NEW dictionary every time and this takes time to set

up. In my version a new directory is only set up once, after which an

existing directory is simply modified.

This teaser turns out to be an extreme example of this sort of effect.

Your use of defauldict here avoids this problem completely (I was just

avoiding an import but since I needed one anyway this was pointless).