Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • 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(A - T) > 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^2 + (30 − x)^2) = √(16^2 + x^2)
      3136 + (900 − 60x + x^2) = 256 + x^2

      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^2 + (30 − x)^2) − √(16^2 + x^2)
      f'(x) = ((x − 30) / √(x^2 − 60x + 4036)) − (x / √(x^2 + 256))

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

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

      (x − 30)^2 (x^2 + 256) = x^2 (x^2 − 60x + 4036)
      (x^2 − 60x + 900) (x^2 + 256) = x^2 (x^2 – 60x + 4036)
      x^4 − 60x^3 + 1156x^2 − 15360x + 230400 = x^4 − 60x^3 + 4036x^2

      2880x^2 + 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

  • 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

  • Unknown's avatar

    Jim Randell 7:30 am on 15 February 2026 Permalink | Reply
    Tags:   

    Teaser 3308: New towns 

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

    Three new towns (A, B and C) were being built, with straight roads joining them. Each road was a whole number of miles in length, with AB the same as BC and a total length of the three roads of 108 miles.

    A new power station was needed for the three towns and two plans were considered. The Plan 1 site at point P was to be the same distance from each town (a perfect square number of miles). The Plan 2 site was to be a whole number of miles along PB such that the total length of cable connecting the power station to each town was a whole number of miles. Given the above, no shorter total cable length of a whole number of miles was possible, so plan 2 was adopted.

    What was the total cable length?

    [teaser3308]

     
    • Jim Randell's avatar

      Jim Randell 9:00 am on 15 February 2026 Permalink | Reply

      This Python program runs in 73ms. (Internal runtime is 291µs).

      from enigma import (Accumulator, irange, is_triangle, rcs, fdiv, is_square_p, point_dist, intr, printf)
      
      # if float <f> is within <t> of integer <i> return <i>
      def snapf(f, t=1e-6):
        i = intr(f)
        if abs(i - f) < t: return i
      
      T = 108  # T = AB + BC + AC
      # choose a value for x (= AB = BC)
      for x in irange(1, (T - 1) // 2):
        # calculate y (= AC)
        y = T - 2*x
        if not is_triangle(x, x, y): continue
        # calculate the positions of the towns
        z = 0.5 * y
        h = rcs(x, -z)
        (A, B, C) = ((z, 0), (0, h), (-z, 0))
      
        # P = (0, h - p) is the circumcentre of triangle ABC
        # distance p = BP must be an integer (and a perfect square)
        p = snapf(fdiv(x * x, 2 * h))
        if not is_square_p(p): continue
        printf("x={x} y={y} -> h={h} p={p} [A={A} B={B} C={C}]")
      
        # find minimum total length of cable
        r = Accumulator(fn=min, collect=1)
      
        # choose a distance along BP (excluding endpoints) to site Q
        for d in irange(1, p - 1):
          # calculate total length of cable required
          Q = (0, h - d)
          ds = list(snapf(point_dist(Q, X)) for X in [A, B, C])
          # distance to each town is an integer
          if None in ds: continue
          t = sum(ds)
          # output scenario
          printf("-> d={d} Q={Q} ds={ds} t={t}")
          r.accumulate_data(t, d)
      
        # output minimal length
        printf("minimal length = {r.value} [d={r.data}]")
        printf()
      

      Solution: The total length of cable required is 60 miles.

      If we consider A to be 24 miles due east of O, and C to be 24 miles due west of O, then B is 18 miles due north of O.

      The distances are AB = BC = 30 miles, AC = 48 miles, total = 108 miles.

      The point P is 7 miles due south of O, and so the distances AP = BP = CP = 25 miles.

      Plan 2 sites the power station at a point Q that is 10 miles due north of O giving distances:

      Q = 10 miles north of O
      AQ = CQ = 26
      BQ = 8
      total = 60

      Other possible positions (along PB) for the power station that give integer distances to each town are:

      Q = 7 miles north of O
      AQ = CQ = 25
      BQ = 11
      total = 61

      Q = O
      AQ = CQ = 24
      BQ = 18
      total = 66

      Note that if the power station were placed at B, then the total amount of cable required would also be the minimum of 60 miles.

      And if it were placed at P, then the total amount of cable required would 25 miles to each town = 75 miles.

      The absolute minimum amount of cable required is 59.57 miles, with the power station placed at a point 13.86 miles due north of O.

      Like

    • Frits's avatar

      Frits 8:09 am on 16 February 2026 Permalink | Reply

      Based on Jim’s program.
      Good chance I don’t understand anymore if I will look at it at a later date.

      I didn’t check if Plan 2 used a shorter total cable length then Plan 1.

      from enigma import is_square, ceil
      
      # distance metric between 2 points
      dist = lambda p, q: ((p[0] - q[0])**2 + (p[1] - q[1])**2)**.5
      
      sol = 99999
      # choose a value for x (= AB = BC), y < 2x so 4x > 108
      for x in range(28, 54):
        # calculate y (= AC)
        y = 108 - 2 * x
        
        # calculate height (line B perpendicular to AC)
        h = 6 * (3 * x - 81)**.5
        # calculate the distance p = BP
        p, r = divmod(x**2, 2 * h)
        if r or not is_square(p): continue
        
        A = (y // 2, 0)
        # derive points that are an integer distance away from point A
        sol = min(sol, min(2 * sq + h - n for n in range(ceil(h)) 
                           if (sq := is_square((54 - x)**2 + n**2))))
          
      print(f"minimal length = {sol}")
      

      Like

      • Jim Randell's avatar

        Jim Randell 11:56 am on 16 February 2026 Permalink | Reply

        @Frits: This doesn’t seem to check all integer points along PB (there should be p − 1 of them, if we exclude the endpoints).

        Like

        • Frits's avatar

          Frits 4:33 pm on 16 February 2026 Permalink | Reply

          @Jim, my code is not totally correct.

          I had noticed that the distances from Q to A and Q to C in the optimal solution were symmetrical around d=h (fi these distances were the same
          for d=h-1 and d=h+1). The distance from Q to B is not symmetrical around d=h but is increasing. This means that if d > h potential new total length of cable will always be higher than the other symmetrical value (at least if p <= 2.h).

          That's why I took a shortcut to check fewer entries. That is not always allowed.

          Like

          • Jim Randell's avatar

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

            @Frits: Fair enough (although does that not assume the intersection of AC and BP is a (half-) integer distance from B and P – is this justified in general?). It just wasn’t clear to me from the code you posted that all possible positions would be considered.

            Like

    • Alex.T.Sutherland's avatar

      Alex.T.Sutherland 2:32 pm on 18 February 2026 Permalink | Reply

      Given:- Towns form an isosceles triangle AB = a; BC = a; AC = b;
      Periphery = 2*a + b = 108. b is even;
      Plan 1. P is the centre of the circumsbribed circle thro’the towns
      and R is the radius (a perfect square ).
      The line PB halves AC at D and is perpendicular to AC.
      P is distant R from B in the appropriate direction.

      Soln:- Using R = a^2 / ( sqrt(4*(a^2) -b^2).
      2*a+b = 108.
      R is a perfect square.
      Solve for R,a,b
      (Since b is even use it instead of ‘a’..less nuumber of calcs).
      This gives R,a,b,and the postion of P.
      The total cable length in Plan 1 = 3*R.
      P = site location for Plan 1.

      S is the site location on line PB ,integer from P distant x = [1:R] .
      For each x calculate Length = 2*AS + BS;
      Find integer pairs (x Lx).(I have found 4 such pairs…one is S=B ie x= R).
      There are 2 with a minimum length (B being one of them).
      The answer is either one.
      This does not affect the required answer only where the site is to be located.

      Answer: Significantly less than Plan_1 which is 3*R.
      Lots of Pythagorean triangles.
      Sum of the digits for each of [AS BS CS] are all equal.
      (as is the digit product of b/2).

      Time :- Aprox. 0.5 ms

      Like

  • Unknown's avatar

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

    Teaser 2458: [Chocolate cylinders] 

    From The Sunday Times, 1st November 2009 [link]

    Willy took a cylindrical cake of radius a whole prime number of millimetres. He coated the curved surface with a layer of chocolate an odd prime number of millimetres thick and let it set. He repeated this a few times, building up layers of the same thickness. Being a silly Willy, he then left the cake in the sun and all the chocolate melted. So he used it to make nine solid cylinders of chocolate, each of radius 20 mm and length the same as the original cake.

    How many layers of chocolate did he apply to the cake?

    This puzzle was originally published with no title.

    [teaser2458]

     
    • Jim Randell's avatar

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

      Suppose the cake has radius r and height h.

      Then, if n layers of chocolate, each of thickness t, are added, the total volume of chocolate used is:

      V = 𝝅 (r + n.t)^2 h − 𝝅 r^2 h

      And this same volume can also be used to make 9 solid cylinders of chocolate, each with a radius of 20 mm:

      V = 9 𝝅 20^2 h

      Hence:

      𝝅 (r + n.t)^2 h − 𝝅 r^2 h = 9 𝝅 20^2 h
      (r + n.t)^2 − r^2 = 60^2

      The LHS is the difference of 2 squares, so can be rewritten:

      (n.t)(2r + n.t) = 60^2

      So we can solve the puzzle by looking at the divisors of 60^2.

      The following Python program runs in 68ms. (Internal runtime is 96µs).

      from enigma import (divisors_pairs, div, is_prime, printf)
      
      # consider divisors of 3600
      for (a, b) in divisors_pairs(60, 2):
        r = div(b - a, 2)
        if r is None or not is_prime(r): continue
      
        printf("r={r} [nt={a}]")
        for (n, t) in divisors_pairs(a, every=1):
          if not (t % 2 == 1 and is_prime(t)): continue
          printf("-> n={n} t={t}")
      

      Solution: Willy applied 10 layers of chocolate to the cake.

      The situation is apparently:

      cake radius = 11 mm
      layer thickness = 5 mm
      number of layers = 10

      So the cake is only 22 mm across, and it was then coated with 10 layers of 5 mm chocolate. Which means the finished item was 122 mm across with only a tiny 22 mm centre consisting of cake.

      Like

    • Ruud's avatar

      Ruud 9:48 am on 13 February 2026 Permalink | Reply

      import peek
      import istr
      
      for r, d, n in istr.product(istr.primes(1000), istr.primes(3, 1000), range(2, 20)):
          if (r + n * d) ** 2 - r**2 == 9 * (20**2):
              peek(r, n, d)
      

      Like

  • Unknown's avatar

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

    Teaser 2333: [Continuous ledger] 

    From The Sunday Times, 10th June 2007 [link]

    The firm I work for has kept a ledger since its foundation. One fresh page is used each day, and these pages have been numbered consecutively since the firm was founded. During a weekend last year I noticed that the page number was a perfect square. Then, during a weekend earlier this year, I noticed that the page number was a perfect cube. On another day earlier that month, I had noticed that the page number was a perfect fourth power.

    When will the number next be palindromic?

    This puzzle was originally published with no title.

    There are now 1300 Teaser puzzles available on the site — which is 25 years worth of weekly puzzles. (And between S2T2 and Enigmatic Code there are 3866 puzzles available in total).

    [teaser2333]

     
    • Jim Randell's avatar

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

      You need to take into account the date the puzzle was originally published when solving this puzzle.

      This Python program runs in 72ms. (Internal runtime is 1.12ms).

      from datetime import (date, timedelta)
      from enigma import (powers, inf, first, le, repeat, inc, peek, irange, is_npalindrome, printf)
      
      # date of puzzle (all dates must be earlier than this)
      pub = date(2007, 6, 10)
      
      # consider numbers up to 200_000
      (sqs, cbs, p4s) = (first(powers(1, inf, k), count=le(200000)) for k in [2, 3, 4])
      
      # is a date a weekend (= Saturday (6) or Sunday (7))
      is_weekend = lambda d: d.isoweekday() in {6, 7}
      
      # consider powers of 4
      for p4 in p4s:
      
        # look for a higher cube, potentially within the same month
        for cb in reversed(cbs):
          if not (cb > p4): break
          if cb - p4 > 30: continue
      
          # consider possible dates for the cube in the current year (before publication)
          for dcb in repeat(inc(timedelta(days=-1)), pub):
            if dcb.year < pub.year: break
            # must be on a weekend
            if not is_weekend(dcb): continue
            # calculate date for the power of 4
            dp4 = dcb - timedelta(days=cb - p4)
            # must be in the same month
            if not (dp4.month == dcb.month): continue
      
            # look for earlier perfect squares
            for sq in sqs:
              if not (sq < p4): break
              # calculate date for the square
              dsq = dp4 - timedelta(days=p4 - sq)
              y = dp4.year - dsq.year
              if not (0 < y < 2): continue
              # must be on a weekend
              if not is_weekend(dsq): continue
      
              # when is the next palindromic date (after cb)
              pl = peek(n for n in irange(cb + 1, inf) if is_npalindrome(n))
              dpl = dp4 + timedelta(days=pl - p4)
      
              # calculate founding date
              df = dp4 - timedelta(days=p4 - 1)
      
              # output solution
              printf("p4 = D{p4} = {dp4}; cb = D{cb} = {dcb}; sq = D{sq} = {dsq}; pl = D{pl} = {dpl}; D1 = {df}")
      

      Solution: The next palindromic number will occur on 20th June 2007.

      The square number is day 50176 (= 224^2) on 2006-01-07 (Saturday).

      The perfect cube is day 50653 (= 37^3) on 2007-04-29 (Sunday).

      The power of 4 is day 50625 (= 15^4) on 2007-04-01 (Sunday).

      And the next palindromic number after 50653 is 50705 on 2007-06-20 (Wednesday), i.e. 10 days after the puzzle was published.

      If the ledger has been in continual operation from the day of founding, then day 1 was 1868-08-23 (Sunday).

      Like

  • Unknown's avatar

    Jim Randell 7:12 am on 8 February 2026 Permalink | Reply
    Tags:   

    Teaser 3307: A dish best served cold 

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

    King Otto’s birthday cake was a giant mousse. Its tiers were two concentric regular octagons. The upper tier’s span equalled the lower tier’s side-length (the diagrams show top and side views). Each tier’s height equalled the base tier octagon’s side-length (a two-figure whole number of inches under 20) divided by the square root of two (chef used a 45 degree right triangle gauge for this). When all tables at the party (arranged in three equal lines) were fully occupied (each set for the same number of couples) each person got an identical portion of eight cubic inches of cake (not necessarily in the shape of a cube). The small amount left over was less than this.

    How many tables were there?

    [teaser3307]

     
    • Jim Randell's avatar

      Jim Randell 7:57 am on 8 February 2026 Permalink | Reply

      (See also: Teaser 2976, Teaser 2921).

      This Python program considers possible side lengths of the octagonal lower tier, calculates the total volume of both tiers, and then divides it into 8 cu in portions.

      The number of portions is then allocated to three equal rows of tables, with the same number of guests at each table. (I allowed between 2 and 20 couples at each table, which gives a single answer).

      It runs in 70ms. (Internal runtime is 87µs).

      from enigma import (irange, sqrt, fdiv, intr, div, divisors_pairs, printf)
      
      r2 = sqrt(2)
      
      # area of an octagon with side x
      area = lambda x: 2 * x * x * (1 + r2)
      
      # consider possible side lengths of the larger octagon = x (2-digit numbers less than 20)
      for x in irange(10, 19):
        # calculate the height of the tiers = h
        h = fdiv(x, r2)
        # volume of the lower tier
        V1 = area(x) * h
        # volume of the upper tier
        z = fdiv(x, 1 + r2)
        V2 = area(z) * h
        # total volume of the cake (to the nearest cu in)
        V = intr(V1 + V2)
      
        # divide into portions = k; remainder = r
        (k, r) = divmod(V, 8)
        # there is a small amount remaining
        if r == 0: continue
        # there are 3 rows of tables, and some couples on each table
        p = div(k, 6)
        if p is None: continue
        # each seating the same number of couples = a, with tables per row = b
        for (a, b) in divisors_pairs(p, every=1):
          if not (a > 1): continue  # more than 1 couple per table
          if a > 20: break  # but no more than 20
          # output scenario
          printf("x={x} h={h:.2f} -> V={V} k={k} r={r}")
          printf("-> 3 rows of: {p} portions = {a} couples on {b} tables", p=2*p)
          printf("--> total tables = {T}", T=3*b)
          printf()
      

      Solution: There were 183 tables.

      The tables are arranged in 3 rows, each with 61 tables, and each table seats 3 couples (= 6 guests), for a total of 1098 guests, which requires 8784 cu in of cake.

      The lower tier of the cake has side length of 13 in, which makes the total volume of the cake 8788 cu in (leaving 4 cu in left over).

      Without restrictions on the number of guests at each table we could seat 1098 guests using:

      3 rows, each with 1 table, and each table seats 183 couples (366 guests)
      3 rows, each with 3 tables, and each table seats 61 couples (122 guests)
      3 rows, each with 61 tables, and each table seats 3 couples (6 guests)
      3 rows, each with 183 tables, and each table seats 1 couple (2 guests)

      I decided to keep the tables a manageable size with 3 couples (= 6 guests) at each table.

      But perhaps it would have been better if the total number of guests had been asked for (or the allowable number of guests per table specified).


      If you determine the total volume of cake in terms of x (or let Wolfram Alpha determine it for you [ link ]), you will find:

      V = 4 x^3

      Which can be used instead of lines 10-18 to directly to calculate the volume of the cake (without using floating point approximations).

      And can also be used in a manual solution, as follows:

      We see that for even x the volume will be an exact multiple of 8, and there will be no unused portion, so we can consider odd values for x, and calculate V (which will have leave a remainder of 4 cu in). The number of portions p must then be divisible by 6 (for each couple in each of the 3 rows).

      x = 11: V = 5324 = 665×8 + 4; p/6 = 110.83…
      x = 13: V = 8788 = 1098×8 + 4; p/6 = 183
      x = 15: V = 13500 = 1687×8 + 4; p/6 = 281.16…
      x = 17: V = 19652 = 2456×8 + 4; p/6 = 409.33…
      x = 19: V = 27436 = 3429×8 + 4; p/6 = 571.5

      Only x = 13, gives a viable number of portions. And we can further split 183 to give the four candidate scenarios given above:

      183 = 183×1 → 3 rows, each with 183 tables, and each table seats 1 couple (2 guests)
      183 = 61×3 → 3 rows, each with 61 tables, and each table seats 3 couples (6 guests)
      183 = 3×61 → 3 rows, each with 3 tables, and each table seats 61 couples (122 guests)
      183 = 1×183 → 3 rows, each with 1 table, and each table seats 183 couples (366 guests)

      Of these the first seems unlikely (1 couple per table), and we can reduce the remaining three to a single candidate by placing an upper limit (of 120 or fewer) on the number of guests per table.

      Giving a solution of 3 rows, each with 61 tables, so 183 tables tables in total.

      Like

      • Jim Randell's avatar

        Jim Randell 10:55 am on 17 February 2026 Permalink | Reply

        If you don’t want to do the analysis yourself (or let Wolfram Alpha do it), and you don’t want to wheel out the big guns (like SymPy), then the following class allows you to do simple arithmetic operations involving an irrational number.

        from enigma import enigma
        
        Q = enigma.Rational()
        
        # manipulate numbers of the form: a + b*sqrt(n) where n is an integer, and a and b are rational
        class Surd(object):
        
          def __init__(self, n, a=0, b=1):
            self.n = n
            self.a = a
            self.b = b
        
          def __repr__(self):
            return str.format("Surd[{a} + {b}*sqrt({n})]", a=self.a, b=self.b, n=self.n)
        
          # evaluate to a standard numeric type (you might get a float, or a complex, or a Q object)
          def val(self):
            (n, a, b) = (self.n, self.a, self.b)
            return (a if b == 0 else a + b * n**0.5)
        
          # extract (n, a, b, c, d) parameters
          def _params(self, other):
            if not isinstance(other, Surd): other = Surd(self.n, other, 0)
            if not (self.n == other.n): raise ValueError("Surd: incompatible values")
            return (self.n, self.a, self.b, other.a, other.b)
        
          # calculate self + other
          def __add__(self, other):
            (n, a, b, c, d) = self._params(other)
            return Surd(n, a + c, b + d)
        
          __radd__ = __add__
        
          def __neg__(self):
            return Surd(self.n, -self.a, -self.b)
        
          def __sub__(self, other):
            (n, a, b, c, d) = self._params(other)
            return Surd(n, a - c, b - d)
        
          __rsub__ = lambda self, other: -self + other
        
          # calculate self * other
          def __mul__(self, other):
            (n, a, b, c, d) = self._params(other)
            return Surd(n, a * c + b * d * n, a * d + b * c)
        
          __rmul__ = __mul__
        
          # calculate self / other
          def __truediv__(self, other):
            (n, a, b, c, d) = self._params(other)
            D = c * c - d * d * n
            return Surd(n, Q(a * c - b * d * n, D), Q(b * c - a * d, D))
        
          def __rtruediv__(self, other):
            if not isinstance(other, Surd): other = Surd(self.n, other, 0)
            return other.__truediv__(self)
        
          # Python 2 support
          __div__ = __truediv__
          __rdiv__ = __rtruediv__
        

        My program can then be modified to use this class, and we find that the irrational falls out of the expression and always leaves us with an exact integer volume for the cake.

        from enigma import (irange, as_int, div, divisors_pairs, printf)
        
        # representation of sqrt(2)
        r2 = Surd(2)
        
        # area of an octagon with side x
        area = lambda x: 2 * x * x * (1 + r2)
        
        # consider possible side lengths of the larger octagon = x (2-digit numbers less than 20)
        for x in irange(10, 19):
          # calculate the height of the tiers = h
          h = x / r2
          # volume of the lower tier
          V1 = area(x) * h
          # volume of the upper tier
          z = x / (1 + r2)
          V2 = area(z) * h
          # total volume of the cake
          V = V1 + V2
          # retrieve the actual numeric value
          V = as_int(V.val())
        
          # divide into portions = k; remainder = r
          (k, r) = divmod(V, 8)
          # there is a small amount remaining
          if r == 0: continue
          # there are 3 rows of tables, and some couples on each table
          p = div(k, 6)
          if p is None: continue
          # each seating the same number of couples = a, with tables per row = b
          for (a, b) in divisors_pairs(p, every=1):
            if not (a > 1): continue  # more than 1 couple per table
            if a > 20: break  # but no more than 20
            # output scenario
            printf("x={x} h={h:.2f} -> V={V} k={k} r={r}", h=float(h.val()))
            printf("-> 3 rows of: {p} portions = {a} couples on {b} tables", p=2*p)
            printf("--> total tables = {T}", T=3*b)
            printf()
        

        Like

  • Unknown's avatar

    Jim Randell 8:39 am on 6 February 2026 Permalink | Reply
    Tags:   

    Teaser 2459: [Queue for tea] 

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

    At today’s tea break only one of the six workers in the canteen queue actually had tea, the other five having different drinks. Terry was directly behind the one who had hot chocolate, while Tony (who was not at the front of the queue) was immediately in front of the sparkling-water drinker (who was not Terry). The milk drinker was three places in front of the person who had white coffee. Tilly was directly behind Tommy. Timmy was two places ahead of the hot-chocolate drinker. The person at the front of the queue had black coffee.

    Who had tea? And where was Trudy in the queue?

    This puzzle was originally published with no title.

    [teaser2459]

     
    • Jim Randell's avatar

      Jim Randell 8:41 am on 6 February 2026 Permalink | Reply

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

      The following run file executes in 79ms. (Internal runtime of the generated code is 87µs).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # we assign queue positions 1 (= front) to 6 (= back)
      --digits="1-6"
      --macro="@Terry = A"
      --macro="@Tony = B"
      --macro="@Tilly = C"
      --macro="@Tommy = D"
      --macro="@Timmy = E"
      --macro="@Trudy = F"
      --macro="@Te = G"  # Tea
      --macro="@HC = H"  # Hot Chocolate
      --macro="@SW = I"  # Sparkling Water
      --macro="@Mk = J"  # Milk
      --macro="@WC = K"  # White Coffee
      --macro="@BC = L"  # Black Coffee
      --distinct="ABCDEF,GHIJKL"
      
      # "Terry was directly behind Hot Chocolate"
      "@HC + 1 = @Terry"
      
      # "Tony (who is not at the front of the queue) ..."
      "@Tony > 1"
      # "... was immediately in front of Sparkling Water ..."
      "@SW - 1 = @Tony"
      # "... (who was not Terry)"
      "@SW != @Terry"
      
      # "Milk was 3 places in front of White Coffee"
      "@WC - 3 = @Mk"
      
      # "Tilly was directly behind Tommy"
      "@Tommy + 1 = @Tilly"
      
      # "Timmy was 2 places ahead of Hot Chocolate"
      "@HC - 2 = @Timmy"
      
      # "The person at the front of the queue had Black Coffee"
      "@BC = 1"
      
      --template="(A B C D E F) (G H I J K L)"
      --solution=""
      --verbose="1-W"
      

      And here is a Python program that runs the solver, and then formats the output nicely:

      from enigma import (SubstitutedExpression, irange, printf)
      
      # load the puzzle
      p = SubstitutedExpression.from_file(["{dir}/teaser2459.run"])
      
      # link symbols to values
      s2n = dict(A='Terry', B='Tony', C='Tilly', D='Tommy', E='Timmy', F='Trudy')
      s2d = dict(G='tea', H='hot chocolate', I='sparkling water', J='milk', K='white coffee', L='black coffee')
      
      # solve the puzzle and format solutions
      for s in p.solve(verbose="0"):
        # for each position 1-6 fill out a corresponding name and drink
        (name, drink) = (dict((s[k], v) for (k, v) in m.items()) for m in (s2n, s2d))
        # output the queue
        for i in irange(1, 6):
          (n, d) = (m.get(i, '???') for m in (name, drink))
          printf("{i}: {n}, {d}")
        printf()
      

      Solution: Terry had tea. Trudy was 6th in the queue.

      The positions in the queue and drinks required are:

      1: Timmy, black coffee
      2: Tommy, milk
      3: Tilly, hot chocolate
      4: Terry, tea
      5: Tony, white coffee
      6: Trudy, sparkling water

      Like

  • Unknown's avatar

    Jim Randell 9:34 am on 3 February 2026 Permalink | Reply
    Tags: ,   

    Teaser 2461: [Football league] 

    From The Sunday Times, 22nd November 2009 [link]

    The saying “Most teams lose most of the time” was true of our league last season. Five teams, Albion, Broncos, City, Devils and Eagles, play each other once, earning three points for a win, one for a draw. Albion won and the teams ended in alphabetical order, the positions of teams tying on points determined by goal difference. Albion’s goal difference was four times the Broncos’. No match scores were the same; 32 goals were scored; City scored none against Albion; and Devils scored four different consecutive numbers of goals.

    What were the scores in the four Devils’ games (DvA, DvB, DvC, DvE)?

    Although this puzzle can be solved there are two possible answers (although only one of them was given when the solution was published in The Sunday Times).

    This puzzle was originally published with no title.

    [teaser2461]

     
    • Jim Randell's avatar

      Jim Randell 9:35 am on 3 February 2026 Permalink | Reply

      There are 5 teams in the league, so each team plays 4 matches. For a team to “lose most of the time”, they would have to lose at least 3 of their 4 matches. And for “most teams to lose most of the time”, there have to be at least 3 teams that lost at least 3 of their matches.

      So, of the 10 matches in the tournament, at least 9 of them must be lost/won, i.e. there can be at most 1 drawn match.

      All the scores in the matches must be different, so in increasing numbers of total goals scored in lost/won match we have the possible following scorelines:

      1: 1-0
      2: 2-0
      3: 3-0, 2-1
      4: 4-0, 3-1
      5: 5-0, 4-1, 3-2

      At this point, we have identified 9 matches, and the total number of goals scored in them is 32. So all these must take place, and the remaining match must be a 0-0 draw. And we have identified the scores in all of the 10 matches.

      This Python program allocates the scores to the matches. It runs in 20s (using PyPy).

      from enigma import (Football, subsets, diff, cproduct, ordered, is_consecutive, printf)
      
      # scoring system
      football = Football(games="wdl", points=dict(w=3, d=1))
      
      # scores in the 10 matches; all different, and 32 goals scored in total
      scores = [(1, 0), (2, 0), (3, 0), (2, 1), (4, 0), (3, 1), (5, 0), (4, 1), (3, 2), (0, 0)]
      assert sum(x + y for (x, y) in scores) == 32
      
      # allocate <k> scores from <scs>
      def allocate(k, scs):
        for ss in subsets(scs, size=k, select='P'):
          rs = diff(scs, ss)
          for xys in cproduct({(x, y), (y, x)} for (x, y) in ss):
            yield (xys, rs)
      
      # calculate the table and goal difference from a sequence of scores
      def table(ss, ts):
        T = football.table(football.outcomes(ss), ts)
        (f, a) = football.goals(ss, ts)
        return (T, f - a)
      
      # X did better than Y
      def better(ptsX, ptsY, gdX, gdY):
        return ptsX > ptsY or (ptsX == ptsY and gdX > gdY)
      
      # choose the scores in D's matches
      for ((AD, BD, CD, DE), scs1) in allocate(4, scores):
        # D's goals in each match must be consecutive (in some order)
        if not is_consecutive(ordered(AD[1], BD[1], CD[1], DE[0])): continue
        # calculate the table for D
        (D, gdD) = table([AD, BD, CD, DE], [1, 1, 1, 0])
      
        # choose the scores in C's matches
        for ((AC, BC, CE), scs2) in allocate(3, scs1):
          # C scored 0 goals in the A v C match
          if not (AC[1] == 0): continue
          # calculate the table for C
          (C, gdC) = table([AC, BC, CD, CE], [1, 1, 0, 0])
          # C did better than D
          if not better(C.points, D.points, gdC, gdD): continue
      
          # choose the scores in B's matches
          for ((AB, BE), scs3) in allocate(2, scs2):
            # calculate the table for B
            (B, gdB) = table([AB, BC, BD, BE], [1, 0, 0, 0])
            # B did better than C
            if not better(B.points, C.points, gdB, gdC): continue
      
            # allocate the remaining match
            for ([AE], _) in allocate(1, scs3):
              # calculate the table for A
              (A, gdA) = table([AB, AC, AD, AE], [0, 0, 0, 0])
              # A did better than B
              if not better(A.points, B.points, gdA, gdB): continue
              # A's goal difference is 4 times B's
              if not (gdA == 4 * gdB): continue
      
              # calculate the table for E
              (E, gdE) = table([AE, BE, CE, DE], [1, 1, 1, 1])
              # D is higher than E
              if not better(D.points, E.points, gdD, gdE): continue
      
              # at least 3 teams lost at least 3 matches
              if not sum(X.l >= 3 for X in [A, B, C, D, E]) >= 3: continue
      
              # output scenario
              printf("AB={AB} AC={AC} AD={AD} AE={AE} BC={BC} BD={BD} BE={BE} CD={CD} CE={CE} DE={DE}")
              printf("A={A} gd={gdA}")
              printf("B={B} gd={gdB}")
              printf("C={C} gd={gdC}")
              printf("D={D} gd={gdD}")
              printf("E={E} gd={gdE}")
              printf()
      

      Solution: There are two possible answers:

      DvA = 0-3; DvB = 2-3; DvC = 1-4; DvE = 3-1
      DvA = 1-4; DvB = 2-3; DvC = 0-3; DvE = 3-1

      Only the first of these was given in the published solution.

      These correspond to the following 4 scenarios:

      AvB = 0-0; AvC = 4-0; AvD = 3-0; AvE = 5-0; BvC = 2-1; BvD = 3-2; BvE = 1-0; CvD = 4-1; CvE = 0-2; DvE = 3-1
      AvB = 0-0; AvC = 4-0; AvD = 3-0; AvE = 5-0; BvC = 1-0; BvD = 3-2; BvE = 2-1; CvD = 4-1; CvE = 0-2; DvE = 3-1
      AvB = 0-0; AvC = 4-0; AvD = 4-1; AvE = 5-0; BvC = 2-1; BvD = 3-2; BvE = 1-0; CvD = 3-0; CvE = 0-2; DvE = 3-1
      AvB = 0-0; AvC = 4-0; AvD = 4-1; AvE = 5-0; BvC = 1-0; BvD = 3-2; BvE = 2-1; CvD = 3-0; CvE = 0-2; DvE = 3-1

      i.e. the order of {AvD, CvD} = {3-0, 4-1} and {BvC, BvE} = {2-1, 1-0} is not determined.

      But in each case the final table is:

      1st: A → 3w1d = 10 points; goal diff = 12
      2nd: B → 3w1d = 10 points; goal diff = 3
      3rd: C → 1w3l = 3 points; goal diff = −4
      4th: D → 1w3l = 3 points; goal diff = −5
      5th: E → 1w3l = 3 points; goal diff = −6

      With teams C, D, E (i.e. “most teams”) having lost 3 of their matches (i.e. “lost most of the time”).

      Like

  • Unknown's avatar

    Jim Randell 6:53 am on 1 February 2026 Permalink | Reply
    Tags:   

    Teaser 3306: Pin-up 

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

    Over the years I have used seven different PINs. The first consisted of a four-digit number (the first digit not being zero), and thereafter each new PIN was obtained by swapping over two of the digits of the previous PIN; each time this increased the PIN.

    The first PIN (not surprisingly!) was divisible by 1, the second was divisible by 2, the third by 3, and so on, with the seventh divisible by 7 (in fact the seventh PIN was the only one divisible by 7).

    What was the third PIN?

    [teaser3306]

     
    • Jim Randell's avatar

      Jim Randell 7:06 am on 1 February 2026 Permalink | Reply

      It is quicker (and neater) to build the list in reverse order. We can start with a final PIN that is a multiple of 7 (and 3), and then work backwards, undoing the swaps, to get back to the initial PIN.

      This Python program runs in 76ms. (Internal runtime is 6.8ms).

      from enigma import (irange, subsets, nsplit, nconcat, swap, printf)
      
      # generate possible swaps by index (on a 4-digit sequence)
      swap_inds = list(subsets(irange(4), size=2))
      
      # solve the puzzle for a sequence of (<number>, <digit>) pairs
      # at each stage finding the _previous_ PIN
      def _solve(ss):
        k = len(ss)
        # are we done?
        if k == 7:
          yield ss
        else:
          (n, ds) = ss[0]
          # swap 2 digits of the following PIN
          for (i, j) in swap_inds:
            ds1 = swap(ds, i, j)
            # no leading zeros
            if ds1[0] == 0: continue
            n1 = nconcat(ds1)
            # previous PINs must be: smaller; not be divisible by 7; divisible by their position
            if n1 < n and n1 % 7 > 0 and n1 % (7 - k) == 0:
              yield from _solve([(n1, ds1)] + ss)
      
      # solve the puzzle for final PIN <n>
      def solve(n):
        # final PIN is divisible by 7
        if n % 7 > 0: return
        # find previous PINs
        for ss in _solve([(n, nsplit(n))]):
          # return the list of numbers
          yield tuple(n for (n, ds) in ss)
      
      # consider the final PIN (divisible by 7 (and 3))
      for n in irange.round(1000, 9999, step=21, rnd='I'):
        for ns in solve(n):
          printf("{ns}")
      

      Solution: The third PIN is 5286.

      There are 2 possible sequences of PINs that differ at the second PIN:

      2568 → {2586, 5268} → 5286 → 8256 → 8265 → 8562 → 8652

      Like

      • Ruud's avatar

        Ruud 11:19 am on 1 February 2026 Permalink | Reply

        How do you know that the final PIN does not start with 0 ?

        Like

        • Jim Randell's avatar

          Jim Randell 11:47 am on 1 February 2026 Permalink | Reply

          @Ruud: The first PIN has no leading zero, so is greater than 999. And each subsequent PIN is larger than the one preceding it, so all the PINs are greater than 999, hence none of them have a leading zero.

          Like

      • ruudvanderham's avatar

        ruudvanderham 11:27 am on 1 February 2026 Permalink | Reply

        Ah, I see: because it has to be greater than the first PIN, which is >=1000 .
        But, how do you know that the final PIN is divisible by 3?

        Like

        • Jim Randell's avatar

          Jim Randell 11:48 am on 1 February 2026 Permalink | Reply

          @Ruud: If a number is divisible by 3 than any number formed from a rearrangement of the digits is also divisible by 3. Hence all the PINs must be divisible by 3.

          So if the final PIN is divisible by both 7 and 3, then it must be divisible by 21 (= LCM(7, 3)).

          Like

          • Jim Randell's avatar

            Jim Randell 12:25 pm on 2 February 2026 Permalink | Reply

            @Ruud: Did you try this? It produces no output for me.

            If you want to go in steps of 21 you need to start with the final PIN and work backwards, because the final PIN is the only one that is divisible by 7.

            Like

            • Ruud's avatar

              Ruud 1:10 pm on 2 February 2026 Permalink | Reply

              I thought I did, but apparently I did not. Can you remove this?

              Like

    • Ruud's avatar

      Ruud 9:34 am on 1 February 2026 Permalink | Reply

      import istr
      
      
      def solve(pins):
          for i0, i1 in istr.combinations(range(4), r=2):
              n = len(pins) + 1
              pin = istr.join(pins[-1][int(i0)] if i == i1 else pins[-1][int(i1)] if i == i0 else pins[-1][i] for i in range(4))
              if pin > pins[-1] and pin.is_divisible_by(n):
                  if n == 7:
                      yield pins + [pin]
                  elif not pin.is_divisible_by(7):
                      yield from solve(pins + [pin])
      
      
      for pin in istr(range(1000,10000)):
          for pins in solve([pin]):
              print(pins)

      Like

    • Frits's avatar

      Frits 1:32 pm on 1 February 2026 Permalink | Reply

      from itertools import combinations
      
      # generate and check the k-th PIN (decreasing k)
      def check(s, k):
        if not k:
          yield int(''.join(s[4]))      # third PIN
        else:
          # swap 2 digits
          for i, j in combinations(range(4), 2):
            # swap should result in a lower number > 999
            if s[-1][i] <= s[-1][j] or (i == 0 and s[-1][j] == "0"): continue
            new = [s[-1][j] if y == i else s[-1][i] if y == j else s[-1][y]
                 for y in range(4)]  
            # the seventh PIN was the only one divisible by 7
            if (n := int(''.join(new))) % k == 0 and n % 7: 
              yield from check(s + [new], k - 1)       
      
      sols = set()
      # first possible seventh PIN divisible by 21 (div 7, div 3)
      f = next(i for i in range(2001, 2024, 3) if i % 7 == 0)
      
      for p7 in range(f, 10000, 21):
        # div 5 case
        if "5" in (s := str(p7)):
          # we need an even digit for the div 4 case
          if set("02468").isdisjoint(s): continue
        elif "0" in s:
          # we need at least 2 even digits for the div 4 case 
          if sum(int(x) % 2 for x in s) > 2: continue
        else: 
          continue  
          
        # we need at least 7 different permutations  
        if len(set(s)) < 3: continue
        
        # generate all PINs and store the third PIN
        for p3 in check([s], 6):
          sols.add(p3)
          
      print(f"answer: {' or '.join(str(s) for s in sols)}")    
      

      Like

    • Ruud's avatar

      Ruud 5:43 am on 2 February 2026 Permalink | Reply

      ChatGPT solved it in 47 seconds:

      from itertools import permutations, combinations
      
      def swaps(a, b):
          """Return True if b can be obtained from a by swapping exactly two digits."""
          diffs = [i for i in range(4) if a[i] != b[i]]
          return len(diffs) == 2 and a[diffs[0]] == b[diffs[1]] and a[diffs[1]] == b[diffs[0]]
      
      def valid_chain(chain):
          """Check divisibility, increasing order, and swap condition."""
          for i in range(7):
              if chain[i] % (i+1) != 0:
                  return False
              if i > 0:
                  if chain[i] <= chain[i-1]:
                      return False
                  if not swaps(str(chain[i-1]), str(chain[i])):
                      return False
          # only the 7th is divisible by 7
          return all(chain[i] % 7 != 0 for i in range(6))
      
      solutions = []
      
      # choose 4 distinct digits, first not zero
      digits = [str(d) for d in range(10)]
      
      for digs in combinations(digits, 4):
          for perm in permutations(digs):
              if perm[0] == "0":
                  continue
              nums = sorted(
                  int("".join(p)) for p in permutations(perm) if p[0] != "0"
              )
              for chain in permutations(nums, 7):
                  if valid_chain(chain):
                      solutions.append(chain)
      
      # print solutions and the third PIN
      for sol in solutions:
          print(sol, "→ third PIN:", sol[2])
      

      Scary …

      Like

      • Ruud's avatar

        Ruud 6:10 am on 2 February 2026 Permalink | Reply

        Maybe less scary than I thought.
        Although, ChatGPT gave the proper answer, it can’t have been done with this pure brute force program. It takes for ever, even for one PIN.

        Like

      • Jim Randell's avatar

        Jim Randell 3:48 pm on 3 February 2026 Permalink | Reply

        This code looks like it might solve the puzzle, although it has decided to generate all possible sequences of 7 numbers and the check to see if it can find any that work (which itself quite a slow approach). But the code it has written for doing this is terrible.

        First it selects 4 digits from 10 (= C(10, 4) = 210 arrangements, and also means it is assuming that all the digits are different), then for each of these it considers all possible permutations of the digits (= P(4, 4) = 24 arrangements), discarding those that begin with a zero. It then makes a sorted list of all arrangements of these digits that do not have a leading zero (up to P(4, 4) = 24 arrangements), and then considers all possible 7 digit permutations of this list (not combinations so they are already correctly sorted), which accounts for up to 1,744,364,160 checks for each of the possible 210 × 24 = 5,040 sequences of digits. So in total it is trying about 8.7 × 10^12 possible sequences, including many repeated checks.

        It is no wonder it does not run in a reasonable time. For quite a straightforward problem ChatGPT has made a real mess of it.

        As Samuel Johnson might say, “[it] is like a dog walking on his hind legs. It is not done well; but you are surprised to find it done at all”.

        In my experience ChatGPT is good at producing something that looks plausible. So you need to know enough about what you are asking it, so that you can determine if the answer is actually reasonable. And this case, it is a massive failure.

        The really scary thing is that some people treat its responses as though it knows what it is talking about.

        Liked by 1 person

    • Ruud's avatar

      Ruud 7:06 am on 2 February 2026 Permalink | Reply

      I asked ChatGPT again (mentioning that it didn’t finish in reosonable time). And now it comes with a solution. Amazing …

      from collections import defaultdict
      
      SWAP_PAIRS = [(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)]
      
      def swap_digits(s, i, j):
          if i == j:
              return s
          lst = list(s)
          lst[i], lst[j] = lst[j], lst[i]
          return "".join(lst)
      
      def build_neighbors():
          """
          neighbors[n] = sorted list of all valid 4-digit numbers obtainable by swapping two digits of n
                         (leading digit cannot become 0).
          """
          neighbors = [[] for _ in range(10000)]
          for n in range(1000, 10000):
              s = f"{n:04d}"
              seen = set()
              for i, j in SWAP_PAIRS:
                  t = swap_digits(s, i, j)
                  if t[0] == "0":
                      continue
                  m = int(t)
                  if m != n and m not in seen:
                      seen.add(m)
              neighbors[n] = sorted(seen)
          return neighbors
      
      def find_chains():
          neighbors = build_neighbors()
      
          # Precompute which numbers satisfy divisibility by k (k=1..7)
          div_ok = {k: [False]*10000 for k in range(1, 8)}
          for n in range(1000, 10000):
              for k in range(1, 8):
                  div_ok[k][n] = (n % k == 0)
      
          solutions = []
      
          def dfs(chain):
              step = len(chain) + 1  # next step index (1..7)
              if step == 8:
                  solutions.append(chain[:])
                  return
      
              prev = chain[-1]
      
              for nxt in neighbors[prev]:
                  if nxt <= prev:
                      continue
                  if not div_ok[step][nxt]:
                      continue
                  # only the 7th is divisible by 7
                  if step < 7 and nxt % 7 == 0:
                      continue
                  dfs(chain + [nxt])
      
          # Step 1: any 4-digit number works for divisibility by 1,
          # but it must allow an increasing chain of swaps.
          # Still, searching all 9000 starts is fine with pruning.
          for start in range(1000, 10000):
              # (Optional) tiny prune: if start has no larger neighbors, skip
              if not neighbors[start] or neighbors[start][-1] <= start:
                  continue
              dfs([start])
      
          return solutions
      
      if __name__ == "__main__":
          sols = find_chains()
          print(f"Found {len(sols)} solution chain(s).")
          for ch in sols:
              print(" -> ".join(map(str, ch)), " | third PIN:", ch[2])
      

      Like

      • Ruud's avatar

        Ruud 4:26 pm on 3 February 2026 Permalink | Reply

        I fully agree with you. But when told ChatGPT that this code does not run in reasonable time, it immediately came up with a way better solution (see my reply). And I find that amazing.

        Like

    • GeoffR's avatar

      GeoffR 10:09 am on 4 February 2026 Permalink | Reply

      Interesting reply from ChatGPT, after I put to it that it was not very efficient at coding teasers.

      It looks as though the problem is recognised and things may improve in the future. Quite a lot of detailed comments – things can only get better!

      https://chatgpt.com/share/698313ad-02fc-8000-ab4c-2a3873afb266

      Like

  • Unknown's avatar

    Jim Randell 8:33 am on 30 January 2026 Permalink | Reply
    Tags:   

    Teaser 2331: Everyone’s invited 

    From The Sunday Times, 27th May 2007 [link]

    The letters A, B, C, D, E, F, G, H and I stand for the digits 1 to 9 in some order. Thus AB, CD and EF are two-digit numbers; [and] GHI is a three-digit number. George and Martha arranged a big wedding for their granddaughter. The top table consisted of AB members (fewer than 60) of their extended family. There were CD other tables each seating EF guests. The total attendance was GHI (fewer than 500).

    What was the total attendance?

    [teaser2331]

     
    • Jim Randell's avatar

      Jim Randell 8:34 am on 30 January 2026 Permalink | Reply

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

      It runs in 92ms. (Internal runtime of the generated code is 14.8ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --digits="1-9"
      
      "A < 6"  # or: "AB < 60"
      "G < 5"  # or: "GHI < 500"
      "AB + CD * EF = GHI"
      
      --template="(AB CD EF GHI)"
      --answer="GHI"
      

      Solution: The total attendance was 489.

      The top table seated 57.

      And then there were either 12 additional tables, each seating 36; or 36 additional tables, each seating 12.

      The total attendance is thus: 57 + 12 × 36 = 489.

      Like

    • ruudvanderham's avatar

      ruudvanderham 9:46 am on 30 January 2026 Permalink | Reply

      import peek
      import istr
      
      for A, B, C, D, E, F, G, H, I in istr.permutations(range(1, 10)):
          if A <= 5 and G <= 4 and (A | B) + (C | D) * (E | F) == (G | H | I):
              peek((A | B), (C | D), (E | F), (G | H | I))
      

      Like

    • GeoffR's avatar

      GeoffR 5:52 pm on 30 January 2026 Permalink | Reply

      from itertools import permutations
      dgts = set('123456789')
      
      for p1 in permutations(dgts, 2):
          A, B = p1
          AB = int(A + B)
          if AB < 60:
              q1 = dgts.difference({A, B})
              for p2 in permutations(q1):
                  C, D, E, F, G, H, I = p2
                  CD, EF = int(C + D), int(E + F)
                  GHI = int(G + H + I)
                  if GHI == AB + CD * EF:
                      if GHI < 500:
                          print(f"Total attendance = {GHI}")
      
      # Total attendance = 489
      
      

      Like

    • BRG's avatar

      BRG 9:30 am on 3 February 2026 Permalink | Reply

      # 10.A + B + (10.C + D).(10.E + F) == 100.G + 10.H + I < 500
      # note: the order of C and E is undetermined (use C < E)
      
      from itertools import permutations
      
      # start with C and E: 100.C.E < 500  E < 4 // C + 1
      for C in range(1, 10):
        for E in range(C + 1, 4 // C + 1):
          s8 = set(range(1, 10)) - {C, E}
          # complete the number of tables (CD) and the number on tables (EF)
          for D, F in permutations(s8, 2):
            CD, EF = 10 * C + D, 10 * E + F
            s6 = s8 - {D, F}
            # now find the number on the top table (AB < 60)
            for A, B in permutations(s6, 2):
              if (AB := 10 * A + B) < 60:
                # find the attendance (< 500) and ensure correct digit allocations
                GHI = (AB := 10 * A + B) + CD * EF
                s_ghi = {int(x) for x in str(GHI)}
                if GHI < 500 and len(s_ghi) == 3 and s_ghi.issubset(s6 - {A, B}):
                  print(f"({AB = }) + ({CD = }) x ({EF = }) == ({GHI = })")
      

      Like

  • Unknown's avatar

    Jim Randell 10:05 am on 27 January 2026 Permalink | Reply
    Tags: by: Derek Emley   

    Teaser 2415: [Double shift] 

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

    I have written down a large number in which no digit occurs more than twice. If I were to move the last digit of the number from the end to the front of the number, then the effect would be to double the number. If I repeated the process with the new number, then the effect would be to double it again. And if I repeated the process again, the effect would be to double the number yet again.

    What is the number that I have written down?

    This puzzle was originally published with no title.

    [teaser2415]

     
    • Jim Randell's avatar

      Jim Randell 10:06 am on 27 January 2026 Permalink | Reply

      If we suppose the original number n is of the form:

      n = 10a + z
      where z is the final digit, and a is a k-digit number

      Then moving the final digit to the front gives us:

      2n = (10^k)z + a

      From which we derive that a is a k-digit number, such that:

      19a = (10^k − 2)z

      If the number contains no digit more than twice, then the longest the number can be is 20 digits long, and so we can consider k up to 19.

      This Python program runs in 70ms. (Internal runtime is 719µs).

      from enigma import (irange, div, nsplit, rotate, zip_eq, seq2str, printf)
      
      # check a candidate number <n> (with digits <ds>)
      def check(n, ds):
        # check no digit occurs more than twice
        if any(ds.count(x) > 2 for x in set(ds)): return
        # record shifts that result in doublings
        ns = [n]
        # collect doubling sequences
        while True:
          n *= 2
          ds = rotate(ds, -1)
          if not zip_eq(ds, nsplit(n)): break
          ns.append(n)
        return ns
      
      # look for up to 19 digits prefixes
      for k in irange(1, 19):
        x = 10**k - 2
        # consider possible final digits
        for z in irange(1, 9):
          a = div(x * z, 19)
          if a is None: continue
          n = 10*a + z
          ds = nsplit(n)
          if len(ds) != k + 1: continue
          ns = check(n, ds)
          if ns and len(ns) >= 4:
            printf("{ns}", ns=seq2str(ns, sep=" -> ", enc=""))
      

      Solution: The number is: 105263157894736842.

      The number is 18 digits long, and uses each digit twice except 0 and 9 (which are used once each).

      Shifting the final digit to the front multiple times we get:

      105263157894736842 = n
      210526315789473684 = n × 2
      421052631578947368 = n × 4
      842105263157894736 = n × 8

      A run of 4 is the longest run we can get in base 10 (as n × 16 will not have the same number of digits as n), but there is another run of 3 we can make:

      157894736842105263 = n
      315789473684210526 = n × 2
      631578947368421052 = n × 4

      These numbers are the repetends of fractions of the form k / 19.

      Like

    • Frits's avatar

      Frits 8:51 am on 31 January 2026 Permalink | Reply

      '''
        n = xbcd where x is a k-digit number and b,c and d are digits 
      2.n = dxbc
      4.n = cdxb
      8.n = bcdx with x, b and c all even
      
      8.n = bcdx also has k+3 digits so b = 8 and first digit of x = 1
         --> c = 4 (not 9) and last digit of x must be 6
             d = 2 (as 2.n must be 2.....)
             
      n = 1....6842      
      
        n = xbcd = 1000.x + bcd  (x has k digits)
      8.n = bcdx = 10^k . bcd + x
      
      8000.x + 8.bcd = 10^k . bcd + x
      7999.x = 19.421.x = (10^k - 8).842
      19.x = 2.(10^k - 8)
      '''
            
      for k in range(2, 18):  
        x, r = divmod(2 * (10**k - 8), 19)
        if r: continue
        n = str(x) + "842"
        # no digit occurs more than twice
        if all(n.count(str(i)) <= 2 for i in range(10)): 
          print("answer method 1:", n)
        
      # in order for 2 * (10**k - 8) % 19 = 0 we must have
      # 2 * 10**k % 19 = 16 
      # 2 * 10**k = 19.10**(k - 1) + 10**(k - 1)
      # so 10**(k - 1) % 19 = 16
      
      # 10**(k - 1) % 19 follows a logical pattern
      k = 2
      a = 10**(k - 1) % 19
      while a != 16: 
        a = (a + 19 * (a % 2)) // 2
        k += 1
      
      n = str(2 * (10**k - 8) // 19) + "842"
      # no digit occurs more than twice
      if all(n.count(str(i)) <= 2 for i in range(10)): 
        print("answer method 2:", n)
      

      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