Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 8:39 am on 3 April 2026 Permalink | Reply
    Tags:   

    Teaser 2443: [Phone masts] 

    From The Sunday Times, 19th July 2009 [link]

    Six phone masts, A to F, are each 24 miles from the base station, and clockwise they form a hexagon, ABCDEF. In degrees, the angles in the hexagon are such that A is not more than B, which is not more than C; and so on. The angle at A is a prime; the angle at C is the average of those at A and E; the angle at D is the average of those at B and F; and the angle at E is a perfect square.

    What are the six angles, and how far is the base station from the line BD?

    This puzzle was originally published with no title.

    [teaser2443]

     
    • Jim Randell's avatar

      Jim Randell 8:40 am on 3 April 2026 Permalink | Reply

      In a hexagon the internal angles sum to 720°.

      In a cyclic hexagon the total sum of alternate internal angles is equal:

      A + B + C + D + E + F = 720°
      A + C + E = B + D + F = 360°

      Also C is the mean of A and E, and D is the mean of B and F, so:

      A + E = B + F = 240°
      C = D = 120°

      We are looking for a square E ≥ 120 such that (240 − E) is a prime number, and there are only a few candidates to check:

      E = 11² = 121; A = 119 = 7×17 (not prime)
      E = 12² = 144; A = 96 = (2^5)×3 (not prime)
      E = 13² = 169; A = 71 (prime)
      E = 14² = 196; A = 44 = (2^2)×11 (not prime)
      E = 15² = 225; A = 15 = 3×5 (not prime)

      There is only one viable (A, E) pair, so:

      A = 71°
      B = B
      C = 120°
      D = 120°
      E = 169°
      F = 240° − B

      Now B must lie between 71° and 120°, so:

      71° ≤ B ≤ 120° ⇒ 120° ≤ F ≤ 169°

      But F ≥ 169°, so we must have F = 169°, and so B = 71°.

      Hence the angles are fully determined:

      A = 71°
      B = 71°
      C = 120°
      D = 120°
      E = 169°
      F = 169°

      If the centre of the circle is X, and we say the angles subtended at X by the chords AB, BC, CD, DE, EF, FA are a, b, c, d, e, f, then we can show:

      a = 218° − u
      b = u
      c = 120° − u
      d = u
      e = 22° − u
      f = u

      To make the smallest angle as large as possible we can set u = 11°, to get the angles subtended at the centre to be (207°, 11°, 109°, 11°, 11°, 11°). Which gives a layout like this:

      (But we could make a valid hexagon for any real value 0° < u < 22°).

      In particular the angle subtended at the centre of the circle by the chord BD is b + c = (u + (120° − u)) = 120°.

      And so, considering the isosceles triangle BXD, the distance from the line BD to the centre of the circle is:

      d = 24 cos(120°/2)
      d = 24 cos(60°)
      d = 12

      Solution: The angles are: 71°, 71°, 120°, 120°, 169°, 169°. The minimum distance from the base station to the line BD is 12 miles.

      Like

  • Unknown's avatar

    Jim Randell 8:15 am on 31 March 2026 Permalink | Reply
    Tags:   

    Teaser 2442: [Gaming machine] 

    From The Sunday Times, 12th July 2009 [link]

    Our club’s gaming machine requires a player to choose two different non-zero digits. It then randomly chooses three two-figure numbers (not necessarily different) [that use only digits from that pair]. If the sum of the three numbers is a perfect square, the player wins that number of pounds.

    Mark, Hannah and Oliver each chose different pairs of digits, [although there was one specific digit that occurred in each pair]. All three were successful, and all won the same amount.

    Oliver [played again, this time using the digits from Mark’s pair and Hannah’s pair that he had not used previously]. With [this new pair], he won two different amounts.

    [What were the digits in Oliver’s original pair?]

    I have modified the puzzle text to try and make it clearer.

    This puzzle was originally published with no title.

    [teaser2442]

     
    • Jim Randell's avatar

      Jim Randell 8:16 am on 31 March 2026 Permalink | Reply

      The following Python program runs in 74ms. (Internal runtime is 644µs).

      from enigma import (
        irange, subsets, is_square, group, item, intersect, union, printf
      )
      
      # generate possible (<digits>, <prize>) values
      def generate():
        # consider 2 different non-zero digits
        for ds in subsets(irange(1, 9), size=2):
          # make the 4 possible 2-digit numbers
          ns = list(10*x + y for (x, y) in subsets(ds, size=2, select='M'))
          # choose 3 numbers, and look for a total that is a perfect square
          for ss in subsets(ns, size=3, select='R'):
            t = sum(ss)
            if not is_square(t): continue
            # return: (<digits>, <prize>)
            yield (ds, t)
      
      # collect possible chosen digits by prize
      g = group(generate(), f=item(0), by=item(1), fn=set)
      
      # find keys in group <g> that include the value <v>
      gfind = lambda g, v: (k for (k, vs) in g.items() if v in vs)
      
      # for each possible prize, look for 3 pairs that share a digit
      for (k, vs) in g.items():
        for ps in subsets(vs, size=3):
          for d in intersect(ps):
            printf("prize = {k}; shared digit = {d}; pairs = {ps}")
            # look for two non-shared digits that can give 2 different prizes
            for ds in subsets(sorted(union(ps).difference({d})), size=2):
              ks = list(gfind(g, ds))
              if len(ks) < 2: continue
              # Oliver's original numbers don't include the numbers in <ds>
              Os = list(p for p in ps if not intersect([ds, p]))
              printf("-> new = {ds} -> {ks}; original = {Os}")
            printf()
      

      Solution: Oliver’s original pair of digits were 4 and 7.

      If the digits chosen are (4, 7) then a prize of 225 may be won in the following way:

      (4, 7) → 74 + 74 + 77 = 225 = 15²

      And it is possible to win a prize of 225 with the following pairs:

      (1, 7) → 71 + 77 + 77 = 225
      (3, 9) → 39 + 93 + 93 = 225
      (4, 7) → 74 + 74 + 77 = 225
      (5, 7) → 75 + 75 + 75 = 225
      (5, 8) → 55 + 85 + 85 = 225

      The only set of 3 pairs that have a common digit are: (1, 7), (4, 7), (5, 7), which share the digit 7.

      So, Mark and Hannah’s pairs were (1, 7) and (5, 7) (in some order).

      And when Oliver played again he used the digits (1, 5), and won 2 different amounts:

      (1, 5) → 15 + 15 + 51 = 81 = 9²
      (1, 5) → 11 + 55 + 55 = 121 = 11²

      Like

    • GeoffR's avatar

      GeoffR 5:28 pm on 31 March 2026 Permalink | Reply

      # ST 2442 by Claude AI
      
      import math, time
      from itertools import combinations, product
      
      t = time.perf_counter()
      
      isqrt = math.isqrt
      sq = lambda n: isqrt(n) ** 2 == n
      
      def wins(pair):
        nums = {10*a + b for a in pair for b in pair}
        return {s for n1,n2,n3 in product(nums, repeat=3) if sq(s := n1+n2+n3)}
      
      pair_wins = {p: w for p in combinations(range(1,10), 2) if (w := wins(p))}
      pairs = list(pair_wins)
      
      for i,m in enumerate(pairs):
        for j,h in enumerate(pairs):
          if j<=i: continue
          for k,o in enumerate(pairs):
            if k in (i,j): continue
            if not (set(m) & set(h) & set(o)): continue
            if not (pair_wins[m] & pair_wins[h] & pair_wins[o]): continue
            od = set(o)
            mo = [d for d in m if d not in od]
            ho = [d for d in h if d not in od]
            if len(mo)==len(ho)==1:
              np = tuple(sorted([mo[0], ho[0]]))
              if np in pair_wins and len(pair_wins[np])==2:
                print(f"Oliver's pair: {o}  ({time.perf_counter()-t:.4f}s)")
      
      # Oliver's pair: (4, 7)  (0.0017s)
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 12:32 am on 29 March 2026 Permalink | Reply
    Tags:   

    Teaser 3314: Number unobtainable! 

    From The Sunday Times, 29th March 2026 [link] [link]

    Dyall recalls six-digit phone numbers as a sequence of two three-figure values. His wife, Belle, recalls them as a sequence of three two-figure values. Their courting-days’ phone numbers had no zeroes. For each of their numbers, Dyall recalled that the two three-figure values in order made a descending set of primes, while Belle recalled that the three two-figure values in order made a descending set of primes. Back then their town’s residential numbers never began with 7, 8 or 9.

    Dyall told Tim these things, but not the numbers. He then stated that choosing a digit at random to be told its value would give a one in three chance of Tim being able to work out Dyall’s old phone number with certainty. In Belle’s case the chance was greater still.

    Give Belle’s old phone number.

    [teaser3314]

     
    • Jim Randell's avatar

      Jim Randell 12:38 am on 29 March 2026 Permalink | Reply

      This Python program uses the [[ SubstitutedExpression ]] solver from the enigma.py library to find candidate phone numbers.

      It runs in 82ms. (Internal runtime is 5.8ms).

      from enigma import (SubstitutedExpression, irange, multiset, unzip, printf)
      
      # generate candidate phone numbers
      def generate():
        # consider the 6-digit phone number: ABCDEF
        exprs = [
          # AB CD EF form a descending sequence of primes
          "is_prime(AB)", "is_prime(CD)", "is_prime(EF)",
          "AB > CD", "CD > EF",
          # ABC DEF form a descending sequence of primes
          "is_prime(ABC)", "is_prime(DEF)",
          "ABC > DEF",
        ]
        p = SubstitutedExpression(
          exprs,
          distinct="",
          # numbers never contained 0
          digits=irange(1, 9),
          # numbers never began with 7, 8, 9
          d2i={7: "A", 8: "A", 9: "A"},
          # return the sequence of digits
          answer="(A, B, C, D, E, F)",
        )
        return p.answers(verbose=0)
      
      # find candidate numbers
      nss = list(generate())
      
      # count digits by position
      pos = list(multiset.from_seq(cols) for cols in unzip(nss))
      
      # for each number how many of the digits allow it to be uniquely identified?
      for ns in nss:
        k = sum(pos[i].count(d) == 1 for (i, d) in enumerate(ns))
        # identify numbers
        p = ("[Dyall]" if k == 2 else "[Belle]" if k > 2 else "")
        printf("{ns} -> {k}/6 {p}")
      

      Solution: [To Be Revealed]

      Like

      • Jim Randell's avatar

        Jim Randell 12:43 pm on 29 March 2026 Permalink | Reply

        Or, without using [[ SubstitutedExpression ]].

        This Python program has an internal runtime of 323µs.

        from enigma import (primes, subsets, multiset, unzip, printf)
        
        # [optional] calculate primes < 700
        primes.expand(699)
        
        # generate candidate phone numbers
        def generate():
          # AB CD EF form a descending sequence of primes with AB < 70
          for (EF, CD, AB) in subsets(primes.between(11, 69), size=3):
            # the digits will necessarily be non-zero, and A < 7
            (C, D) = divmod(CD, 10)
            # ABC DEF form a descending sequence of primes
            (ABC, DEF) = (10*AB + C, 100*D + EF)
            if not (ABC > DEF and DEF in primes and ABC in primes): continue
            # return the phone number as a sequence of digits
            yield divmod(AB, 10) + (C, D) + divmod(EF, 10)
        
        # find candidate numbers
        nss = list(generate())
        
        # count digits by position
        pos = list(multiset.from_seq(cols) for cols in unzip(nss))
        
        # for each number how many of the digits allow it to be uniquely identified?
        for ns in nss:
          k = sum(pos[i].count(d) == 1 for (i, d) in enumerate(ns))
          # identify numbers
          p = ("[Dyall]" if k == 2 else "[Belle]" if k > 2 else "")
          printf("{ns} -> {k}/6 {p}")
        

        Like

    • GeoffR's avatar

      GeoffR 11:16 am on 29 March 2026 Permalink | Reply

      Claude AI optimised its own solution when requested to produce a fast solution – 3.5 msec on my I9 Desktop PC. Seemed quite a complex list comprehension for the candidates.

      # ST 3314 by Claude AI 
      
      import time
       
      def sieve(n):
          is_prime = bytearray([1]) * (n + 1)
          is_prime[0] = is_prime[1] = 0
          for i in range(2, int(n**0.5) + 1):
              if is_prime[i]:
                  is_prime[i*i::i] = bytearray(len(is_prime[i*i::i]))
          return is_prime
       
      t0 = time.perf_counter()
      is_prime = sieve(999)
      p3 = [p for p in range(101, 1000) if is_prime[p] and '0' not in str(p)]
       
      candidates = [
          s for a in p3 for b in p3
          if a > b and str(a)[0] not in "789"
          and '0' not in (s := f"{a}{b}")
          and int(s[:2]) > int(s[2:4]) > int(s[4:])
          and all(is_prime[int(s[i:i+2])] for i in (0, 2, 4))
      ]
       
      det = lambda n: sum(sum(c[i] == n[i] for c in candidates) == 1 for i in range(6))
       
      belle = next(n for n in candidates if det(n) >  2)
       
      print(f"Belle's old phone number was {belle} [{(time.perf_counter()-t0)*1000:.2f} ms]")
      
      
      

      Like

    • Frits's avatar

      Frits 5:03 pm on 29 March 2026 Permalink | Reply

      from itertools import combinations, compress
      from functools import reduce
      
      def primesbelow(n):
        # rwh_primes1v2(n):
        """ Returns a list of primes < n for n > 2 """
        sieve = bytearray([True]) * (n // 2 + 1)
        for i in range(1, int(n ** 0.5) // 2 + 1):
          if sieve[i]:
            sieve[2 * i * (i + 1)::2 * i + 1] = \
            bytearray((n // 2 - 2 * i * (i + 1)) // (2 * i + 1) + 1)
        return [2, *compress(range(3, n, 2), sieve[1:])]
        
      P = primesbelow(1000)
      P2 = [p for p in P if 9 < p < 70]
      P3 = {p for p in P if 99 < p < 700}
      
      # positions
      cols = [[] for _ in range(6)]
      ns = []
      
      # Dyall and Belle's numbers have to look like pqrstu or .ooo.o where o is odd
      for pq, rs in combinations(P2[::-1][:-1], 2):
        r, s = divmod(rs, 10) 
        if r % 2 == 0: continue
        if 10 * s >= pq: continue
        if (pqr := 10 * pq + r) not in P3: continue
        s100 = 100 * s
        for tu in P2:
          if tu >= rs: break  
          
          if (stu := s100 + tu) not in P3: continue 
          if pqr <= stu: continue
          # store possible phone numbers
          ns.append(dgts := [y for x in [pq, rs, tu] for y in divmod(x, 10)])
          for i, c in enumerate(dgts):
            cols[i] += [c]
      
      # for how many positions can Tim work out the phone number      
      cnts = [sum([col.count(col[r]) == 1 for col in cols]) for r in range(len(ns))]
         
      assert 2 in cnts # Dyall
      # print Belle's number          
      print(f"answer: {' or '.join(str(''.join(str(x) for x in ns[i])) 
                              for i, c in enumerate(cnts) if c > 2)}")
      

      Like

  • Unknown's avatar

    Jim Randell 8:11 am on 27 March 2026 Permalink | Reply
    Tags:   

    Teaser 2449: [Numbers of marbles] 

    From The Sunday Times, 30th August 2009 [link]

    George and Martha gave their grandson nine plastic cups, numbered 1 to 9, and 45 marbles. They asked him to place one marble in cup 1, two in cup 2, etc.

    A while later he had indeed used all the marbles, placing a different number in each cup, but no cup contained the correct number. The child explained the task was too easy, so, for each cup, he had multiplied its number by George and Martha’s two-digit house number, added up the product’s digits, looked at the units digit of that sum, and placed that number of marbles in the cup.

    What is the house number?

    This puzzle was originally published with no title.

    [teaser2449]

     
    • Jim Randell's avatar

      Jim Randell 8:12 am on 27 March 2026 Permalink | Reply

      This Python program runs in 69ms. (Internal runtime is 379µs).

      from enigma import (irange, dsum, printf)
      
      # solve for house number h
      def solve(h):
        ns = list()
        for i in irange(1, 9):
          # perform the procedure
          n = dsum(i * h) % 10
          # values cannot be in the correct position, or repeated
          if n == i or n in ns: return
          ns.append(n)
        return ns
      
      # consider 2-digit house numbers
      for h in irange(10, 99):
        ns = solve(h)
        # check 45 marbles are used
        if ns and sum(ns) == 45:
          # output solution
          printf("{h} -> {ns}")
      

      Solution: The house number is 94.

      Which gives rise to the following sequence of numbers:

      1 → 1 × 94 = 94 → 9 + 4 = 13 → 3
      2 → 2 × 94 = 188 → 1 + 8 + 8 = 17 → 7
      3 → 3 × 94 = 282 → 2 + 8 + 2 = 12 → 2
      4 → 4 × 94 = 376 → 3 + 7 + 6 = 16 → 6
      5 → 5 × 94 = 470 → 4 + 7 + 0 = 11 → 1
      6 → 6 × 94 = 564 → 5 + 6 + 4 = 15 → 5
      7 → 7 × 94 = 658 → 6 + 5 + 8 = 19 → 9
      8 → 8 × 94 = 752 → 7 + 5 + 2 = 14 → 4
      9 → 9 × 94 = 846 → 8 + 4 + 6 = 18 → 8

      Like

    • Ruud's avatar

      Ruud 8:43 am on 27 March 2026 Permalink | Reply

      import istr
      
      istr.repr_mode("int")
      
      for house_number in istr.range(10, 100):
          amounts = [sum(cup_number * house_number)[-1] for cup_number in range(1, 10)]
          if sum(amounts) == 45 and all(amount != cup_number for cup_number, amount in enumerate(amounts, 1)):
              print(house_number, amounts)
      

      Like

      • Jim Randell's avatar

        Jim Randell 9:07 am on 27 March 2026 Permalink | Reply

        @Ruud: Does this ensure that there are a different number of marbles in each cup?

        Like

        • Ruud's avatar

          Ruud 9:22 am on 27 March 2026 Permalink | Reply

          You are right.
          The code should read

          import istr
          
          
          istr.repr_mode("int")
          
          for house_number in istr.range(10, 100):
              amounts = [sum(cup_number * house_number)[-1] for cup_number in range(1, 10)]
              if sum(amounts) == 45 and all(amount != cup_number for cup_number, amount in enumerate(amounts, 1)) and len({*amounts}) == 9:
                  print(house_number, amounts)
          

          Like

  • Unknown's avatar

    Jim Randell 7:33 am on 24 March 2026 Permalink | Reply
    Tags:   

    Brain-Teaser 968: Friends and neighbours 

    From The Sunday Times, 8th February 1981 [link]

    My old school friends, Rose and May, both live on the sunny side of Woodland Grove in a row of five houses. I know that May lives with her parents, Mr and Mrs Joiner, in the house called “The Oaks”, but Rose had never told me her surname nor the name of her house.

    I wanted to send both of them invitations to my 21st birthday party, so I asked the local paper boy if he knew which house Rose lived in or the name of her parents. He was not sure, but came up with the following facts:

    1. Each of the five houses is occupied by a married couple with one unmarried daughter.
    2. The last of the five houses on the right is called “Fir Trees”.
    3. The Turner family lives in the house immediately to the right of the house called “The Willows”.
    4. The Carpenters live next door but one to the house called “Silver Birches”.
    5. The second house from the left is the home of the Sawyer family.
    6. Cherry is the daughter of the couple in the middle house.
    7. Ivy lives with her parents at The Elms”.
    8. Hazel is the daughter of Mr and Mrs Woodman.

    From this information I was able to deduce Rose’s surname and the name of her house.

    What are they?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser968]

     
    • Jim Randell's avatar

      Jim Randell 7:34 am on 24 March 2026 Permalink | Reply

      Here is a solution using the [[ SubstitutedExpression ]] solver from the enigma.py library.

      It runs in 80ms. (Internal runtime of the generated code is 85µs).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # allocate house numbers 1-5 to:
      --digits="1-5"
      # house names:
      --macro="@Elm = A"
      --macro="@Fir = B"
      --macro="@Oak = C"
      --macro="@Bir = D"
      --macro="@Wil = E"
      # family name:
      --macro="@Car = F"
      --macro="@Joi = G"
      --macro="@Saw = H"
      --macro="@Tur = I"
      --macro="@Woo = J"
      # daughter:
      --macro="@Che = K"
      --macro="@Haz = L"
      --macro="@Ivy = M"
      --macro="@May = N"
      --macro="@Ros = P"
      --distinct="ABCDE,FGHIJ,KLMNP"
      
      # "May Joiner lives in Oak"
      "@May = @Joi"
      "@May = @Oak"
      
      # 2: "House 5 is Fir"
      "@Fir = 5"
      
      # 3: "Turners live in the house immediately right of Willow"
      "@Wil + 1 = @Tur"
      
      # 4: "Carpenters live next door but one to Birch"
      "abs(@Car - @Bir) = 2"
      
      # 5: "Sawyers are house 2"
      "@Saw = 2"
      
      # 6: "Cherry is in house 3"
      "@Che = 3"
      
      # 7: "Ivy is in Elm"
      "@Ivy = @Elm"
      
      # 8: "Hazel Woodman"
      "@Haz = @Woo"
      
      --solution="ABCDEFGHIJKLMNP"
      --template=""
      --verbose="1-W"
      

      And here is a Python wrapper to prettify the output:

      from enigma import (SubstitutedExpression, irange, printf)
      
      # load the puzzle
      p = SubstitutedExpression.from_file(["{dir}/teaser968.run"])
      
      # link symbols to values
      s2h = dict(A="The Elms", B="Fir Trees", C="The Oaks", D="Silver Birches", E="The Willows")
      s2f = dict(F="Carpenter", G="Joiner", H="Sawyer", I="Turner", J="Woodman")
      s2n = dict(K="Cherry", L="Hazel", M="Ivy", N="May", P="Rose")
      
      # solve the puzzle
      for s in p.solve(verbose=0):
        # fill out the slots
        (house, family, name) = (dict((s[k], v) for (k, v) in m.items()) for m in (s2h, s2f, s2n))
        # output the solution
        for i in irange(1, 5):
          (h, f, n) = (m.get(i, '???') for m in (house, family, name))
          printf("{i}: {n} {f}, \"{h}\"")
        printf()
      

      Solution: Rose is Rose Sawyer, she lives at “The Willows”.

      The complete solution is:

      1: Ivy Carpenter, “The Elms”
      2: Rose Sawyer, “The Willows”
      3: Cherry Turner, “Silver Birches”
      4: May Joiner, “The Oaks”
      5: Hazel Woodman, “Fir Trees”

      Like

  • Unknown's avatar

    Jim Randell 6:14 am on 22 March 2026 Permalink | Reply
    Tags:   

    Teaser 3313: Rod’s rods 

    From The Sunday Times, 22nd March 2026 [link] [link]

    Rodney has a set of small identical rods, which he uses to display numbers in “calculator” style. The “0” requires six rods, the “1” requires two rods, the “2”, “3” and “5” require five each, the “4” requires four, the “6” and “9” require six, the “7” requires three and the “8” requires seven.

    He recently showed me three numbers, each made by using the whole set. The lowest was a three-figure perfect square, the next was a palindromic prime, and the highest was a four-figure prime with its four different digits increasing from left to right.

    What were those three numbers?

    [teaser3313]

     
    • Jim Randell's avatar

      Jim Randell 6:30 am on 22 March 2026 Permalink | Reply

      This Python program collects possible candidate numbers in the three categories, grouping them by the total number of segments used. It then looks for a total common to each of the categories, and finds suitable numbers from each of the categories.

      It runs in 75ms. (Internal runtime is 5.9ms).

      from enigma import (
        enigma, defaultdict, primes, nsplit, powers,
        intersect, cproduct, true, is_palindrome, printf
      )
      
      # number of segments per digit
      #      0  1  2  3  4  5  6  7  8  9
      seg = [6, 2, 5, 5, 4, 5, 6, 3, 7, 6]
      
      # collect numbers from <ns> by the total count of segments used
      # where the digits of the number satisfy function <fn>
      # returns dict: <segment-count> -> <numbers>
      def collect(ns, fn=true):
        r = defaultdict(list)
        for n in ns:
          ds = nsplit(n)
          if fn(ds):
            k = sum(seg[d] for d in ds)
            r[k].append(n)
        return r
      
      # look for strictly increasing sequences
      is_increasing = lambda ns: enigma.is_increasing(ns, strict=1)
      
      # collect number categories by total segment count
      nss = [
        # first number (3-digit perfect squares)
        collect(powers(10, 31, 2)),
        # second number (3- and 4-digit palindromic primes)
        collect(primes.between(100, 9999), is_palindrome),
        # third number (4-digit primes, with digits in increasing order)
        collect(primes.between(1000, 9999), is_increasing),
      ]
      
      # look for common total segments
      for k in intersect(ns.keys() for ns in nss):
        for ss in cproduct(ns[k] for ns in nss):
          if is_increasing(ss):
            # output solution
            printf("{k} segments -> {ss}")
      

      Solution: The three numbers are: 225, 353, 1237.

      The only total number of segments that can make numbers in each category is 15. And we have the following possibilities with 15 segments:

      first number = 225, 484, 676
      second number = 353
      third number = 1237

      Since the numbers must be in increasing order, there is only one possible arrangement.

      Like

      • Jim Randell's avatar

        Jim Randell 4:03 pm on 24 March 2026 Permalink | Reply

        By noting that all 4-digit palindromes are multiples of 11:

        XYYX = 1001.X + 110.Y = 11(91.X + 10.Y)

        We can see that the second number must be a 3-digit prime, allowing us to test fewer candidates, and this provides a modest improvement in the runtime of my program above (down to 3.9ms). (I’m not sure if other programs posted that only check 3-digit palindromes use this fact, or just missed the possibility that the second number could have 4 digits).

        Also, as noted in Geoff’s program below it is faster to generate 3-digit palindromes and 4-digit numbers with increasing digits and then test them for primality, instead of starting with primes.

        The following (more compact) variation on my original program has an internal runtime of 625µs.

        from enigma import (
          irange, subsets, is_prime, nsplitter, nconcat, group,
          powers, npalindromes, intersect, cproduct, is_increasing, printf
        )
        
        # number of segments per digit
        #      0  1  2  3  4  5  6  7  8  9
        seg = [6, 2, 5, 5, 4, 5, 6, 3, 7, 6]
        
        # count the total number of segments used in a number
        segs = lambda n: sum(seg[d] for d in nsplitter(n))
        
        # collect number categories by total segment count
        collect = lambda ns: group(ns, by=segs)
        nss = [
          # first number (3-digit perfect squares)
          collect(powers(10, 31, 2)),
          # second number (3-digit palindromic primes) [there are no 4-digit palindromic primes]
          collect(n for n in npalindromes(3) if is_prime(n)),
          # third number (4-digit primes, with digits in increasing order)
          collect(n for n in subsets(irange(1, 9), size=4, fn=nconcat) if is_prime(n)),
        ]
        
        # look for common total segments
        for k in intersect(ns.keys() for ns in nss):
          for ss in cproduct(ns[k] for ns in nss):
            if is_increasing(ss, strict=1):
              # output solution
              printf("{k} segments -> {ss}")
        

        Like

    • Ruud's avatar

      Ruud 7:19 am on 22 March 2026 Permalink | Reply

      import peek
      import istr
      
      
      rods = [6, 2, 5, 5, 4, 5, 6, 3, 8, 6]
      
      for n0 in istr.squares(100, 1000):
          for n1 in istr.primes(n0, 10000):
              if n1.is_palindrome():
                  for n2 in istr.primes(max(n1, 1000), 10000):
                      if n2[0] < n2[1] < n2[2] < n2[3]:
                          if len(set(sum(rods[int(i)] for i in n) for n in (n0, n1, n2))) == 1:
                              peek(n0, n1, n2)
      

      Like

    • ruudvanderham's avatar

      ruudvanderham 10:23 am on 22 March 2026 Permalink | Reply

      As a one liner:

      import istr
      
      print(
          *(
              (n0, n1, n2)
              for n0 in istr.squares(100, 1000)
              for n2 in filter(istr.is_increasing, istr.primes(1000, 10000))
              for n1 in filter(istr.is_palindrome, istr.primes(n0 + 1, n2))
              if len({sum([6, 2, 5, 5, 4, 5, 6, 3, 8, 6][i] for i in map(int, n)) for n in (n0, n1, n2)}) == 1
          )
      )
      

      Like

    • GeoffR's avatar

      GeoffR 5:12 pm on 22 March 2026 Permalink | Reply

      I used Claude AI to find to refine its original solution, and to produce shorter, faster code. This solution ran in 37 msec on my I7 laptop.

      # Teaser 3313 -  Rod’s rods - Code by Claude AI
      
      from itertools import combinations
      from time import perf_counter
      
      t0 = perf_counter()
      
      # --- Sieve of Eratosthenes ---
      def sieve(limit):
          is_prime = bytearray([1]) * (limit + 1)
          is_prime[0] = is_prime[1] = 0
          for i in range(2, int(limit**0.5) + 1):
              if is_prime[i]:
                  is_prime[i*i::i] = bytearray(len(is_prime[i*i::i]))
          return is_prime
      
      RODS = [6,2,5,5,4,5,6,3,7,6]
      is_prime = sieve(9999)
      
      # Rod counts via integer arithmetic (no str() conversion)
      def rc3(n): return RODS[n%10] + RODS[(n//10)%10] + RODS[n//100]
      def rc4(n): return RODS[n%10] + RODS[(n//10)%10] + RODS[(n//100)%10] + RODS[n//1000]
      
      # 3-digit perfect squares (10²..31²)
      squares = {}
      for r in range(10, 32):
          n = r * r
          squares.setdefault(rc3(n), []).append(n)
      
      # 3-digit palindromic primes (form aba)
      palprimes = {}
      for a in range(1, 10):
          for b in range(0, 10):
              n = 100*a + 10*b + a
              if is_prime[n]:
                  palprimes.setdefault(rc3(n), []).append(n)
      
      # 4-digit primes with strictly increasing digits (210 combos)
      inc4 = {}
      for digits in combinations(range(10), 4):
          if digits[0] == 0: continue
          n = digits[0]*1000 + digits[1]*100 + digits[2]*10 + digits[3]
          if is_prime[n]:
              inc4.setdefault(rc4(n), []).append(n)
      
      # Find solutions: same rod count, correct ordering
      for t in set(squares) & set(palprimes) & set(inc4):
          for sq in squares[t]:
              for pp in palprimes[t]:
                  for p4 in inc4[t]:
                      if sq < pp < p4:
                          print(f"Rods={t}: square={sq}, palindromic prime={pp}, 4-digit prime={p4}")
      
      print(f"Runtime: {(perf_counter() - t0)*1000:.3f} ms")
      
      

      Like

      • Frits's avatar

        Frits 10:19 am on 24 March 2026 Permalink | Reply

        Claude AI generated fast code but it doesn’t cover the full solution space unless you proof/explain that a 4-digit palindromic number never can be prime (as it is divisible by 11).

        Like

  • Unknown's avatar

    Jim Randell 6:51 am on 20 March 2026 Permalink | Reply
    Tags:   

    Brain teaser 985: Sanders explores the river 

    From The Sunday Times, 7th June 1981 [link]

    Sanders decided one day to explore the river upstream from his camp. The current flowed, and Sanders paddled, at constant rates; and when he stopped paddling, he changed the boat’s speed so that it immediately (or so let us assume) moved at the speed of the current.

    Sanders’ companion, equipped with a watch and notebook, did his best to keep a log of the great voyage, but later Sanders found that the log read as follows:

    08:30 — We leave camp. Sanders paddles upstream.
    10:10 — We pass a blue jetty, and a shirt which is drifting down with the stream.
    10:15 — Sanders stops paddling and rests.
    ??:?? — We are again abreast of the blue jetty. Sanders resumes his paddling upstream.
    11:16 — Sanders stops paddling, turns the boat in mid-stream, and rests.
    11:34 — The paddling downstream begins.
    ??:?? — Once again abreast of the blue jetty.
    12:30 — We pass the shirt.
    ??:?? — We reach the camp.
    ??:?? — The shirt reaches the camp.

    What are the four missing times?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser985]

     
    • Jim Randell's avatar

      Jim Randell 6:52 am on 20 March 2026 Permalink | Reply

      There are no distance units given, so we shall say the jetty is 100 units distant from the camp.

      Suppose the river flows at a rate of r units/min, and Sanders rows (in still water) at a rate of s units/min.

      Then Sanders effective speed against the flow of the river is (s − r), and his effective speed with the flow of the river is (s + r).

      Between 8:30 and 10:10 = 100 minutes, Sanders rows 100 units against the flow of the river to the jetty:

      s − r = 100 / 100 = 1

      s = r + 1

      So, rowing against the river, Sanders proceeds at a rate of 1 unit/min. And rowing with the river he proceeds at a rate of (2r + 1).

      He then rows for another 5 minutes (until 10:15) against the river, so travels an additional 5 units distance beyond the jetty.

      And takes time t to drift back to the jetty (at 10:15 + t = the first unknown time):

      t = 5/r

      The shirt drifts down river for 140 minutes (between 10:10 and 12:30), at which point it has travelled a distance from the jetty towards the camp of: (140r).

      Sanders then rows against the river for (61 − t) minutes (between 10:15 + t and 11:16), and so achieves a distance from the jetty of: (61 − t).

      He drifts back for 18 minutes (until 11:34), and is nearer to the jetty by (18r) units.

      So his distance to the shirt intercept point (at 12:30) is:

      (61 − t) − (18r) + (140r)
      = (61 − t + 122r)

      Which he travels (rowing with the flow of the river) in 56 minutes (between 11:34 and 12:30).

      Hence:

      (61 − t + 122r) = 56(2r + 1)

      61 − t + 122r = 112r + 56
      t = 10r + 5

      Equating our equations for t:

      10r + 5 = 5/r

      10r² + 5r − 5 = 0
      5(r + 1)(2r − 1) = 0

      r = 1/2

      Hence t = 10.

      So the first unknown time (drift back to the jetty) is 10:25.

      And the shirt has drifted 140r = 70 units from the jetty, and so the intercept points is 30 units before the camp.

      Sanders is rowing with the river, at a rate of (2r + 1) = 2 units/min, so arrives at the camp 15 minutes after passing the shirt.

      Hence, Sanders reaches the camp at 12:30 + 15m = 12:45 (= third unknown time).

      And the shirt, drifting at a rate of 1/2 units/min reaches the camp 60 minutes after being passed.

      So the fourth unknown time is 12:30 + 60m = 13:30 (= fourth unknown time).

      The second unknown time is how long after 11:34 does it take Sanders to row with the river a distance of:

      (61 − t) − 18r
      = 51 − 9
      = 42 units

      The time taken is:

      42 / 2
      = 21 minutes

      And so the second unknown time (rowing downstream past the jetty) is 11:34 + 21m = 11:55.

      Solution: The missing times are: 10:25, 11:55, 12:45, 13:30.

      Like

      • Jim Randell's avatar

        Jim Randell 11:02 am on 20 March 2026 Permalink | Reply

        Here is a numerical solution using the same approach:

        from enigma import (find_zero, fdiv, intr, sprintf, printf)
        
        # suppose the rate of flow of the river is <r>
        def solve(r):
          s = r + 1  # Sanders rate of rowing
        
          # the first interception occurs at 10:10, and then ...
        
          # the shirt travels downstream for 140 minutes (10:10 - 12:30)
          # and so travels a distance of d1 = 140.r
          d1 = 140 * r
        
          # Sanders rows for another 5 minutes (to 10:15), moving a distance
          # of 5 beyond the jetty and is then carried back by the river to the
          # jetty at a time of (10:15 + t)
          # r.t = 5
          t = fdiv(5, r)
        
          # Sanders then rows for (61 - t) minutes to a distance d2 beyond the jetty
          d2 = 61 - t
          # and drifts back towards the jetty for 18 minutes (distance d3)
          d3 = d2 - 18 * r
        
          # and then rows for 56 minutes (11:34 - 12:30) with the river to make the
          # second interception
          # return the distance between Sanders and the shirt at 12:30
          return d1 - (56 * (s + r) - d3)
        
        # this is equivalent to:
        # solve = lambda r: 10*r + 5 - fdiv(5, r)
        
        # find the value of r when the distance is zero
        r = find_zero(solve, 0, 100).v
        s = r + 1
        t = fdiv(5, r)
        printf("[r={r:.02f}, s={s:.02f}, t={t:.02f}]")
        
        # express time hh:mm + xx minutes (to the nearest minute)
        def time(hh, mm, xx=0):
          (h, m) = divmod(mm + xx, 60)
          return sprintf("{h:02d}:{m:02d}", h=intr(hh + h), m=intr(m))
        
        # compute the unknown times:
        # when Sanders drifts back to the jetty
        t1 = time(10, 15, t)
        printf("unknown time 1 = {t1}")
        
        # when Sanders rows back to the jetty
        t2 = time(11, 34, fdiv(61 - t - 18*r, r + s))
        printf("unknown time 2 = {t2}")
        
        # remaining distance to camp from second interception
        rd = 100 - 140*r
        
        # when Sanders reaches camp
        t3 = time(12, 30, fdiv(rd, r + s))
        printf("unknown time 3 = {t3}")
        
        # when the shirt reaches camp
        t4 = time(12, 30, fdiv(rd, r))
        printf("unknown time 4 = {t4}")
        

        Like

    • John Crabtree's avatar

      John Crabtree 9:53 pm on 25 March 2026 Permalink | Reply

      Sanders sees the shirt at 10:10 and 12:30. During that time, the upstream distance that he travels through the water must equal the downstream distance through the water. Therefore he spends the same time padding upstream (10:10 to 11:16 minus t) and downstream (11:34 to 12:30) Therefore t = 10 minutes.
      Between 10:10 and 10:25 Sanders paddles for 5 of the 15 minutes. Therefore s = 3r. etc.
      I have not seen the published solution.

      Like

  • Unknown's avatar

    Jim Randell 7:40 am on 17 March 2026 Permalink | Reply
    Tags:   

    Teaser 2451: [Just one out] 

    From The Sunday Times, 13th September 2009 [link]

    I have just typed a product, but my machine gives a display that is “just one out”:

    3 × 6 = 19

    Whenever I type a digit, it displays a digit one more or less than the one I intended. So you should realise that I was trying to type:

    4 × 7 = 28

    I have tried again with another product, and again the display shows one digit times another higher digit equalling a two-figure prime answer that appears to be just one out.

    This time if I showed you the display it would be impossible for you to work out the product I was trying to type.

    What is the display this time?

    This puzzle was originally published with no title.

    [teaser2451]

     
    • Jim Randell's avatar

      Jim Randell 7:41 am on 17 March 2026 Permalink | Reply

      I have assumed that digits do not “wrap around”, and so if “0” is typed the machine always replaces it with “1”. And if “9” is typed the machine always replaces it with “8”.

      This Python program runs in 75ms. (Internal runtime is 744µs).

      from enigma import (defaultdict, irange, subsets, cproduct, is_prime, sprintf as f, join, printf)
      
      # possible incorrect digits
      ds = {
        0: [1], 1: [0, 2], 2: [1, 3], 3: [2, 4], 4: [3, 5],
        5: [4, 6], 6: [5, 7], 7: [6, 8], 8: [7, 9], 9: [8],
      }
      
      # collect candidate inputs by possible output
      rs = defaultdict(list)
      
      # choose an input sum: a * b = xy
      for (a, b) in subsets(irange(1, 9), size=2):
        xy = a * b
        (x, y) = divmod(xy, 10)
        if x == 0: continue
      
        # can we adjust all the digits by 1 to give a result that is off by one?
        for (a_, b_, x_, y_) in cproduct(ds[k] for k in (a, b, x, y)):
          if x_ == 0: continue
          xy_ = 10*x_ + y_
          if abs(a_ * b_ - xy_) == 1 and is_prime(xy_):
            (expr, expr_) = (f("{a} * {b} = {xy}"), f("{a_} * {b_} = {xy_}"))
            rs[expr_].append(expr)
            printf("[{expr} -> {expr_}]")
      
      # look for ambiguous outputs
      for (k, vs) in rs.items():
        if len(vs) > 1:
          printf("{k} <- {vs}", vs=join(vs, sep="; "))
      

      Solution: The display shows: “5 × 6 = 31”.

      And there are two inputs that may lead to this display:

      “4 × 5 = 20” → “5 × 6 = 31” (++++)
      “6 × 7 = 42” → “5 × 6 = 31” (−−−−)

      Note that in both these cases the digits are all changed in the same direction.


      However, if we allow the digits to “wrap around” so that “0” could be replaced by “1” or “9”, and “9” could be replaced by “0” or “8”, then there are further possible displays that have ambiguous origins:

      “3 × 6 = 18” → “4 × 7 = 29” (++++)
      “5 × 6 = 30” → “4 × 7 = 29” (−+−−)

      “4 × 5 = 20” → “3 × 6 = 19” (−+−−)
      “4 × 7 = 28” → “3 × 6 = 19” (−−−+)

      And these require that some digits in the expression are moved up and some moved down.

      Like

  • Unknown's avatar

    Jim Randell 6:40 am on 15 March 2026 Permalink | Reply
    Tags:   

    Teaser 3312: Never ends rumbas 

    From The Sunday Times, 15th March 2026 [link] [link]

    Whether or not Reverend Spooner really did accidentally swap over the sounds of initial bits of words (or wits of birds?) with amusing results, we might wonder if he did the same thing with numbers.

    He might have prepared a list of all possible pairs of three-digit prime numbers, for which each pair became square numbers when the first digit of the first number was exchanged with the first digit of the second number. He might have put each pair of primes in numerical order, and might then have put the pairs overall in numerical order.

    I’m not trying to start a rumour that this really happened. Like spoonerisms of words, this is just for amusement.

    What would have been the list of the Reverend’s numbers?

    [teaser3312]

     
    • Jim Randell's avatar

      Jim Randell 6:50 am on 15 March 2026 Permalink | Reply

      By looking at possible pairs of (odd) squares, and then looking to see if the numbers in the swapped pair are primes we can significantly reduce the number of pairs to consider.

      This Python program runs in 76ms. (Internal runtime is 167µs).

      from enigma import (primes, powers, subsets, ordered, printf)
      
      # 3-digit primes
      primes.expand(999)
      
      # collect the list of primes
      rs = list()
      
      # consider possible (odd) 3-digit squares
      for (sq1, sq2) in subsets(powers(11, 31, 2, step=2), size=2):
        # swap the leading digits over
        ((h1, t1), (h2, t2)) = (divmod(n, 100) for n in [sq1, sq2])
        if h1 == h2: continue
        (pr1, pr2) = (100*h + t for (h, t) in [(h2, t1), (h1, t2)])
        # check for primes
        if not (pr1 in primes and pr2 in primes): continue
        printf("[squares = ({sq1}, {sq2}) <-> primes = ({pr1}, {pr2})]")
        rs.append(ordered(pr1, pr2))
      
      # output result
      printf("prime pairs = {rs}", rs=sorted(rs))
      

      Solution: The Reverend’s list of numbers (primes) is: (461, 941), (541, 829), (761, 929).

      This give rise to the following squares:

      (461, 941) → (961, 441) = (31², 21²)
      (541, 829) → (841, 529) = (29², 23²)
      (761, 929) → (961, 729) = (31², 27²)

      Like

    • Ruud's avatar

      Ruud 7:13 am on 15 March 2026 Permalink | Reply

      import istr
      
      istr.repr_mode("int")
      print([(p0, p1) for p0, p1 in istr.combinations(istr.primes(100, 1000), r=2) if (p1[0] | p0[1:]).is_square() and (p0[0] | p1[1:]).is_square()])
      

      Like

  • Unknown's avatar

    Jim Randell 7:29 am on 13 March 2026 Permalink | Reply
    Tags:   

    Teaser 2452: [Cubic time] 

    From The Sunday Times, 20th September 2009 [link]

    The time and date 19:36, 24/08/57, uses all 10 digits. Furthermore, the product of the five constituent numbers is a perfect square (namely the square of 2736).

    19 × 36 × 24 × 08 × 57 = 7485696 = 2736²

    There are many times and dates that have this property. However, there is only one time and date that uses all 10 digits, and where the product of the five constituent numbers is a perfect cube.

    What is that time and date?

    This puzzle was originally published with no title.

    [teaser2452]

     
    • Jim Randell's avatar

      Jim Randell 7:30 am on 13 March 2026 Permalink | Reply

      The following run file executes in 105ms. (Internal runtime of the generated code is 22ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --invalid=""
      
      "is_cube(AB * CD * EF * GH * IJ)"
      
      # limits on the ranges
      "AB < 24"  # hour
      "CD < 60"  # minute
      "0 < EF < 32"  # day
      "0 < GH < 13"  # month
      
      --answer="(AB, CD, EF, GH, IJ)"
      

      Solution: The time/date is: 13:54, 26/09/78.

      i.e. 1:54pm on 26th September 1978.

      And:

      13 × 54 × 26 × 09 × 78 = 12812904 = 234³
      or:
      (13) × (2×3×3×3) × (2×13) × (3×3) × (2×3×13) = (2×3×3×13)³

      The code doesn’t check that a valid date is produced, but it easy to see that the only candidate solution corresponds to a valid date, so such a check is not necessary.

      Like

    • ruudvanderham's avatar

      ruudvanderham 2:22 pm on 13 March 2026 Permalink | Reply

      This code checks only valid dates:

      import peek
      import istr
      
      number_of_days_in_month = [None, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      istr.int_format("02")
      
      for hour in istr.range(24):
          if hour.all_distinct():
              for minute in istr.range(60):
                  if (hour | minute).all_distinct():
                      for year in istr.range(100):
                          if (hour | minute | year).all_distinct():
                              for month in istr.range(1, 13):
                                  if (hour | minute | year | month).all_distinct():
                                      for day in istr.range(number_of_days_in_month[int(month)] + 1):
                                          if (hour | minute | year | month | day).all_distinct():
                                              if (hour * minute * year * month * day).is_cube():
                                                  peek(hour, minute, year, month, day)

      Like

    • ruudvanderham's avatar

      ruudvanderham 4:46 pm on 13 March 2026 Permalink | Reply

      Here is a recursive version (that doesn’t check for valid dates):

      import istr
      
      istr.int_format("02")
      
      def solve(sofar, ranges):
          if ranges:
              for i in ranges[0]:
                  if (sofar | i).all_distinct():
                      solve(sofar | i, ranges[1:])
          else:
              if (sofar[0:2] * sofar[2:4] * sofar[4:6] * sofar[6:8] * sofar[8:10]).is_cube():
                  print(f"{sofar[0:2]}:{sofar[2:4]} {sofar[4:6]}/{sofar[6:8]}/{sofar[8:10]}")
      
      
      solve(istr(""), [istr.range(24), istr.range(60), istr.range(1, 32), istr.range(1, 13), istr.range(100)])

      Like

  • Unknown's avatar

    Jim Randell 7:31 am on 10 March 2026 Permalink | Reply
    Tags: ,   

    Teaser 2450: [Triangle squares] 

    From The Sunday Times, 6th September 2009 [link]

    Geomotto’s latest painting celebrates Napoleon, who was quite a mathematician. The painting consists of a triangle of area a whole number of square feet, but less than a square yard. One of its sides is one yard long. On each of the other two sides sits a square with its side equal in length to the triangle’s side.

    The area of the triangle and the smaller square together equals the area of the larger square. Furthermore, if you write down the area of the smaller square in square inches, then the digits can be arranged to give the area of the larger square.

    What are the two squares’ areas?

    This puzzle was originally published with no title.

    [teaser2450]

     
    • Jim Randell's avatar

      Jim Randell 7:31 am on 10 March 2026 Permalink | Reply

      Working in units of inches:

      If the area of the triangle is A (square inches), and we consider the triangle to have sides of (36, x, y) (where x < y).

      Then we can calculate the height of the triangle above the 36 side:

      A = 18h

      h = A/18

      And now suppose the height splits the base into (18 − z) on the x side, and (18 + z) on the y side. Then we have:

      y² = (18 + z)² + h²
      x² = (18 − z)² + h²

      Subtracting:

      y² − x² = (18 + z)² − (18 − z)²

      y² − x² = 72z

      But we are told:

      y² − x² = A

      z = A/72

      So, for any given value of A we can calculate the values of x² and y², and look for values that use the same digits, and there are only a few values of A to check.

      This Python program runs in 78ms. (Internal runtime is 136µs).

      from enigma import (irange, rdiv, nsplit, sq, sqrt, triangle_area, printf)
      
      # area = A is a whole number of square feet < 9
      for n in irange(1, 8):
        A = 144*n
        (h, z) = (rdiv(A, 18), rdiv(A, 72))  # = (8 * n, 2 * n)
        (x2, y2) = (sq(18 - z) + sq(h), sq(18 + z) + sq(h))
      
        # x^2 and y^2 are anagrams of each other
        if not (sorted(nsplit(x2)) == sorted(nsplit(y2))): continue
      
        # check for a valid triangle, with the correct area
        T = triangle_area(36, sqrt(x2), sqrt(y2))
        if T is None or abs(T - A) > 1e-6: continue
      
        # output solution
        printf("x^2={x2} y^2={y2} [A={A} h={h} z={z} T={T:.3f}]")
      

      Solution: The areas of the squares (in square inches) are: 2340 and 3204.

      Like

  • Unknown's avatar

    Jim Randell 6:56 am on 8 March 2026 Permalink | Reply
    Tags:   

    Teaser 3311: Calling from home 

    From The Sunday Times, 8th March 2026 [link] [link]

    Edna is charged a single digit number of pence for each call to a mobile. A smaller whole number of pence is charged for every minute or part minute for which each mobile call exceeds five minutes. She spent exactly an hour in real time on mobile calls with the shortest call being less than a minute and each of the other four calls rounding up to different prime numbers of minutes. Curiously, the product of the primes is a palindrome; the sum of its digits being a single digit odd number.

    The total charge for the five calls was a palindromic number of pence. If I told you whether that number was odd or even, you would know what it was.

    In increasing order, what are the two palindromic numbers?

    [teaser3311]

     
    • Jim Randell's avatar

      Jim Randell 7:17 am on 8 March 2026 Permalink | Reply

      This Python program runs in 72ms. (Internal runtime is 519µs).

      from enigma import (defaultdict, irange, primes, subsets, multiply, is_npalindrome, dsum, printf)
      
      # calculate the total cost for a call of <m> chargeable minutes
      # <a> pence for the first 5 minutes, <b> pence per minute thereafter
      def cost(a, b, m):
        t = a
        if m > 5: t += b * (m - 5)
        return t
      
      # group candidate results by parity
      r = defaultdict(list)
      
      # choose 4 primes with a sum between 59 and 64 and a palindromic product
      for ps in subsets(primes.between(2, 49), size=4):
        t = sum(ps)
        if not (59 <= t <= 64): continue
        p = multiply(ps)
        if not is_npalindrome(p): continue
        d = dsum(p)
        if not (d < 10 and d % 2 == 1): continue
        printf("{ps} -> sum={t}, prod={p}")
      
        # chargeable durations for the 5 calls
        ms = [1]
        ms.extend(ps)
      
        # choose the tariff values
        for (b, a) in subsets(irange(1, 9), size=2):
          t = sum(cost(a, b, m) for m in ms)
          if not is_npalindrome(t): continue
          printf("-> a={a} b={b} -> t={t}")
          r[t % 2].append((a, b, ms, t, p))
      
      # look for unique solutions by parity
      for (k, vs) in r.items():
        if len(vs) != 1: continue
        (a, b, ms, t, p) = vs[0]
        printf("parity={k} -> a={a} b={b} ms={ms}, t={t} p={p}")
      

      Solution: The two palindromic numbers are: 292 and 10101.

      The tariff is 8p for the first 5 minutes and 6p per minute thereafter.

      The chargeable lengths of the 5 calls are:

      1 min = 8p
      3 min = 8p
      7 min = 20p
      13 min = 56p
      37 min = 200p

      The product of the 4 primes is: 3 × 7 × 13 × 37 = 10101, which as required is a palindrome with a digit sum (3) that is an odd single digit. (And this is the only candidate collection of 4 primes with an appropriate total that has a palindromic product).

      The total charge for the 5 calls is: 8 + 8 + 20 + 56 + 200 = 292p, which is also a palindrome.

      And the (8p, 6p) tariff is the only candidate where the total charge is an even palindrome.

      Example call lengths that give rise to the chargeable call lengths above are: 0:48, 2:48, 6:48, 12:48, 36:48, which have a total combined length of 60:00 exactly.

      Like

    • Ruud's avatar

      Ruud 11:08 am on 8 March 2026 Permalink | Reply

      import peek
      import istr
      
      istr.repr_mode("int")
      
      collect = {False: [], True: []}
      for durations in istr.combinations(istr.primes(60), 4):
          durations = [1, *durations]
          prod = istr.prod(durations)
          if 60 <= sum(durations) <= 64 and prod.is_palindrome() and sum(prod) in (1, 3, 5, 7):
              for extra_charge, charge in istr.combinations(range(1, 10), 2):
                  total = sum(charge + max(duration - 5, 0) * extra_charge for duration in durations)
                  if total.is_palindrome():
                      solution = sorted((prod, total))
                      collect[total.is_even()].append(peek(solution, prod, durations, charge, extra_charge, total, as_str=True, use_color=False))
      
      print(*[v[0] for v in collect.values() if len(v) == 1])
      

      Note this requires istr>=1.1.26

      Like

    • Ellen Napier's avatar

      Ellen Napier 6:22 pm on 10 March 2026 Permalink | Reply

      I’m getting only one set of primes that satisfies the requirements, and this
      set produces exactly one even palindrome but more than one odd palindrome for a
      possible total charge. I don’t see how that would result in a distinguishable
      answer.

      Like

      • Jim Randell's avatar

        Jim Randell 9:37 pm on 10 March 2026 Permalink | Reply

        @Ellen: But the puzzle text says that if you were told the parity of the second palindrome you would know its value. And there is only one parity for which you would know the value for certain, so that must be the actual parity, and that gives the answer to the puzzle.

        Like

  • Unknown's avatar

    Jim Randell 8:11 am on 6 March 2026 Permalink | Reply
    Tags:   

    Teaser 2453: [Half a dozen] 

    From The Sunday Times, 27th September 2009 [link]

    In the following three statements, I have consistently replaced digits with letters, using a different letter for each different digit. In this way, I discover that:

    SIX × TWO = TWELVE
    FIVE is divisible by 5
    SIX is divisible by 6

    With a little guile, you can solve this and answer the following question:

    What is the number for SOLVE?

    This puzzle was originally published with no title.

    [teaser2453]

     
    • Jim Randell's avatar

      Jim Randell 8:13 am on 6 March 2026 Permalink | Reply

      Here is a solution using the [[ SubstitutedExpression ]] solver from the enigma.py library, that is a direct interpretation of the puzzle text.

      The following run file executes in 86ms. (Internal runtime of the generated code is 5.8ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      "SIX * TWO = TWELVE"
      "FIVE % 5 = 0"
      "SIX % 6 = 0"
      
      --answer="SOLVE"
      

      Solution: SOLVE = 95380.

      We have:

      TWO = 165
      FIVE = 4780
      SIX = 972
      TWELVE = 160380

      Like

    • ruudvanderham's avatar

      ruudvanderham 2:45 pm on 6 March 2026 Permalink | Reply

      Very brute force:

      for s, i, x, t, w, o, l, f, e, v in istr.permutations(range(10)):
          if (s | i | x) * (t | w | o) == (t | w | e | l | v | e) and (f | i | v | e).is_divisible_by(5) and (s | i | x).is_divisible_by(6):
              peek(s | o | l | v | e)
      

      and a bit less brute:

      for five in istr.range(1000, 10000, 5):
          for six in istr.range(102, 1000, 6):
              if five[1] == six[1]:
                  istr.decompose(five | six, "fivesix")
                  if len(fivesix := set(five) | set(six)) == 6:
                      for t, w, o in istr.permutations(set(istr.range(10)) - set(fivesix), 3):
                          twelve = six * (two := t | w | o)
                          if len(twelve) == 6:
                              istr.decompose(twelve, "TWElVF")
                              if T == t and W == w and E == F == e and V == v:
                                  if len({f, i, v, e, s, i, x, t, w, o, t, w, e, l, v, e}) == 10:
                                      solve = istr("=solve")
                                      peek(five, six, two, twelve, solve)
      

      Like

  • Unknown's avatar

    Jim Randell 9:29 am on 3 March 2026 Permalink | Reply
    Tags:   

    Brain teaser 1006: Seafood dinner 

    From The Sunday Times, 8th November 1981 [link]

    The exclusive Ichthyophage Club was invited to hold its annual dinner at a resort on the Costa Fortuna last summer. Several members accepted the invitation, and each was asked to bring one of the local seafood specialities as their contribution to the feast.

    The fare consisted of:

    • the local edible starfish, which has five legs,
    • the squid, which has ten,
    • and octopus.

    Each guest provided one such creature, and in fact more people provided octopus than provided starfish.

    A chef was hired locally. He arranged the food so that each guest received a plateful consisting of the same number of legs — at least one leg from each species — but no guest’s plate was made up in the same way as any other guest’s plate. In other words, no guest had the same combination of legs on his plate as any other guest did.

    When the food had been arranged in this way, the chef was grieved to find that all the legs had been used, leaving no edible fragments for him to take home to his family.

    How many guests were there?

    How many brought starfish, how many brought squid, and how many brought octopus?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser1006]

     
    • Jim Randell's avatar

      Jim Randell 9:29 am on 3 March 2026 Permalink | Reply

      Suppose there were n guests, and x of them brought a starfish, y of them brought a squid, z of them brought an octopus (each with their full complement of legs).

      And they are each served a different arrangement of k legs, then:

      n = x + y + z
      k = (5x + 10y + 8z) / n

      This Python program considers increasing numbers of guests, and looks for viable decompositions that allow a different arrangement of legs to be served to each of them.

      It runs in 74ms. (Internal runtime is 334µs).

      from enigma import (enigma, Enumerator, irange, inf, div, subsets, unzip, printf)
      
      # set defaults for decompose
      decompose = enigma.partial(enigma.decompose, increasing=0, sep=0, min_v=1)
      
      # solve the puzzle for <n> guests
      def solve(n):
        # each guest brings one of:
        # starfish (5 legs) = x; squid (10 legs) = y; octopus (8 legs) = z
        for (x, y, z) in decompose(n, 3):
          # "more people provided octopus than starfish"
          if not (z > x): continue
      
          # calculate the number of legs per guest
          ts = (5*x, 10*y, 8*z)
          k = div(sum(ts), n)
          if k is None: continue
      
          # look for a set of n decompositions
          for ss in subsets(decompose(k, 3), size=n):
            # that give the correct number of leg types
            ns = tuple(sum(vs) for vs in unzip(ss))
            if not (ns == ts): continue
            # return solution
            yield ((x, y, z), k, ss)
            break  # one decomposition is enough
      
      # consider the number of guests
      for n in irange(3, inf):
        # look for a viable scenario with <n> guests
        sols = Enumerator(solve(n))
        for ((x, y, z), k, ss) in sols:
          printf("{n} guests ({x} starfish, {y} squid, {z} octopus) -> {k} legs per plate")
          printf("-> plates = {ss}")
        # are we done?
        if sols.count > 0: break
      

      Solution: There were 8 guests. 2 brought starfish, 3 brought squid, 3 brought octopus.

      The total number of legs available was: 2×5 + 3×10 + 3×8 = 64 legs.

      And so each of the guests was served 8 legs.

      The distinct served plates were (starfish legs, squid legs, octopus legs):

      (1, 1, 6)
      (1, 2, 5)
      (1, 3, 4)
      (1, 4, 3)
      (1, 5, 2)
      (1, 6, 1)
      (2, 4, 2)
      (2, 5, 1)
      total = (10, 30, 24)

      Although the program only looks for one way to make up the plates, this is in fact the only way for n = 8.

      Like

  • Unknown's avatar

    Jim Randell 6:59 am on 1 March 2026 Permalink | Reply
    Tags:   

    Teaser 3310: Building bridges 

    From The Sunday Times, 1st March 2026 [link] [link]

    Chuck’s ranch is 16 km north of a narrow straight river flowing west to east. His cousin Dwayne has a ranch 72 km south and 30 km east of Chuck’s ranch. To join their ranches, they are each required to build a straight road from their ranch to a bridge that will be constructed over the river at a location yet to be decided.

    Selfishly, Chuck wishes to have the bridge built at a location that would require Dwayne’s road to be as much as possible longer than the road he has to build on his side of the river. Dwayne wishes another location for the bridge, a location that would require the roads on both sides of the river to be of equal lengths.

    What is the distance between these two bridge locations?

    [teaser3310]

     
    • Jim Randell's avatar

      Jim Randell 7:12 am on 1 March 2026 Permalink | Reply

      (See also: Teaser 3286).

      Here is a numerical solution. (Which saves having to do any calculus).

      The following Python program runs in 72ms. (Internal runtime is 231µs).

      from enigma import (hypot, find_max, find_zero, printf)
      
      # calculate lengths of road for location of bridge x
      # x = km E from C
      C = lambda x: hypot(16, x)
      D = lambda x: hypot(56, 30 - x)
      
      # calculate how much more road D builds than C
      f = lambda x: D(x) - C(x)
      
      # find C's proposed bridge location (maximise f)
      xC = find_max(f, -100, +130).v
      printf("xC = {xC:.3f}")
      
      # find D's proposed bridge location (f = 0)
      xD = find_zero(f, -100, +130).v
      printf("xD = {xD:.3f}")
      
      # calculate the distance between the proposed locations
      d = abs(xC - xD)
      printf("diff = {d:.3f}")
      

      Solution: The proposed locations for the bridge are 75 km apart.

      C’s proposed location is 12 km west of the nearest point on the river to him. C’s road would be 20 km, and D’s road would be 70 km. (Total road length would be 90 km).

      (This corresponds to the position where the angles that the roads make with the river are equal. So if we mirrored one of the brothers ranches in the river, so that they are both on the same side, then there would be one straight road that joins the bridge to both ranches).

      D’s proposed location is 33 km east of the nearest point on the river to him. Both roads would be 65 km. (Total road length would be 130 km).


      The minimal total road length occurs if the bridge is built 6.66 km east of the nearest point on the river to C. C’s road would be 17.33 km, and D’s road would be 60.66 km. (Total road length = 78 km).

      (If we were to draw a straight line between the ranches, we achieve the shortest possible combined distance, and the position of the bridge is where the line crosses the river).

      Note that measuring the distances between points, we have:

      [C→D] × [XC→XD] = 78 × 75 = 5850
      [C→XC] × [D→XD] + [C→XD] × [D→XC] = 20 × 65 + 65 × 70 = 5850

      Which means the quadrilateral (C, XC, D, XD) is cyclic.


      Analytically:

      The two roads are the same length when:

      √(56² + (30 − x)²) = √(16² + x²)
      3136 + (900 − 60x + x²) = 256 + x²

      60x = 3780
      x = 63

      And we see both roads have length 65 km.

      To calculate the maximum of the function f(x) we can differentiate it, at look for stationary points (i.e. points where f'(x) = 0):

      f(x) = √(56² + (30 − x)²) − √(16² + x²)
      f'(x) = ((x − 30) / √(x² − 60x + 4036)) − (x / √(x² + 256))

      And we can find values when f'(x) is 0 by looking for values where:

      ((x − 30) / √(x² − 60x + 4036)) = (x / √(x² + 256))

      (x − 30)² (x² + 256) = x² (x² − 60x + 4036)
      (x² − 60x + 900) (x² + 256) = x² (x² – 60x + 4036)
      x4 − 60x³ + 1156x² − 15360x + 230400 = x4 − 60x³ + 4036x²

      2880x² + 15360x − 230400 = 0
      960(x + 12)(3x − 20) = 0
      x = −12; x = 20/3

      x = −12 is a stationary point that gives the maximum value of f(x) (= 50 km when D’s road is 70 km and C’s road is 20 km).

      (And x = 20/3 is not a stationary point of f(x), but does give the position of the bridge for the minimum combined distance for the roads).

      Like

      • Jim Randell's avatar

        Jim Randell 8:48 am on 28 March 2026 Permalink | Reply

        Here is a way to find the maximum value of f(x) without using calculus:

        We reflect ranch D in the line of the river, so it appears at D′, north of the river.

        For a point on the river X the road distances XD and XD′ are the same length.

        We then construct the triangle CXD′, and using the triangle inequality we get:

        XC + CD′ ≥ XD′

        XD′ − XC ≤ CD′

        with equality occurring when the points are co-linear and the triangle is degenerate (zero area).

        but:

        XC = C(x)
        XD′ = D(x)

        Hence:

        f(x) = D(x) − C(x)
        f(x) = XD′ − XC
        f(x) ≤ CD′

        with equality occurring when X, C, D′ are co-linear.

        And so when X, C, D′ are co-linear the maximum value of f(x) is achieved, and this value is the distance CD′.

        The distance CD′ is calculated as the hypotenuse of a right-angled triangle where the other two sides are 30 and 56 − 16 = 40, and so CD′ = 50.

        This is a (3, 4, 5)×10 triangle.

        Since the points X, C, D′ are co-linear, the triangle with hypotenuse XD′ is just a larger version of a (3, 4, 5) triangle with the 4 side measuring 56 (= 4×14).

        Hence the base of the triangle is 3×14 = 42, which gives a point X on the river 12 km to the W of the closest point on the river to C (i.e. x = −12).

        Like

  • Unknown's avatar

    Jim Randell 8:33 am on 27 February 2026 Permalink | Reply
    Tags:   

    Teaser 2454: [French registrations] 

    From The Sunday Times, 4th October 2009 [link]

    On holiday in France, I spotted two French cars parked side by side. The last two digits of their registration numbers represent the départements in which they are registered. Viewing the cars from the front and juxtaposing those two département numbers, I saw a four-figure prime number; and on going around the back and doing the same, I saw a different prime number.

    Furthermore, the average of the two département numbers was prime, as was half their difference.

    What were the two département numbers?

    This puzzle was originally published with no title.

    [teaser2454]

     
    • Jim Randell's avatar

      Jim Randell 8:34 am on 27 February 2026 Permalink | Reply

      Fortunately there is only one possible answer to the puzzle for 2-digit numbers, and it turns out they are valid département numbers.

      The following run file executes in 84ms. (Internal runtime of the generated code is 4.6ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # the department numbers are AB and CD
      --distinct=""
      --invalid=""
      
      "is_prime(ABCD)"
      "is_prime(CDAB)"
      "is_prime(div(AB + CD, 2))"
      "is_prime(div(abs(AB - CD), 2))"
      
      --answer="ordered(AB, CD)"
      

      Solution: The two département numbers are: 39 and 43.

      39 is Jura. 43 is Haute-Loire.

      3943 and 4339 are primes, as is the mean of the two département numbers (41), and half the difference (2).

      Like

      • Ruud's avatar

        Ruud 2:44 pm on 27 February 2026 Permalink | Reply

        Your code doesn’t seem to test whether the front and back numbers are different., which is in the text.
        It doesn’t make a difference here, though.

        Like

        • Jim Randell's avatar

          Jim Randell 3:00 pm on 27 February 2026 Permalink | Reply

          @Ruud: If the numbers are the same, then half the difference is zero, which is not prime. So in any candidate solutions found the numbers will be different. And it turns out there is is only one candidate pair of (2 digit) numbers that works.

          Like

      • Jim Randell's avatar

        Jim Randell 4:29 pm on 27 February 2026 Permalink | Reply

        Or using the prime sieve from the enigma.py library.

        The following program has an internal runtime of just 713µs.

        from enigma import (primes, div, printf)
        
        # consider the smaller (4-digit) prime
        for p in primes.between(0, 9999):
          # extract the two 2-digit numbers
          (a, b) = divmod(p, 100)
          # construct the larger 4-digit prime
          if not (a < b): continue
          q = 100 * b + a
          if not (q in primes and div(a + b, 2) in primes and div(b - a, 2) in primes): continue
          # output solution
          printf("{a} {b}")
        

        Like

    • Ruud's avatar

      Ruud 2:42 pm on 27 February 2026 Permalink | Reply

      import istr
      
      istr.int_format("02")
      
      for dep0, dep1 in istr.product(range(100), repeat=2):
          if all(x.is_prime() for x in (dep0 | dep1, dep1 | dep0, (dep0 + dep1) / 2, abs(dep0 - dep1) / 2)) and dep0 != dep1:
              print(dep0, dep1)
      

      Like

      • Ruud's avatar

        Ruud 5:19 pm on 27 February 2026 Permalink | Reply

        With a sieve (like @Jim Randell)

        import istr
        
        istr.int_format("04")
        for n in istr.primes(10000):
            if all(x.is_prime() for x in (n[2:] | (n[:2]), (n[:2] + n[2:]) / 2, (n[2:] - n[:2]) / 2)) and n[:2] < n[2:]:
                print(n[:2], n[2:])

        Like

  • Unknown's avatar

    Jim Randell 9:09 am on 24 February 2026 Permalink | Reply
    Tags:   

    Teaser 2455: [Mixing jugs] 

    From The Sunday Times, 11th October 2009 [link]

    I had a red jug, a white one and a blue one, containing kiwi, lime and mango juice, not necessarily in that order. Each [initially contained] a whole number of millilitres, totalling less than a litre. I poured juice from the red jug to the white and stirred the contents. I poured some from the white to the blue, some from the blue to the white, and some from the white to the red. Each time I doubled the contents of the jug into which the juice was poured. I ended with equal quantities in each jug and, in one jug, 45 ml more lime juice than mango.

    At the end, how much of each juice was in the red jug?

    This puzzle was originally published with no title.

    [teaser2455]

     
    • Jim Randell's avatar

      Jim Randell 9:10 am on 24 February 2026 Permalink | Reply

      At the end of the series of transfers we end up with the same quantity in each jug.

      So we can call that quantity 1 unit and we have:

      after transfer 4 (white → red): R = 1; W = 1; B = 1

      Each transfer doubles the amount in the receiving jug, so before the transfer the red jug must have contained only 1/2 unit, and the white just must have contained 3/2 units:

      before transfer 4 (white → red): R = 1/2; W = 3/2; B = 1

      We can then move on to transfer 3 (blue → white), and we get:

      before transfer 3 (blue → white): R = 1/2; W = 3/4; B = 7/4

      And transfer 2:

      before transfer 2 (white → blue): R = 1/2; W = 13/8; B = 7/8

      And finally, transfer 1:

      before transfer 1 (red → white): R = 21/16; W = 13/16; B = 7/8

      And so we find the relative quantities initially in the jugs are: 21 : 13 : 14.

      We can then look for integer multiples of these values to give us the starting quantities in the jugs (total volume is less than 1000 ml), such that after the final transfer one of the jugs has a mix with 45 ml more lime than mango.

      The following Python program starts from the sequence of transfers, and performs the steps outlined above.

      It runs in 73ms. (Internal runtime is 2.7ms).

      from enigma import (irange, inf, rdiv, update, rev, ratio_q, subsets, seq2str, printf)
      
      # indices for red, white blue
      (R, W, B) = (0, 1, 2)
      # sequences of transfers
      xfers = [(R, W), (W, B), (B, W), (W, R)]
      
      # working backwards from the end
      # we start with a volume of 1 in each jug, and then make the reverse transfers
      jugs = [1, 1, 1]
      for (s, t) in rev(xfers):
        # volume moved = half the volume of the target jug
        v = rdiv(jugs[t], 2)
        # update the jugs
        jugs = update(jugs, [(s, jugs[s] + v), (t, jugs[t] - v)])
      
      # jugs now has the relative quantities in the three jugs at the start
      (r, w, b) = ratio_q(*jugs)
      T = r + w + b
      
      printf("initial volume ratio = {jugs} -> {r} : {w} : {b}", jugs=seq2str(jugs))
      
      
      # calculate vector sum: <a[i] + k.b[i]>
      vec_add = lambda xs, ys, k: list(x + y * k for (x, y) in zip(xs, ys))
      
      # start with the initial quantities in the R, W, B jugs
      jugs = [[r, 0, 0], [0, w, 0], [0, 0, b]]
      # perform the transfers
      for (s, t) in xfers:
        # volume to move = total volume in target jug
        vt = sum(jugs[t])
        # total volume in the source jug
        vs = sum(jugs[s])
        # calculate quantities of each flavour to move
        qs = list(rdiv(vt * v, vs) for v in jugs[s])
        # update the jugs
        jugs = update(jugs, [(s, vec_add(jugs[s], qs, -1)), (t, vec_add(jugs[t], qs, +1))])
      
      printf("final volume ratio = {jugs}")
      
      
      # now look for a multiple that gives a total less than 1000 ml
      for k in irange(1, inf):
        if not (T * k < 1000): break
      
        # multiply up the final quantities
        jugs4 = list(list(k * v for v in vs) for vs in jugs)
      
        # assign indices to flavours
        for (lime, kiwi, mango) in subsets([0, 1, 2], size=len, select='P'):
          # one of the jugs has 45 ml more lime than mango
          if not any(vs[lime] == vs[mango] + 45 for vs in jugs4): continue
          # output solution
          d = { lime: "lime", kiwi: "kiwi", mango: "mango" }
          printf()
          printf("lime = {lime}, kiwi = {kiwi}, mango = {mango}")
          printf("initial: R={R} {dR}; W={W} {dW}; B={B} {dB}", R=r * k, W=w * k, B=b * k, dR=d[R], dW=d[W], dB=d[B])
          printf("final: {jugs4}")
      

      Solution: At the end, the red jug contains: 55 ml lime, 15 ml kiwi, 10 ml mango.

      The situation is (with volumes in ml):

      Start:
      Red = 105 lime
      White = 65 kiwi
      Blue = 70 mango
      total = 240

      Transfer 1: Red → White; vol = 65 (= 65 lime)
      Red = 40 lime
      White = 65 lime + 65 kiwi (= 130 total)
      Blue = 70 mango

      Transfer 2: White → Blue; vol = 70 (= 35 lime + 35 kiwi)
      Red = 40 lime
      White = 30 lime + 30 kiwi (= 60 total)
      Blue = 35 lime + 35 kiwi + 70 mango (= 140 total)

      Transfer 3: Blue → White; vol = 60 (= 15 lime + 15 kiwi + 30 mango)
      Red = 40 lime
      White = 45 lime + 45 kiwi + 30 mango (= 120 total)
      Blue = 20 lime + 20 kiwi + 40 mango (= 80 total)

      Transfer 4: White → Red; vol = 40 (= 15 lime + 15 kiwi + 10 mango)
      Red = 55 lime + 15 kiwi + 10 mango (= 80 total)
      White = 30 lime + 30 kiwi + 20 mango (= 80 total)
      Blue = 20 lime + 20 kiwi + 40 mango (= 80 total)

      And we see that the red jug has 45 ml more lime than mango in its mix.

      Like

  • Unknown's avatar

    Jim Randell 7:43 am on 22 February 2026 Permalink | Reply
    Tags:   

    Teaser 3309: Family street 

    From The Sunday Times, 22nd February 2026 [link] [link]

    George and Martha live in Millipede Avenue, a road with 1000 houses numbered from 1 to 1000. Their five daughters and their families will also shortly be buying houses along that road.

    “It’s quite simple”, commented Martha. “Andrea rang this afternoon. If  you multiply her house number by three and square that product, you get Bertha’s. If you subtract Bertha’s number from 900, you get Caroline’s. If you subtract Andrea’s number from 20 and multiply the result by 36, you get Dorothy’s and finally Elizabeth’s is the average of Caroline’s and Dorothy’s”.

    “Priceless!” retorted George. “Four pieces of information and five unknowns. Who do you think I am, Nostradamus?”

    “Oh, come, come!” replied Martha. “Even someone of your pathetic mathematical ability can work it out!”

    In increasing order, which five houses will their daughters be living in?

    [teaser3309]

     
    • Jim Randell's avatar

      Jim Randell 7:51 am on 22 February 2026 Permalink | Reply

      On the face of it there seem to be multiple solutions.

      But Martha thinks George can work it out, using additional information that he knows. (Presumably he knows his own house number, and maybe some of the other numbers on the street that cannot occur in any candidate solution). So any candidate that includes an invalid number can be discarded.

      It turns out that there is a number that appears in all the candidate solutions but one. So we can identify the candidate that does not include this number as the likely required answer.

      This Python program runs in 69ms. (Internal runtime is 71µs).

      from enigma import (irange, sq, div, multiset, printf)
      
      # generate candidate solutions
      def generate():
        for A in irange(1, 9):
          B = sq(3 * A)
          C = 900 - B
          D = 36 * (20 - A)
          E = div(C + D, 2)
          ns = {A, B, C, D, E}
          if len(ns) < 5 or None in ns or min(ns) < 1 or max(ns) > 1000: continue
          printf("[A={A} B={B} C={C} D={D} E={E}]")
          yield (A, B, C, D, E)
      
      # collect candidate solutions
      ss = list(generate())
      
      # count the numbers that occur in each solution
      m = multiset.from_seq(*ss)
      
      # look for a number that occurs in all but one of the candidates
      for (k, v) in m.items():
        if not (v + 1 == len(ss)): continue
        # so, if G and M live at house k, there is only one remaining candidate
        for ns in ss:
          if k in ns: continue
          (A, B, C, D, E) = ns
          printf("k={k} -> A={A} B={B} C={C} D={D} E={E} -> {ns}", ns=sorted(ns))
      

      Solution: The daughters are moving to houses: 2, 36, 648, 756, 864.


      Manually:

      For odd values of A the calculated value of E is not an integer, and for values of A > 8 the calculated value of C is not a positive integer. So we only need to calculate candidates using four values of A:

      A=2 B=36 C=864 D=648 E=756
      A=4 B=144 C=756 D=576 E=666
      A=6 B=324 C=576 D=504 E=540
      A=8 B=576 C=324 D=432 E=378

      If George and Martha live at number 576 (or know that this number is invalid for some other reason), then this leaves just the first candidate as a viable solution. And this is the only single invalid number that allows us to narrow down the candidates to a single answer.

      But if there is more than one number that George can eliminate we could find different solutions. (In fact, any of the four candidate solutions can be isolated using 2 invalid numbers).

      Like

  • Unknown's avatar

    Jim Randell 8:13 am on 20 February 2026 Permalink | Reply
    Tags:   

    Teaser 2456: [Digit groups] 

    From The Sunday Times, 18th October 2009 [link]

    A teacher asked each of her pupils to write the digits 1 to 9 in a row and then use dashes to divide their row of digits into groups, such as in 123-4-56-789. She then asked them to add up the numbers in each group and multiply all the totals together — so the above example would give the
    answer 6×4×11×24 = 6336.

    Billy’s answer was an odd perfect square. Jilly’s answer was also a perfect square, but in her case each group of digits had more than one digit in it.

    What were Billy’s and Jilly’s rows of digits and dashes?

    This puzzle was originally published with no title, and no setter was given.

    [teaser2456]

     
    • Jim Randell's avatar

      Jim Randell 8:14 am on 20 February 2026 Permalink | Reply

      This Python program runs in 69ms. (Internal runtime is 1.6ms)

      from enigma import (irange, decompose, tuples, csum, multiply, is_square_p, printf)
      
      # the sequence of digits
      digits = list(irange(1, 9))
      n = len(digits)
      
      # divide the digits into k groups (min groups = 1, max groups = n)
      for k in irange(1, n):
        for js in decompose(len(digits), k, increasing=0, sep=0, min_v=1):
          # split the digits into the groups
          gs = list(digits[i:j] for (i, j) in tuples(csum(js, empty=1), 2))
          # calculate the result of the process
          r = multiply(sum(ns) for ns in gs)
          # both B and J found a square number
          if is_square_p(r):
            # B's was odd
            if r % 2 == 1:
              printf("Billy: {gs} -> {r}")
            # J's groups each have more than one digit
            if all(len(ns) > 1 for ns in gs):
              printf("Jilly: {gs} -> {r}")
      

      Solution: Billy = 12-3-456-78-9; Jilly = 12-3456-789.

      So:

      Billy = (1+2)×(3)×(4+5+6)×(7+8)×(9) = 18225 = 135^2
      Jilly = (1+2)×(3+4+5+6)×(7+8+9) = 1296 = 36^2

      Here is a complete list of square numbers that can be formed this way:

      12345678-9 → 324 = 18^2
      12-3456-789 → 1296 = 36^2 (only one with multiple digits per group)
      123-4-5-6789 → 3600 = 60^2
      1-2-34567-8-9 → 3600 = 60^2
      1-2-3-4-5-6789 → 3600 = 60^2
      12-345-6-789 → 5184 = 72^2
      12-3-45-6-789 → 11664 = 108^2
      1-234-567-8-9 → 11664 = 108^2
      1-23-4-5-6-789 → 14400 = 120^2
      12-3-456-78-9 → 18225 = 135^2 (only odd square)
      12-3-4-567-8-9 → 46656 = 216^2

      Like

    • ruudvanderham's avatar

      ruudvanderham 12:07 pm on 20 February 2026 Permalink | Reply

      I make expressions by putting either )*( or + between the digits 1 – 8, like
      (1)*(2+3+4+5)*(6+7)*(8)*(9)
      and then evaluate that with eval.
      For Jilly, I then also check whether the lengths of all parts split by ‘)*(‘ is greater than 2.

      import itertools
      import math
      
      for ops in itertools.product(("+", ")*("), repeat=8):
          exp = "(1" + "".join(f"{op}{i}" for i, op in enumerate(ops, 2)) + ")"
          if math.isqrt(val := eval(exp)) ** 2 == val:
              if val % 2:
                  print("Billy", exp, "=", val)
              else:
                  if all(len(part) > 2 for part in exp.split(")*(")):
                      print("Jilly", exp, "=", val)
      

      Like

      • Ruud's avatar

        Ruud 3:41 pm on 20 February 2026 Permalink | Reply

        With a more elegant check for no groups of one digit:

        import itertools
        import math
        
        for ops in itertools.product(("+", ")*("), repeat=8):
            exp = "(1" + "".join(f"{op}{i}" for i, op in enumerate(ops, 2)) + ")"
            if math.isqrt(val := eval(exp)) ** 2 == val:
                if val % 2:
                    print("Billy", exp, "=", val)
                else:
                    if all("+" in part for part in exp.split("*")):
                        print("Jilly", exp, "=", val)
        

        Like

  • Unknown's avatar

    Jim Randell 8:47 am on 17 February 2026 Permalink | Reply
    Tags: ,   

    Teaser 2457: [Wedding gifts] 

    From The Sunday Times, 25th October 2009 [link]

    On their wedding day, Lauren was 19 years old and John was older than that. John gave Lauren half of the money he had, plus a further £19, being a love token of £1 for each year of her life. Then Lauren gave John half of all the money she then had, plus £1 for each year of his life.

    The amount of money John now had was an exact multiple of the amount he would have had if the order of giving had been reversed.

    How much money did John start with?

    This puzzle was originally published with no title.

    [teaser2457]

     
    • Jim Randell's avatar

      Jim Randell 8:48 am on 17 February 2026 Permalink | Reply

      If J starts with an amount X and is a (> 19) years old. L starts with an amount Y.

      Then in the actual scenario:

      J has X; L has Y
      J→L: X/2 + 19
      J has (X/2 − 19); L has (Y + X/2 + 19)
      L→J: (Y + X/2 + 19)/2 + a
      J has (X/2 − 19 + Y/2 + X/4 + 19/2 + a) = (3X/4 + Y/2 + a − 19/2)

      In the reverse scenario:

      J has X; L has Y
      L→J: Y/2 + a
      J has (X + Y/2 + a); L has (Y/2 − a)
      J→L: (X + Y/2 + a)/2 + 19
      J has (X + Y/2 + a − (X/2 + Y/4 + a/2 + 19)) = (X/2 + Y/4 + a/2 − 19)

      And so:

      (3X/4 + Y/2 + a − 19/2) is a multiple of (X/2 + Y/4 + a/2 − 19)

      i.e., for some positive integer k:

      k = (3X/4 + Y/2 + a − 19/2) / (X/2 + Y/4 + a/2 − 19)

      k = 2 + (114 − X)/(2X + Y + 2a − 76)

      And if we want a proper multiple, then k ≥ 2.

      Considering the fractional part of the expression:

      If X > 114, then the numerator is negative, and the denominator is positive, so this is no good.

      if X = 114, then the numerator is zero, and the denominator is positive, whatever the values of Y and a are.

      If X < 114, then we need to choose values of Y and a, such that Y ≥ 2a ≥ 40, and the denominator divides into the numerator to give a positive integer.

      Here is a Python program to examine the scenarios:

      from enigma import (irange, divisors_pairs, printf)
      
      # consider possible values for X
      # X/2 >= 19 => X >= 38
      # 114 - X >= 0 => X <= 114
      
      # consider possible X values
      for X in irange(38, 114):
        n = 114 - X
        if n == 0:
          # output scenario
          printf("X = {X}; a >= 20; Y >= 2n")
        else:
          # consider possible divisors of n
          for (d, x) in divisors_pairs(n, every=1):
            k = 2 + x
            # calculate z = (Y + 2a)
            z = d + 76 - 2*X
            # which must be >= 40 + 40 = 80
            if z < 80: continue
            # consider possible a values >= 20
            for a in irange(20, 90):
              Y = z - 2 * a
              if Y < 0: break
              if Y < 2 * a: continue
              # output scenario
              printf("X = {X} [n = {n}; d = {d}] -> k = {k}; a={a} Y={Y}")
      

      Solution: John started with £ 114.

      And it doesn’t matter how old he is (a), or Lauren’s initial amount (Y), as long as Y ≥ 2a ≥ 40, then John will end up with twice the amount in the reverse scenario.

      For example: X = 114, Y = 62, a = 25.

      actual:
      start: J=114, L=62
      (57+19) J→L: J=38, L=138
      (69+25) J→L: J=132, L=44

      reverse:
      start: J=114, L=62
      (31+25) L→J: J=170, L=6
      (85+19) J→L: J=66, L=110

      And 132 = 2×66.

      Like

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel
Design a site like this with WordPress.com
Get started