Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 7:14 am on 24 April 2026 Permalink | Reply
    Tags: ,   

    Teaser 2444: [Pat’s lawn] 

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

    Pat’s lawn is a 600 sq metre rectangle with a tree at each corner. Working around the perimeter, these are apple, beech, cherry and damson. Pat has marked four straight lines on the grass: one from the apple to a point 75% of the way from the beech to the cherry; the second from the beech to 75% of the way from the cherry to the damson; the third from the cherry to 75% of the way from the damson to the apple; the fourth from the damson to 75% of the way from the apple to the beech. He plans to turn the area enclosed by the four lines into a rose garden.

    What will the rose garden’s area be?

    This puzzle was originally published with no title.

    [teaser2444]

     
    • Jim Randell's avatar

      Jim Randell 7:15 am on 24 April 2026 Permalink | Reply

      This is similar to Routh’s Theorem but in a rectangle instead of a triangle. (See: Teaser 3021).

      If the lines are drawn to a fraction f along the appropriate side, then the area of the central quadrilateral is (as a fraction of the overall parallelogram):

      A = (1 − f)² / (1 + f²)

      Specifically if f is represented as a fraction a/b we get:

      A = (b − a)² / (a² + b²)

      And in our case a = 3, b = 4, hence:

      A = (4 − 3)² / (3² + 4²)
      A = 1/25

      from enigma import (Rational, sq, printf)
      
      Q = Rational()
      
      # fraction along the sides
      f = Q(75, 100)
      printf("[f = {f}]")
      
      # calculate the area of the central quadrilateral
      A = Q(sq(1 - f), 1 + sq(f))
      printf("[A = {A}]")
      
      # output solution (area of central region)
      R = A * 600
      printf("area = {R} sq m")
      

      Solution: The area of the rose garden is 24 sq m.

      Like

  • Unknown's avatar

    Jim Randell 8:13 am on 22 April 2026 Permalink | Reply
    Tags: by: Lachlan MacKinnon,   

    Teaser 2447: [Number grid] 

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

    In this variation on a sudoku theme, your task is to find a particular 5-by-5 array of digits in which each row and each column use the same five consecutive digits. When completed, you can read five 5-digit numbers across the rows and a further five down the columns. These 10 numbers should all be different, and their product should be divisible by the fourth powers of 11, 10 and 9. The product should also be divisible by the fourth (but not the fifth) power of 8.

    What are the lowest and highest of your 10 numbers?

    This puzzle was originally published with no title.

    [teaser2447]

     
    • Jim Randell's avatar

      Jim Randell 8:14 am on 22 April 2026 Permalink | Reply

      The overall product is divisible by 9^4 (= 3^8), so at least one of the numbers is divisible by 3, and as the numbers are rearrangements of each other, they must all be divisible by 3. (And so the final product will have a divisor of at least 3^10).

      We can fill out a grid using the digits 0-4 (allowing leading zeros), and than add the same value (between 0 and 5) to each digit to form a candidate grid.

      The sum of the digits 0-4 is 10, and if this combines with 5 copies of the additional digit to give a multiple of 3 then the additional digit can only be 1 or 4.

      This is the approach taken with the following run file, using the [[ SubstitutedExpression ]] solver from the enigma.py library.

      It executes in 661ms. (Internal runtime of the generated code is 448ms (using PyPy 7.3.21)).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      #  A B C D E
      #  F G H I J
      #  K L M N O
      #  P Q R S T
      #  U V W X Y
      #
      # and each number is increased by: ZZZZZ
      
      --macro="@rows = ABCDE, FGHIJ, KLMNO, PQRST, UVWXY"
      --macro="@cols = AFKPU, BGLQV, CHMRW, DINSX, EJOTY"
      --macro="@nums = [@rows, @cols]"
      
      # rows and cols are rearrangements of the same 5 digits
      --distinct="ABCDE,FGHIJ,KLMNO,PQRST,UVWXY,AFKPU,BGLQV,CHMRW,DINSX,EJOTY"
      
      # numbers are formed from 5 consecutive digits
      # so the numbers in the grid are formed from 0-4
      # and the base (Z) is 1 or 4
      --digits="0-4"
      --invalid="0|2|3,Z"
      
      # the numbers are all different
      "seq_all_different(@nums)"
      
      # check divisibility
      --code="""
      D = mlcm(8**4, 9**4, 10**4, 11**4)  # product is divisible by D
      X = 8**5  # but not X
      def check(ns, b=0):
        p = multiply(n + b for n in ns)
        return (p % D == 0) and (p % X != 0)
      """
      "check(@nums, ZZZZZ)"
      
      # remove duplication
      "ABCDE < AFKPU"
      
      --template=""
      --denest=-30  # CPython workaround
      

      Solution: The lowest of the numbers is: 45768. And the highest is: 86547.

      The prime factors are:

      74856 = (2^3)(3)(3119)
      68475 = (3)(5^2)(11)(83)
      86547 = (3)(17)(1697)
      57684 = (2^2)(3)(11)(19)(23)
      45768 = (2^3)(3)(1907)

      76854 = (2)(3)(12809)
      48675 = (3)(5^2)(11)(59)
      84567 = (3)(7)(4027)
      57486 = (2)(3)(11)(13)(67)
      65748 = (2^2)(3)(5479)

      These combine to give an overall product with the following repeated prime factors:

      (2^12)(3^10)(5^4)(11^4)


      In the published solution the lowest number was given as 45678, but this is likely to have been a typo.

      Like

  • Unknown's avatar

    Jim Randell 6:32 am on 19 April 2026 Permalink | Reply
    Tags:   

    Teaser 3317: Hitting all the spots 

    From The Sunday Times, 19th April 2026 [link] [link]

    A board game uses three coloured tetrahedral dice (red, green and blue) to determine which numbers are hit when the three dice are rolled. The 12 faces of the three dice each have different numbers, comprising a 1 and the primes from 2 to 31. A hit is made when it is the number rolled on any of the dice, the sum of any two numbers rolled or the sum of all three numbers rolled.

    The arrangement of the numbers on each die made it possible to hit every number in a range and this range was as large as it could be. The green die has numbers 2, 5, 7, 11 and the blue die also has three consecutive primes.

    In ascending order, what are the numbers on the red die?

    [teaser3317]

     
    • Jim Randell's avatar

      Jim Randell 6:32 am on 19 April 2026 Permalink | Reply

      The most interesting part was writing the [[ maxcss() ]] function to find the maximum continuous subsequence of consecutive integers contained in the parent sequence.

      This Python program runs in 70ms. (Internal runtime is 3.3ms).

      from enigma import (
        Accumulator, primes, irange, tuples, trim,
        diff, cproduct, union, subsets, printf
      )
      
      # calculate the hits for a set of dice
      def hits(ds):
        # sum of any (non-empty) subset of a throw
        hs = union(subsets(vs, min_size=1, fn=sum) for vs in cproduct(ds))
        # return as a sorted list
        return sorted(hs)
      
      # generate possible sets of dice
      def generate():
        # numbers used on the dice
        ns = list(primes.between(2, 31))
        ns.insert(0, 1)
      
        # green die
        G = {2, 5, 7, 11}
      
        # choose 3 consecutive primes for the blue die
        for b3 in tuples(trim(ns, head=6), 3, fn=set):
          rs = diff(ns, G, b3)
          # and one of the remaining numbers
          for bx in subsets(rs, size=1, fn=set):
            B = b3.union(bx)
            # red die is what's left
            R = diff(rs, bx, fn=set)
            yield (G, B, R)
      
      # find the largest continuous subsequence (length >= m)
      # in the list of integers <vs>, the list should be strictly increasing
      # return (<max-length>, (<lowest-value>, <highest-value>))
      def maxcss(vs, m=0):
        r = Accumulator(fn=max, value=m)
        # consider possible starts
        n = len(vs)
        for (i, v) in enumerate(vs):
          if i + r.value > n: break
          for j in irange(n - 1, i + r.value, step=-1):
            if vs[j] - v == j - i:
              r.accumulate_data(j - i + 1, i)
              break
        if r.data is None: return
        (k, i) = (r.value, r.data)
        return (k, (vs[i], vs[i + k - 1]))
      
      # find maximal length range of hits
      r = Accumulator(fn=max, value=1, collect=1)
      for ds in generate():
        hs = hits(ds)
        z = maxcss(hs, r.value)
        if z is None: continue
        (n, (a, b)) = z
        r.accumulate_data(n, ds)
      
      # output solution
      printf("max range = {r.value}")
      for (G, B, R) in r.data:
        printf("-> G={G} B={B} R={R}", G=sorted(G), B=sorted(B), R=sorted(R))
      

      Solution: [To Be Revealed]

      Like

      • Jim Randell's avatar

        Jim Randell 4:51 pm on 19 April 2026 Permalink | Reply

        Here is a simpler implementation of [[ maxcss() ]], that just returns the length of a maximal subsequence:

        def maxcss(vs):
          if not vs: return 0
          return max(map(len, clump(v - i for (i, v) in enumerate(vs))))
        

        We calculate a derived sequence by subtracting the index of each element of the original sequence from the value of that element. We then look for a maximal length run of equal values in the derived sequence, and this corresponds to a maximal length continuous subsequence of consecutive integers in the original sequence.

        Like

      • Ruud's avatar

        Ruud 5:10 am on 20 April 2026 Permalink | Reply

        @Frits
        Now, I see what you mean. i had interpreted the problem wrongly.
        Here’s a revised version:

        import itertools
        
        max_max_length = 0
        faces = [n for n in range(1, 32) if all(n % i for i in range(2, int(n**0.5) + 1))]  # 1, 2, 3, ...,31
        green = {2, 5, 7, 11}
        for primes3 in zip(faces[0:], faces[1:], faces[2:]):
            if not any(x in green for x in primes3):
                for prime4 in set(faces) - green - {primes3}:
                    blue = {*primes3, prime4}
                    red = set(faces) - green - blue
                    hits = sorted({sum(x) for a, b, c in itertools.product(green, blue, red) for x in itertools.combinations((0, 0, a, b, c), 3)})
                    max_length = 0
                    length = 1
                    for d0, d1 in zip(hits, hits[1:] + [0]):
                        if d1 - d0 == 1:
                            length += 1
                        else:
                            max_length = max(max_length, length)
                            length = 1
                    if max_length >= max_max_length:
                        if max_length > max_max_length:
                            solutions = []
                            max_max_length = max_length
                        solutions.append((green, blue, red))
        print("maximum range = ", max_max_length)
        for solution in solutions:
            for i, color in enumerate("green blue red".split()):
                print(color, sorted(solution[i]))
        

        Like

    • Ruud's avatar

      Ruud 10:14 am on 19 April 2026 Permalink | Reply

      import itertools
      
      max_max_hit = 0
      faces = [n for n in range(1, 32) if all(n % i for i in range(2, int(n**0.5) + 1))]  # 1, 2, 3, ...,31
      green = {2, 5, 7, 11}
      for primes3 in zip(faces[0:], faces[1:], faces[2:]):
          if not any(x in green for x in primes3):
              for prime4 in set(faces) - green - {primes3}:
                  blue = {*primes3, prime4}
                  red = set(faces) - green - blue
                  hits = {sum(x) for a, b, c in itertools.product(green, blue, red) for x in itertools.combinations((0, 0, a, b, c), 3)}
                  for max_hit in itertools.count():
                      if max_hit + 1 not in hits:
                          break
      
                  if max_hit >= max_max_hit:
                      if max_hit > max_max_hit:
                          solutions = []
                          max_max_hit = max_hit
                      solutions.append((green, blue, red))
      print("maximum range = ", max_max_hit)
      for solution in solutions:
          for i, color in enumerate("green blue red".split()):
              print(color, sorted(solution[i]))
      

      Like

      • Frits's avatar

        Frits 11:26 am on 19 April 2026 Permalink | Reply

        @Ruud, why do you assume the range has to start with 1?

        Like

        • Ruud's avatar

          Ruud 3:18 pm on 19 April 2026 Permalink | Reply

          Because 1 is also one of the numbers on the dice. So faces is really 1, 2, 3, …, 31 .

          Like

          • Frits's avatar

            Frits 9:32 pm on 19 April 2026 Permalink | Reply

            @Ruud,

            The collection of hits might be (if 1 and 3 belong to the same colour):

              
            [1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 55, 57, 59, 61]
            

            In this case the biggest range isn’t 3 but 37 (17-53) and number 1 isn’t included in this range.

            Like

    • Ellen Napier's avatar

      Ellen Napier 1:21 pm on 20 April 2026 Permalink | Reply

      I found two different answers to this puzzle.

      Like

      • Jim Randell's avatar

        Jim Randell 6:53 am on 21 April 2026 Permalink | Reply

        @Ellen: I found there were 20 possible sets of dice. And one of them gave the single longest continuous subsequence of consecutive hits.

        However there are two sets which give the second longest continuous subsequence of consecutive hits.

        Like

    • Ellen Napier's avatar

      Ellen Napier 11:13 pm on 21 April 2026 Permalink | Reply

      Got it, thanks. Had a sorting error.

      Like

  • Unknown's avatar

    Jim Randell 7:56 am on 17 April 2026 Permalink | Reply
    Tags:   

    Teaser 2448: [Contractors] 

    From The Sunday Times, 23rd August 2009 [link]

    Zachary, a contractor, wants to place a stone wall round a new development. He knows he can hire two masons for as long as it takes: Angus, who could do the job on his own in 21 days, and Bruce who could complete it on his own in 28 days. Together, they could finish the job in a certain number of days, but Zachary would like it done in three fewer days. So, as well as hiring Angus and Bruce for the whole time, he hires Chuck, who could do it on his own in 24 days, for the few days necessary to achieve his aim.

    For how many days does he employ Chuck?

    This puzzle was originally published with no title.

    [teaser2448]

     
    • Jim Randell's avatar

      Jim Randell 7:57 am on 17 April 2026 Permalink | Reply

      If we say there is 1 unit of work to do, then:

      A does 1/21 units of work per day
      B does 1/28 units of work per day
      C does 1/24 units of work per day

      We can calculate how long it would take A + B to do the job by combining their work rates:

      1/21 + 1/28 = 1/12

      So together A + B could complete the job in 12 days.

      But Z wants the work to be completed in 9 days.

      In 9 days A + B can do 9/12 (= 3/4) of the job, leaving C to do the remaining 1/4.

      And: 1/4 = 6/24, so C would have to work for 6 days.

      Solution: Chuck works for 6 days.


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

      from enigma import (Rational, as_int, printf)
      
      Q = Rational()
      
      # work rates for A, B, C
      (A, B, C) = (Q(1, x) for x in [21, 28, 24])
      
      # calculate number of days for A and B together
      d = as_int(Q(1, A + B))
      printf("A + B = {d} days")
      
      # target number of days
      t = d - 3
      printf("target = {t} days")
      
      # calculate number of extra days = x
      # (A + B) * 9 + C * x = 1
      # => x = (1 - 9(A + B)) / C
      x = Q(1 - 9 * (A + B), C)
      printf("C works for {x} days")
      

      Like

  • Unknown's avatar

    Jim Randell 1:21 pm on 15 April 2026 Permalink | Reply
    Tags:   

    Teaser 2440: [Safe combination] 

    From The Sunday Times, 28th June 2009 [link]

    George and Martha’s safe was broken into. The combination had six digits: they remembered it as two three-figure numbers, the second being the first multiplied by their single-digit house number. All seven digits were different and non-zero. The burglar knew these facts and worked out the code. Their new combination has eight digits, remembered as four-figure numbers, the second being the first multiplied by their lucky digit. All nine digits are different and non-zero. The last three digits are the same as in the original.

    What is their new combination?

    This puzzle was originally published with no title.

    [teaser2440]

     
    • Jim Randell's avatar

      Jim Randell 1:23 pm on 15 April 2026 Permalink | Reply

      We don’t know the house number, but the burglar does, and we can look for situations where knowing the house number allows you to work out a unique combination.

      The following Python program uses the [[ SubstitutedExpression ]] solver from the enigma.py library to generate candidate combinations are house numbers, and then uses the [[ filter_unique() ]] function to identify viable situations.

      We can then look for possible second combinations where the final 3 digits are the same as those in the first combination.

      It runs in 74ms. (Internal runtime is 7.2ms).

      from enigma import (SubstitutedExpression, irange, filter_unique, item, nsplit, printf)
      
      # suppose the first combination is: ABC DEF, and the house number G
      p1 = SubstitutedExpression(
        [ "{ABC} * {G} = {DEF}" ],
        digits=irange(1, 9),
        answer="({ABC}, {DEF}, {G})",
      )
      
      # knowing the house number, the burglar could work out the code
      rs = filter_unique(p1.answers(verbose=0), f=item(2))
      for (ABC, DEF, G) in rs.unique:
      
        # find the second combination: HIJK LMNP, and the lucky number Q
        # and the last 3 digits are the same as in the first combination
        p2 = SubstitutedExpression(
          [ "{HIJK} * {Q} = {LMNP}" ],
          digits=irange(1, 9),
          s2d=dict(zip("MNP", nsplit(DEF))),
          answer="({HIJK}, {LMNP}, {Q})",
        )
      
        for (HIJK, LMNP, Q) in p2.answers(verbose=0):
          # output solution
          printf("code 1 = {ABC} {DEF} (house = {G}); code 2 = {HIJK} {LMNP} (lucky = {Q})")
      

      Solution: The new combination is: 1738 6952.

      And their lucky number is 4.

      1738 × 4 = 6952

      The original code was: 136 952. And the house number is 7.

      136 × 7 = 952

      There is another candidate for the old combination that can be determined knowing the house number. This is: 157 942 (house number = 6), but this does not permit a second combination to be made sharing the final 3 digits.

      Like

    • Ruud's avatar

      Ruud 2:30 pm on 15 April 2026 Permalink | Reply

      import peek
      import istr
      
      collect = {i: [] for i in istr.range(1, 10)}
      for digit, f0, f1, f2 in istr.permutations(range(1, 10), 4):
          first = f0 | f1 | f2
          second = first * digit
          if len(second) == 3 and (first | second | digit).all_distinct():
              collect[digit].append(second)
      for digit, second3s in collect.items():
          if len(second3s) == 1:
              house_number = digit
              second3 = second3s[0]
      
      for lucky_digit, f0, f1, f2, f3 in istr.permutations(range(1, 10), 5):
          first = f0 | f1 | f2 | f3
          second = first * lucky_digit
          if len(second) == 4 and (first | second | lucky_digit).all_distinct():
              if second[1:] == second3:
                  peek(first, second, lucky_digit, house_number)
      

      Like

      • Jim Randell's avatar

        Jim Randell 3:41 pm on 15 April 2026 Permalink | Reply

        @Ruud: You need to ensure: “All seven digits were different and non-zero”.

        Like

        • Ruud's avatar

          Ruud 3:49 pm on 15 April 2026 Permalink | Reply

          @Jim
          Yes, I missed the non zero condition. This one does:

          import peek
          import istr
          
          collect = {i: [] for i in istr.range(1, 10)}
          for digit, f0, f1, f2 in istr.permutations(range(1, 10), 4):
              first = f0 | f1 | f2
              second = first * digit
              if len(second) == 3 and not '0' in second and (first | second | digit).all_distinct():
                  collect[digit].append(second)
          for digit, second3s in collect.items():
              if len(second3s) == 1:
                  house_number = digit
                  second3 = second3s[0]
          
          for lucky_digit, f0, f1, f2, f3 in istr.permutations(range(1, 10), 5):
              first = f0 | f1 | f2 | f3
              second = first * lucky_digit
              if len(second) == 4 and not '0' in second and (first | second | lucky_digit).all_distinct():
                  if second[1:] == second3:
                      peek(first, second, lucky_digit, house_number)
          

          Like

        • Jim Randell's avatar

          Jim Randell 4:53 pm on 15 April 2026 Permalink | Reply

          And I think the final loop (lines 15-20) should fall under the if statement at line 11, otherwise you are only checking the final candidate second3 to be found (or the program will abort if none are found).

          Like

          • Ruud's avatar

            Ruud 4:40 am on 16 April 2026 Permalink | Reply

            Ok.

            import peek
            import istr
            
            collect = {i: [] for i in istr.range(1, 10)}
            for digit, f0, f1, f2 in istr.permutations(range(1, 10), 4):
                first = f0 | f1 | f2
                second = first * digit
                if len(second) == 3 and not '0' in second and (first | second | digit).all_distinct():
                    collect[digit].append(second)
            
            for digit, second3s in collect.items():
                if len(second3s) == 1:
                    house_number = digit
                    second3 = second3s[0]
            
                    for lucky_digit, f0, f1, f2, f3 in istr.permutations(range(1, 10), 5):
                        first = f0 | f1 | f2 | f3
                        second = first * lucky_digit
                        if len(second) == 4 and not '0' in second and (first | second | lucky_digit).all_distinct():
                            if second[1:] == second3:
                                peek(first, second, lucky_digit, house_number)
            

            Like

  • Unknown's avatar

    Jim Randell 6:42 am on 12 April 2026 Permalink | Reply
    Tags:   

    Teaser 3316: The odd one out 

    From The Sunday Times, 12th April 2026 [link] [link]

    George drew a 3 × 3 grid and wrote an odd digit in each square. Martha did the same and her grid was identical to George’s except for the digit in one square. Both grids contained eight different three-digit prime numbers, reading across the three rows, down the three columns and down the two diagonals. Remarkably, all eight of George’s primes were also primes when read backwards but only seven of Martha’s primes had this property.

    Which prime number in Martha’s grid was the odd one out?

    [teaser3316]

     
    • Jim Randell's avatar

      Jim Randell 7:33 am on 12 April 2026 Permalink | Reply

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

      It runs in 134ms. (Internal runtime of the generated code is 41ms).

      Run: [@codepad]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # use a prime sieve to test for 3-digit primes
      --code="is_prime = primes.is_prime"
      --code="primes.expand(999)"
      
      # grids are:
      #
      # George = A B C   Martha = J K L
      #          D E F            M N P
      #          G H I            Q R S
      #
      # and they differ in one position
      
      # cell values are not distinct
      --distinct=""
      # but are all odd
      --digits="1,3,5,7,9"
      
      # for George's grid:
      # the grid forms 8 different bi-directional primes:
      # rows
      "is_prime(ABC)" "is_prime(CBA)"
      "is_prime(DEF)" "is_prime(FED)"
      "is_prime(GHI)" "is_prime(IHG)"
      # cols
      "is_prime(ADG)" "is_prime(GDA)"
      "is_prime(BEH)" "is_prime(HEB)"
      "is_prime(CFI)" "is_prime(IFC)"
      # diagonals
      "is_prime(AEI)" "is_prime(IEA)"
      "is_prime(CEG)" "is_prime(GEC)"
      # different
      "all_different(ABC, DEF, GHI, ADG, BEH, CFI, AEI, CEG)"
      
      # for Martha's grid:
      # it differs from George's in exactly 1 position
      "sum(x != y for (x, y) in zip([A, B, C, D, E, F, G, H, I], [J, K, L, M, N, P, Q, R, S])) == 1"
      
      # the grid forms 8 different primes:
      # rows
      "is_prime(JKL)"
      "is_prime(MNP)"
      "is_prime(QRS)"
      # cols
      "is_prime(JMQ)"
      "is_prime(KNR)"
      "is_prime(LPS)"
      # diagonals
      "is_prime(JNS)"
      "is_prime(LNQ)"
      # different
      "all_different(JKL, MNP, QRS, JMQ, KNR, LPS, JNS, LNQ)"
      
      # exactly 7 of them are primes when reversed
      --macro="@Mrevs = (LKJ, PNM, SRQ, QMJ, RNK, SPL, SNJ, QNL)"
      "sum(is_prime(x) for x in @Mrevs) == 7"
      
      # answer is the one that is not a prime when reversed
      --code="ans = lambda ns: singleton(nrev(n) for n in ns if not is_prime(n))"
      --answer="ans(@Mrevs)"
      
      --template=""
      

      Solution: The prime number in Martha’s grid that is not a prime when reversed is 173.

      As: 173 is prime, but 371 = 7 × 53.

      Here are the grids:

      George:    Martha:
      [ 1 7 9 ]  [ 1 7 9 ]
      [ 1 5 7 ]  [ 7 5 7 ]
      [ 3 1 1 ]  [ 3 1 1 ]

      The numbers in the grids are:

      George: 179, 157, 311, 113, 751, 971, 151, 953 (all bi-directional primes)
      Martha: 179, 757, 311, 173, 751, 971, 151, 953 (all bi-directional primes except 173)

      We can get a second set of grids by writing the rows of these grids as columns and vice versa.

      Like

      • Jim Randell's avatar

        Jim Randell 2:51 pm on 12 April 2026 Permalink | Reply

        Splitting out the condition that George and Martha’s grids differ in exactly one position, into multiple expressions, allows us to get a run-file with an internal run time of just 1.8ms.

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        # use a prime sieve to test for 3-digit primes
        --code="is_prime = primes.is_prime"
        --code="primes.expand(999)"
        
        # grids are:
        #
        # George = A B C   Martha = J K L
        #          D E F            M N P
        #          G H I            Q R S
        #
        # and they differ in position X (0 - 8)
        
        # cell values are not distinct
        --distinct=""
        # but are all odd
        --invalid="0|2|4|6|8,ABCDEFGHIJKLMNPQRS"
        # changed cell position is 0-8
        --invalid="9|X"
        
        # for George's grid:
        # the grid forms 8 different bi-directional primes:
        # rows
        "is_prime(ABC)" "is_prime(CBA)"
        "is_prime(DEF)" "is_prime(FED)"
        "is_prime(GHI)" "is_prime(IHG)"
        # cols
        "is_prime(ADG)" "is_prime(GDA)"
        "is_prime(BEH)" "is_prime(HEB)"
        "is_prime(CFI)" "is_prime(IFC)"
        # diagonals
        "is_prime(AEI)" "is_prime(IEA)"
        "is_prime(CEG)" "is_prime(GEC)"
        # different
        "all_different(ABC, DEF, GHI, ADG, BEH, CFI, AEI, CEG)"
        
        # for Martha's grid:
        # it differs from George's in exactly 1 position (position = X)
        "(X == 0) == (A != J)"
        "(X == 1) == (B != K)"
        "(X == 2) == (C != L)"
        "(X == 3) == (D != M)"
        "(X == 4) == (E != N)"
        "(X == 5) == (F != P)"
        "(X == 6) == (G != Q)"
        "(X == 7) == (H != R)"
        "(X == 8) == (I != S)"
        
        # the grid forms 8 different primes:
        # rows
        "is_prime(JKL)"
        "is_prime(MNP)"
        "is_prime(QRS)"
        # cols
        "is_prime(JMQ)"
        "is_prime(KNR)"
        "is_prime(LPS)"
        # diagonals
        "is_prime(JNS)"
        "is_prime(LNQ)"
        # different
        "all_different(JKL, MNP, QRS, JMQ, KNR, LPS, JNS, LNQ)"
        
        # exactly 7 of them are primes when reversed
        --macro="@Mrevs = (LKJ, PNM, SRQ, QMJ, RNK, SPL, SNJ, QNL)"
        "sum(is_prime(x) for x in @Mrevs) == 7"
        
        # answer is the one that is not a prime when reversed
        --code="ans = lambda ns: singleton(nrev(n) for n in ns if not is_prime(n))"
        --answer="ans(@Mrevs)"
        
        --template=""
        --denest=-1  # CPython workaround
        

        The run time can be further reduced (to 1.3ms) with the observation that 5 cannot occur at the end of a prime, or at the beginning of a bi-directional prime.

        --invalid="5,ABCDFGHILPQRS"
        

        Liked by 1 person

    • Ruud's avatar

      Ruud 8:31 am on 12 April 2026 Permalink | Reply

      import istr
      
      primes13579 = {i for i in istr.primes(100, 1000) if all(c in [1, 3, 5, 7, 9] for c in i)}
      reversible_primes13579 = {i for i in primes13579 if i.reversed().is_prime()}
      
      
      def grid(number_reversible):
          for rows in istr.permutations(primes13579, 3):
              all8 = list(rows)
              for i in range(3):
                  all8.append(rows[0][i] | rows[1][i] | rows[2][i])
              all8.append(rows[0][0] | rows[1][1] | rows[2][2])
              all8.append(rows[0][2] | rows[1][1] | rows[2][0])
              if len(set(all8)) == 8:  # all different?
                  if all(n in primes13579 for n in all8) and sum(n in reversible_primes13579 for n in all8) == number_reversible:
                      yield all8
      
      
      georges = list(grid(number_reversible=8))
      
      for martha in grid(number_reversible=7):
          for george in georges:
              diff = sum(g != m for g, m in zip(george[0] | george[1] | george[2], martha[0] | martha[1] | martha[2]))
              if diff == 1:
                  for p in martha:
                      if p not in reversible_primes13579:
                          print(p)
                          for g, m in zip(george[:3], martha[:3]):
                              print("    ", g, m)
                          print()
      

      Liked by 1 person

    • GeoffR's avatar

      GeoffR 5:20 pm on 12 April 2026 Permalink | Reply

      
      # ST 3316 by Claude AI
      
      from itertools import product
      
      sieve = bytearray([1]) * 1000
      
      sieve[0] = sieve[1] = 0
      for i in range(2, 32):
        if sieve[i]: sieve[i*i::i] = bytes(len(sieve[i*i::i]))
      
      ODD = (1,3,5,7,9)
      P = {t: sieve[100*t[0]+10*t[1]+t[2]] for t in product(ODD, repeat=3)}
      R = {t: sieve[100*t[2]+10*t[1]+t[0]] for t in product(ODD, repeat=3)}
      E = {t: P[t] and R[t] for t in product(ODD, repeat=3)}
      
      def search(check):
        found = []
        for a,b,c in product(ODD, repeat=3):
          if not check[(a,b,c)]: continue
          for d,e,f in product(ODD, repeat=3):
            if not check[(d,e,f)]: continue
            for g,h,i in product(ODD, repeat=3):
              if (check[(g,h,i)] and check[(a,d,g)] and check[(b,e,h)]
                  and check[(c,f,i)] and check[(a,e,i)] and check[(c,e,g)]):
                nums = {100*a+10*b+c, 100*d+10*e+f, 100*g+10*h+i,
                        100*a+10*d+g, 100*b+10*e+h, 100*c+10*f+i,
                        100*a+10*e+i, 100*c+10*e+g}
                if len(nums) == 8:
                  found.append((a,b,c,d,e,f,g,h,i))
        return found
      
      george = search(E)
      
      seen = set()
      for gg in george:
        for pos in range(9):
          for digit in ODD:
            if digit == gg[pos]: continue
            mg = list(gg); mg[pos] = digit
            a,b,c,d,e,f,g,h,i = mg
            tris = [(a,b,c),(d,e,f),(g,h,i),(a,d,g),(b,e,h),(c,f,i),(a,e,i),(c,e,g)]
            if not all(P[t] for t in tris): continue
            nums = [100*t[0]+10*t[1]+t[2] for t in tris]
            if len(set(nums)) != 8: continue
            flags = [R[t] for t in tris]
            if sum(flags) == 7:
              ans = nums[flags.index(False)]
              if ans not in seen:
                seen.add(ans)
                print(f"Grid: {mg[:3]} / {mg[3:6]} / {mg[6:]}")
                print(f"  Odd prime out: {ans}  (reversal {int(str(ans)[::-1])} is not prime)")
      
      print(f"\nAnswer: {sorted(seen)}")
      
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 6:27 pm on 12 April 2026 Permalink | Reply

        Claude doesn’t seem to believe that comments are useful in code.

        Like

      • Frits's avatar

        Frits 9:08 am on 19 April 2026 Permalink | Reply

        Also a minor issue is that “mg = list(gg)” could/should have been done at a higher level.

        Like

    • GeoffR's avatar

      GeoffR 6:34 pm on 12 April 2026 Permalink | Reply

      It depends on the prompt – I asked Claude AI to make the code short and fast, and it chose to leave the comments out.

      Like

      • Brian Gladman's avatar

        Brian Gladman 3:23 pm on 13 April 2026 Permalink | Reply

        Perhaps you shouldn’t encourage its bad habits!

        Like

    • Frits's avatar

      Frits 7:01 pm on 12 April 2026 Permalink | Reply

      Not considering reflections/rotations.

      from itertools import permutations
      
      # 3-digit primes with odd digits
      P = [3, 5, 7]
      P += [x for x in range(11, 33, 2) if all(x % p for p in P)]
      P = {s for x in range(101, 1000, 2) if all(x % p for p in P) and
           set("13579").issuperset(s := str(x))}
      
      # reversable primes for George
      G = {p for p in P if p[::-1] in P}
        
      # return sequence of 3 rows, 3 columns and 2 diagonals
      def nums8(rs):
        seq = set(''.join(r) for r in rs)
        # add columns
        seq.update(list(''.join(c) for c in zip(*[list(r) for r in rs])))
        # add diagonals
        seq.update((rs[0][0] + rs[1][1] + rs[2][2], rs[0][2] + rs[1][1] + rs[2][0]))
        return seq  
      
      sols = set()
      # select reversable primes for George
      for rs in permutations(G, 3):
        rowsG = [list(r) for r in rs]
        # check the two diagonals for George
        d1, d2 = rs[0][0] + rs[1][1] + rs[2][2], rs[0][2] + rs[1][1] + rs[2][0]
        if any(x not in G for x in (d1, d2)):  continue
        # check the columns for George
        colsG = list(''.join(c) for c in zip(*rowsG))
        if any(c not in G for c in colsG): continue
        # check for 8 different numbers for George
        if len(nums8(rowsG)) != 8: continue
        
        # change one digit in George's grid in cell [r][c]
        for r in range(3):
          for c in range(3):
            rowsM = list(row.copy() for row in rowsG)
            # choose new digit value
            for v in "13579":
              if v == rowsG[r][c]: continue
              rowsM[r][c] = v
              # check for 8 different numbers for Martha
              if len(numsM := nums8(rowsM)) != 8: continue
              # all Martha's 8 numbers must also be prime
              if any(n not in P for n in numsM): continue 
              # count number of reversed numbers not being prime
              np = [x for x in numsM if x[::-1] not in P]
              if len(np) != 1: continue  # seven were prime
              
              sols.add(np[0])
      
      print(f"answer: {' or '.join(sols)}")
      

      Like

  • Unknown's avatar

    Jim Randell 7:56 am on 10 April 2026 Permalink | Reply
    Tags:   

    Teaser 2436: [It’s all Greek…] 

    From The Sunday Times, 31st May 2009 [link]

    In this puzzle I have consistently replaced digits by letters. In this way an addition sum has become:

    ETA + ZETA + THETA = DELTA.

    And the three unused digits form the number SUM.

    Even if I told you the value of Z you, could not work out the values of ETA, ZETA and THETA. But if I now told you whether SUM was prime or not, then you could work out the three summands.

    What are they?

    This puzzle was originally published with no title.

    [teaser2436]

     
    • Jim Randell's avatar

      Jim Randell 7:57 am on 10 April 2026 Permalink | Reply

      This Python program uses the [[ SubstitutedExpression() ]] solver from the enigma.py library to find possible solutions to the alphametic sum, and then uses the [[ filter_unique() ]] function to eliminate non-viable candidates.

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

      from enigma import (SubstitutedExpression, filter_unique, item, unpack, is_prime, printf)
      
      # the alphametic sum
      p = SubstitutedExpression.split_sum(
        ["ETA + ZETA + THETA = DELTA"],
        # record (0 = Z, 1 = SUM, 2 = summands)
        answer="(Z, SUM, (ETA, ZETA, THETA))",
      )
      
      # consider possible solutions, where the value of Z (= item(0))
      # does not uniquely determine the summands (= item(2))
      (f, g) = (item(0), item(2))
      rs = filter_unique(p.answers(verbose=0), f, g).non_unique
      
      # from the remaining candidates, knowing whether SUM (= item(1)) is prime
      # does allow us to determine the summands (= item(2))
      (f, g) = (unpack(lambda Z, SUM, ts: is_prime(SUM)), item(2))
      rs = filter_unique(rs, f, g).unique
      
      # output solution
      for (Z, SUM, terms) in rs:
        printf("Z={Z}, SUM={SUM} [prime={p}] -> (ETA, ZETA, THETA) = {terms}", p=is_prime(SUM))
      

      Solution: The summands are: ETA = 250; ZETA = 8250; THETA = 54250.


      There are 8 possible assignments of digits to letters that solve the alphametic sum:

      Z=2; A=0 D=6 E=1 H=9 L=4 T=5; {S, U, M} = {3, 7, 8}
      
      Z=3; A=0 D=6 E=1 H=8 L=4 T=5; {S, U, M} = {2, 7, 9}
      Z=3; A=0 D=6 E=2 H=9 L=7 T=5; {S, U, M} = {1, 4, 8}
      
      Z=4; A=0 D=6 E=2 H=8 L=7 T=5; {S, U, M} = {1, 3, 9}
      
      Z=8; A=0 D=6 E=1 H=3 L=4 T=5; {S, U, M} = {2, 7, 9}
      Z=8; A=0 D=6 E=2 H=4 L=7 T=5; {S, U, M} = {1, 3, 9}
      
      Z=9; A=0 D=6 E=1 H=2 L=4 T=5; {S, U, M} = {3, 7, 8}
      Z=9; A=0 D=6 E=2 H=3 L=7 T=5; {S, U, M} = {1, 4, 8}

      Z = 2 or 4 would uniquely identify the values in the sum, so these are discarded to leave the 6 ambiguous cases Z = 3, 8, 9.

      Of the possible values for SUM (6 arrangements for each case) only 139 and 193 are prime.

      So, if we are told SUM was not prime, we would not be able to identify the values of the summands.

      But if we are told SUM is prime, we know that the value must be 139 or 193 (so S = 1), and so the only possible candidate solution is:

      Z=8; A=0 D=6 E=2 H=4 L=7 T=5; S=1 {U, M} = {3, 9}

      Hence: Z = 8; ETA = 250; ZETA = 8250; THETA = 54250; DELTA = 62750; SUM = 139 or 193.

      Like

    • Ruud's avatar

      Ruud 2:01 pm on 10 April 2026 Permalink | Reply

      import istr
      
      
      z_collect = {z: [] for z in istr.range(10)}
      for e, t, a, z, h, d, l in istr.permutations(range(10), r=7):
          if (e | t | a) + (z | e | t | a) + (t | h | e | t | a) == d | e | l | t | a:
              z_collect[z].append(istr("=etazhdl"))
      
      for z, v in z_collect.items():
          if len(v) > 1:  # we need at least two so;ution with z=, this can't be the solution
              for s in v:
                  outcomes = {sum_.is_prime() for sum_ in map(istr.join, istr.permutations(set(istr.range(10)) - set(s)))}
                  if len(outcomes) == 2:  # False and True
                      s.decompose("etazhdl")
                      print(f"eta={istr('=eta')} zeta={istr('=zeta')} theta={istr('=theta')} delta={istr('=delta')}")
      

      Like

  • Unknown's avatar

    Jim Randell 10:41 am on 8 April 2026 Permalink | Reply
    Tags: ,   

    Teaser 2438: [Nested quadrilaterals] 

    From The Sunday Times, 14th June 2009 [link]

    Quadrilateral A has its vertices on a circle, and no two angles are the same. Another circle is drawn inside A that touches its sides at four points, and these points form the corners of Quadrilateral B. Then Quadrilateral C is formed inside B in the same way, and so on.

    Quadrilateral K’s angles are whole numbers of degrees.

    What (in increasing order) are Quadrilateral A’s angles?

    This puzzle was originally published with no title.

    [teaser2438]

     
    • Jim Randell's avatar

      Jim Randell 10:42 am on 8 April 2026 Permalink | Reply

      A quadrilateral that has a circumcircle (i.e. a circle that passes through each of the vertices) is called a cyclic quadrilateral.

      A quadrilateral is cyclic if (and only if) opposite internal angles are supplementary (i.e. if the angles at the vertices are (𝛂, 𝛃, 𝛄, 𝛅) we have: 𝛂 + 𝛄 = 𝛃 + 𝛅 = 180°).

      A quadrilateral that has a incircle (i.e. a circle inscribed inside the quadrilateral that touches all four sides) is called a tangential quadrilateral.

      A quadrilateral is tangential if (and only if) opposite sides sum to the same total length (i.e. if the length of the sides are (a, b, c, d) we have: a + c = b + d).

      Note that the condition for a tangential quadrilateral is more restrictive than the condition for a cyclic quadrilateral. For example, all rectangles (all internal angles = 90°) are cyclic, but only squares are tangential.

      A quadrilateral that is both cyclic and tangential is called bicentric.

      This puzzle concerns a series of nested bicentric quadrilaterals.


      Consider a bicentric quadrilateral ABCD. The tangential points on the incircle form the contact quadrilateral WXYZ. (Such that W is on AB, X on BC, Y on CD, Z on DA).

      Considering the internal angles of the ABCD quadrilateral (as a, b, c, d), the tangent lengths from a vertex to the vertices of the the contact quadrilateral are equal (see: Pitot Theorem), and so the angle subtended at the incentre of the contact quadrilateral (I) is supplementary to the internal angle of the original quadrilateral. (And so it is the same as the opposite internal angle of the original quadrilateral):

      ∠ZIW = 180° − a = c
      ∠WIX = 180° − b = d
      ∠XIY = 180° − c = a
      ∠YIZ = 180° − d = b

      (Note that knowing the angles subtended at the centre of the incircle means we can fully determine the shape of the contact quadrilateral, and we can then draw the tangents to the vertices of the contact quadrilateral to find the original quadrilateral (which has opposite angles supplementary, and so is necessarily cyclic). [*]

      And so, each internal angle of the contact quadrilateral is the mean of the internal angles at the vertices on the original quadrilateral at either end of the tangent side:

      w = (a + b) / 2
      x = (b + c) / 2
      y = (c + d) / 2
      z = (d + a) / 2

      So, if the original angles are (a, b, c, d), then the final angles (a′, b′, c′, d′) after 10 applications of this transformation are:

      a′ = (16a + 17b + 16c + 15d) / 64
      b′ = (15a + 16b + 17c + 16d) / 64
      c′ = (16a + 15b + 16c + 17d) / 64
      d′ = (17a + 16b + 15c + 16d) / 64

      And remembering a + c = 180°, and b + d = 180°, we get:

      a′ = 87 + (6 + b)/32
      b′ = 92 + (26 − a)/32
      c′ = 92 + (26 − b)/32
      d′ = 87 + (6 + a)/32

      And these are integers.

      So, if we assume a and b are angles where 0° < a < b < 90°, we get:

      a = 26°, b = 58°, c = 154°, d = 122°

      a′ = 89°, b′ = 92°, c′ = 91°, d′ = 88°

      Solution: The angles in the original quadrilateral were: 26°, 58°, 122°, 154°.

      Calculating successive internal angles we get:

      A: 26°, 58°, 154°, 122°.
      B: 42°, 106°, 138°, 74°.
      C: 74°, 122°, 106°, 58°.
      D: 98°, 114°, 82°, 66°.
      E: 106°, 98°, 74°, 82°.
      F: 102°, 86°, 78°, 94°.
      G: 94°, 82°, 86°, 98°.
      H: 88°, 84°, 92°, 96°.
      I: 86°, 88°, 94°, 92°.
      J: 87°, 91°, 93°, 89°.
      K: 89°, 92°, 91°, 88°.

      Note that the angles are approaching 90°, and a square forms a stable scenario, which can be extended in either direction indefinitely.

      The sequence would continue:

      L: 90.5°, 91.5°, 89.5°, 88.5°.
      M: 91°, 90.5°, 89°, 89.5°.
      N: 90.75°, 89.75°, 89.25°, 90.25°.
      O: 90.25°, 89.5°, 89.75°, 90.5°.
      P: 89.875°, 89.625°, 90.125°, 90.375°.
      Q: 89.75°, 89.875°, 90.25°, 90.125°.
      R: 89.8125°, 90.0625°, 90.1875°, 89.9375°.

      by which time the angles are all within 0.2° of 90°.


      However if we attempt to actually construct the series of nested quadrilaterals we run into a problem.

      From [*] we know that if the internal angles of quadrilateral A are: (26°, 58°, 154°, 122°), then the angles subtended at the centre of the contact quadrilateral (= quadrilateral B) are: (122°, 26°, 58°, 154°), and this is enough information to draw quadrilateral B inside a circle, and then construct quadrilateral A outside the circle.

      If we do so we get this:

      (quadrilateral A is red; quadrilateral B is black).

      Similarly, knowing the internal angles of quadrilateral B are: (42°, 106°, 138°, 74°), then the angles subtended at the centre of quadrilateral C are: (74°, 42°, 106°, 138°), and drawing quadrilaterals C and B we get this:

      (quadrilateral B is red; quadrilateral C is black).

      And, although the two quadrilaterals B in the diagrams look superficially similar (as they have the same angles), they are not mathematically similar, so the second cannot be uniformly scaled to fit onto the first. (The easy way to see this is that the distance between the 106° and 138° angles in the second diagram is longer than if the first diagram was scaled so as to fit the opposite side over the corresponding side in the first diagram. It is as if a strip parallel to that side has been removed (keeping the angles the same)).

      Here are the two quadrilaterals B superimposed:

      (Note the extra red line where one side of the red (smaller) quad B does not overlap the black quad B, and the inscribed circle does not touch all four sides of the black quad B).

      So, practically it is not possible to construct such a sequence of quadrilaterals.

      Hence the puzzle itself is flawed.

      Like

      • Jim Randell's avatar

        Jim Randell 2:44 pm on 8 April 2026 Permalink | Reply

        Although I have not found a correction printed in The Sunday Times, this puzzle did prompt an article by Victor Bryant (editor of Teaser puzzles at the time) and John Duncan, showing that if a nest of 3 bicentric quadrilaterals can be made, then they are necessarily all squares.

        “Wheels within wheels”, Victor Bryant, John Duncan
        The Mathematical Gazette, Volume 94, Issue 531, November 2010, pp. 502 – 505
        https://www.jstor.org/stable/25759740

        Like

  • Unknown's avatar

    Jim Randell 6:28 am on 5 April 2026 Permalink | Reply
    Tags: by: Andrew Gibbons   

    Teaser 3315: Primal scream 

    From The Sunday Times, 5th April 2026 [link] [link]

    A fox’s scream woke me in the night. I noted the time on my alarm clock display (hours and minutes) and realised that this number was the product of two prime numbers. (For example, at 01:43, the number 143 is 11×13).

    One minute later the numerical time was also the product of two prime numbers. Interestingly, one of the prime numbers at the later time was less than one of the numbers at the earlier time, but these two numbers had the same final two digits in the same order.

    At what time did the fox wake me?

    [teaser3315]

     
    • Jim Randell's avatar

      Jim Randell 6:38 am on 5 April 2026 Permalink | Reply

      It is not possible for the times involved to be during the switch to/from daylight savings time, as both 100 and 200 have more than 2 factors [*].

      From the format given in the example (01:43) I am assuming the display is a 24-hour clock.

      The following Python program considers displays on a 24-hour clock between 21:00 and 05:59 the following morning.

      It runs in 72ms. (Internal runtime is 961µs).

      from enigma import (irange, prime_factors, tuples, cproduct, printf)
      
      # generate times that are products of <k> primes (21:00 - 05:59)
      def generate(k):
        for h in irange(21, 24 + 5):
          for m in irange(0, 59):
            # 24 hour clock
            n = 100*(h % 24) + m
            fs = list(prime_factors(n))
            if len(fs) == k:
              # return (<minute-count>, <number>, <prime-factors>)
              yield (60*h + m, n, fs)
      
      # consider pairs of times ...
      for ((t1, n1, fs1), (t2, n2, fs2)) in tuples(generate(2), 2):
        # separated by 1 minute
        if not (t2 == t1 + 1): continue
        # and check the factors
        for (f1, f2) in cproduct([fs1, fs2]):
          if not (f1 > f2 > 9 and f1 % 100 == f2 % 100): continue
          # output solution
          printf("{n1} {fs1} -> {n2} {fs2}")
      

      Solution: The fox woke you at 03:34.

      We have:

      03:34 → 334 = 2 × 167
      03:35 → 335 = 5 × 67

      (Six, seven!)


      There is another time when the situation could happen:

      12:02 → 1202 = 2 × 601
      12:03 → 1203 = 3 × 401

      If we assume the clock is a 24-hour clock, then this does not occur during the night.

      But, for a 12-hour clock it would provide a viable additional answer to the puzzle.

      And another near miss is:

      14:17 → 1417 = 13 × 109
      14:18 → 1418 = 2 × 709

      But in this case the larger of the primes with the shared digits is associated with the later time.


      [*] In fact all times of the form xx:00 will correspond with numbers that do not have exactly 2 prime factors, so we can simplify the logic by only considering times that occur between xx:01 and xx:59.

      Like

      • Jim Randell's avatar

        Jim Randell 10:45 am on 6 April 2026 Permalink | Reply

        Here is an alternative approach, that is slightly faster. (But depends heavily on the times having exactly 2 prime factors).

        The times differ by 1 minute, so one must correspond to an even number and the other to an odd number. The even number must be a product of 2 along with a larger prime, that shares its final two digits with a prime divisor of the odd number. The times fall within the same hour, so the numbers must be consecutive, and so 2 must be paired with the larger prime to give the earlier time.

        This Python program collects primes by their residue mod 100, and then looks for pairs of primes that can correspond to consecutive times.

        It has an internal runtime of 331µs.

        from enigma import (Record, primes, group, mod, seq_items, div, sprintf, printf)
        
        # convert <n> to valid time: hour <h>, minute <m>
        def time(n):
          (h, m) = divmod(n, 100)
          if m > 59 or h > 24 or (5 < h < 21): return
          return Record(h=h, m=m)
        
        # format a time
        def fmt(t): return sprintf("{h:02d}:{m:02d}", h=t.h, m=t.m)
        
        # collect primes by residue mod 100 (max time is 2359)
        d = group(primes.between(10, 1179), by=mod(100))
        
        # consider possible residues
        for (k, vs) in d.items():
          # consider the larger of the primes
          for (i, p2) in seq_items(vs, 1):
            # 2 is paired with p2 to make the earlier time
            n1 = 2 * p2
            t1 = time(n1)
            if t1 is None: continue
            # the later time is 1 minute later
            n2 = n1 + 1
            # look for the smaller prime
            for (_, p1) in seq_items(vs, 0, i - 1):
              # is n2 a prime multiple of p1?
              p = div(n2, p1)
              if p not in primes: continue
              # output solution
              printf("time = {t1} [k={k}; {n1} = [{p}, {p1}], {n2} = [2, {p2}]]", t1=fmt(t1))
        

        (Note: Requires enigma.py version 2026-04-08).

        Like

    • Ruud's avatar

      Ruud 9:36 am on 5 April 2026 Permalink | Reply

      import istr
      from collections import namedtuple
      
      istr.int_format("04")
      candidates = {}
      for p0, p1 in istr.product(istr.primes(1, 1000), repeat=2):
          if (hour := (product := (p0 * p1))[:2]) <= 23 and (minute := product[2:]) <= 59 and p0 <= p1 and (hour < 8 or hour > 18):
              t = hour * 60 + minute
              candidates[t] = namedtuple("x", "primes time")((p0, p1), hour | ":" | minute)
      
      t0 = None
      for t1 in sorted(candidates):
          if t0 == t1 - 1 and any(p0x > p1x and p0x[-2:] == p1x[-2:] for p0x in candidates[t0].primes for p1x in candidates[t1].primes):
              print(candidates[t0].time, *map(int, candidates[t0].primes), candidates[t0].time, *map(int, candidates[t1].primes))
          t0 = t1
      

      Like

      • Frits's avatar

        Frits 2:40 pm on 5 April 2026 Permalink | Reply

        @Ruud,

        “primes(1, 1000)” seems to be too restrictive (eg ignoring time 23:06)

        Like

  • Unknown's avatar

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

    Teaser 2443: [Phone masts] 

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

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

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

    This puzzle was originally published with no title.

    [teaser2443]

     
    • Jim Randell's avatar

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

      In a hexagon the internal angles sum to 720°.

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

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

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

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

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

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

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

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

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

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

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

      Hence the angles are fully determined:

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

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

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

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

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

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

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 2442: [Gaming machine] 

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

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

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

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

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

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

    This puzzle was originally published with no title.

    [teaser2442]

     
    • Jim Randell's avatar

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

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

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

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

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

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

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

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

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

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

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

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

      Like

    • GeoffR's avatar

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

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

      Like

  • Unknown's avatar

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

    Teaser 3314: Number unobtainable! 

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

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

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

    Give Belle’s old phone number.

    [teaser3314]

     
    • Jim Randell's avatar

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

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

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

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

      Solution: Belle’s number was: 431311.

      And Dyall’s number was: 593113.

      The are 6 candidate numbers that give a descending sequence of primes when group by 2 digits or by 3 digits.

      Here is a list, along with the number of digits that uniquely identify the number:

      373113 → 1
      431311 → 3 (Belle)
      433113 → 0
      593113 → 2 (Dyall)
      613113 → 1
      673113 → 0

      So, if you were told the 1st digit of Dyall’s number is 5, or the 2nd digit is 9, you could determine the entire number with certainty (2/6 digits = 1/3 chance).

      And for Belle’s number if you were told the 3rd digit is 1, the 4th digit is 3, or the 6th digit is 1, you could determine the entire number with certainty (3/6 digits = 1/2 chance).

      Like

      • Jim Randell's avatar

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

        Or, without using [[ SubstitutedExpression ]].

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

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

        Liked by 1 person

    • GeoffR's avatar

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

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

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

      Like

    • Frits's avatar

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

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

      Like

  • Unknown's avatar

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

    Teaser 2449: [Numbers of marbles] 

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

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

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

    What is the house number?

    This puzzle was originally published with no title.

    [teaser2449]

     
    • Jim Randell's avatar

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

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

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

      Solution: The house number is 94.

      Which gives rise to the following sequence of numbers:

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

      Like

    • Ruud's avatar

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

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

      Like

      • Jim Randell's avatar

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

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

        Like

        • Ruud's avatar

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

          You are right.
          The code should read

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

          Like

  • Unknown's avatar

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

    Brain-Teaser 968: Friends and neighbours 

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

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

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

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

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

    What are they?

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

    [teaser968]

     
    • Jim Randell's avatar

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

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

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

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

      And here is a Python wrapper to prettify the output:

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

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

      The complete solution is:

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

      Like

  • Unknown's avatar

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

    Teaser 3313: Rod’s rods 

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

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

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

    What were those three numbers?

    [teaser3313]

     
    • Jim Randell's avatar

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

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

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

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

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

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

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

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

      Like

      • Jim Randell's avatar

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

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

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

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

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

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

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

        Like

    • Ruud's avatar

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

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

      Like

    • ruudvanderham's avatar

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

      As a one liner:

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

      Like

    • GeoffR's avatar

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

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

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

      Like

      • Frits's avatar

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

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

        Like

  • Unknown's avatar

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

    Brain teaser 985: Sanders explores the river 

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

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

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

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

    What are the four missing times?

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

    [teaser985]

     
    • Jim Randell's avatar

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

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

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

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

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

      s − r = 100 / 100 = 1

      s = r + 1

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

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

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

      t = 5/r

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

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

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

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

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

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

      Hence:

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

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

      Equating our equations for t:

      10r + 5 = 5/r

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

      r = 1/2

      Hence t = 10.

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

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

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

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

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

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

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

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

      The time taken is:

      42 / 2
      = 21 minutes

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

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

      Like

      • Jim Randell's avatar

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

        Here is a numerical solution using the same approach:

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

        Like

    • John Crabtree's avatar

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

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

      Like

  • Unknown's avatar

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

    Teaser 2451: [Just one out] 

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

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

    3 × 6 = 19

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

    4 × 7 = 28

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

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

    What is the display this time?

    This puzzle was originally published with no title.

    [teaser2451]

     
    • Jim Randell's avatar

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

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

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

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

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

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

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

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


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

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 3312: Never ends rumbas 

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

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

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

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

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

    [teaser3312]

     
    • Jim Randell's avatar

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

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

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

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

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

      This give rise to the following squares:

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

      Like

    • Ruud's avatar

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

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

      Like

  • Unknown's avatar

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

    Teaser 2452: [Cubic time] 

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

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

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

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

    What is that time and date?

    This puzzle was originally published with no title.

    [teaser2452]

     
    • Jim Randell's avatar

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

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

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

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

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

      And:

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

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

      Like

    • ruudvanderham's avatar

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

      This code checks only valid dates:

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

      Like

    • ruudvanderham's avatar

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 2450: [Triangle squares] 

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

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

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

    What are the two squares’ areas?

    This puzzle was originally published with no title.

    [teaser2450]

     
    • Jim Randell's avatar

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

      Working in units of inches:

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

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

      A = 18h

      h = A/18

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

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

      Subtracting:

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

      y² − x² = 72z

      But we are told:

      y² − x² = A

      z = A/72

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

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

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

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

      Like

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