Updates from June, 2025 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 7:27 am on 10 June 2025 Permalink | Reply
    Tags:   

    Teaser 2542: Two routes 

    From The Sunday Times, 12th June 2011 [link] [link]

    A line of equally spaced poles is marked in order with odd numbers, 1, 3, 5, etc. Directly opposite is a parallel line of an equal number of poles with even numbers, 2, 4, 6, etc. There are fewer than 100 poles. The distance in metres between adjacent poles is a two-digit prime; the distance between opposite poles is another two-digit prime.

    Jan walks the route 1-2-4-3-5, etc to reach the final pole. John walks the odds 1-3-5, etc to the last odd-numbered pole; then walks diagonally to pole 2; then walks the evens 2-4-6, etc, also finishing at the final pole. Jan’s and John’s routes are of equal length.

    How many poles are there?

    News

    This completes the archive of puzzles from the book The Sunday Times Brain Teasers 2 (2020).

    As far as I am aware, there are 927 Teaser puzzles that have been published across 9 books. So far I have posted 835 (= 90.1%) of these puzzles to the S2T2 site, and have complete coverage of 6 of the books. I will continue working on the 3 remaining books (published in 1974, 1981, 1994).

    [teaser2542]

     
    • Jim Randell's avatar

      Jim Randell 7:27 am on 10 June 2025 Permalink | Reply

      If the distance between adjacent odd (and even) poles is a, and the distance between the two rows is b, and there are k gaps between poles in a row (i.e each row has (k + 1) poles, so in total there are (2k + 2) poles), then:

      1 < k < 49

      Jan’s distance = ak + b(k + 1)

      John’s distance = 2ak + hypot(ak, b)

      And these two distances are equal when:

      b(k + 1) = ak + hypot(ak, b)

      It also seems the setter intends that Jan and John are to arrive at the same final pole, so we require k to be a multiple of 2. (Although this condition does not eliminate any candidate solutions).

      The following Python program runs in 71ms. (Internal runtime is 11.4ms).

      from enigma import (irange, primes, subsets, ihypot, printf)
      
      # consider (different) 2-digit primes a, b
      for (a, b) in subsets(primes.between(10, 99), size=2, select='P'):
        # consider number of gaps
        for k in irange(2, 48, step=2):
          ak = a * k
          h = ihypot(ak, b)
          if h is None or ak + h != b * (k + 1): continue
          # output solution
          printf("k={k} a={a} b={b} -> {n} poles", n=2 * k + 2)
      

      Solution: There are 74 poles.

      Adjacent poles are separated by 19 m, and the rows are separated by 37 m.

      Jan walks 19×36 + 37×37 = 2053 m.

      John walks 2×19×36 + hypot(19×36, 37) = 2053 m.


      With additional analysis we can show:

      b(k + 1) = ak + hypot(ak, b)

      b(k + 2) = 2a(k + 1)

      Where a and b are prime.

      Hence:

      b = k + 1
      2a = k + 2

      This allows us to consider fewer cases:

      from enigma import (primes, div, printf)
      
      for a in primes.between(10, 99):
        k = 2 * a - 2
        n = 2 * k + 2
        if not (n < 100): break
        b = k + 1
        if not (b in primes): continue
        # output solution
        printf("k={k} a={a} b={b} -> {n} poles")
      

      Like

    • Frits's avatar

      Frits 2:40 pm on 10 June 2025 Permalink | Reply

      See also [https://brg.me.uk/?page_id=3746#comment-578]

      Liked by 1 person

    • Ruud's avatar

      Ruud 6:01 pm on 10 June 2025 Permalink | Reply

      import itertools
      import sympy
      import math
      
      primes = [i for i in range(10, 100) if sympy.isprime(i)]
      for a, b in itertools.product(primes, primes):
          for n in range(4, 100):
              n1 = int((n - 1) / 2)
              n2 = int(n / 2)
              d_jan = n1 * a + n2 * b
              d_john = n1 * a + n1 * a + math.sqrt((n1 * a) ** 2 + b**2)
              if d_jan == d_john:
                  print(a, b, n)
      

      Like

  • Unknown's avatar

    Jim Randell 9:11 am on 5 June 2025 Permalink | Reply
    Tags: ,   

    Teaser 2536: ELEMENTARY 

    From The Sunday Times, 1st May 2011 [link] [link]

    “Every number has some significance”, said Holmes, referring to his monograph on the subject. “Then what do you make of this?”, asked Watson, scribbling a seven-digit number on the desk diary. “Apart from the obvious fact that it is your old Indian army number”, replied Holmes, “I see that it is the largest seven-digit number where the difference between it and its reverse is the cube of a factor of the number itself”.

    What was Watson’s number?

    [teaser2536]

     
    • Jim Randell's avatar

      Jim Randell 9:11 am on 5 June 2025 Permalink | Reply

      A simple search yields the largest possible number in a reasonable time.

      This Python program runs in 194ms. (Internal runtime is 122ms).

      from enigma import (irange, nrev, is_cube, printf)
      
      # consider 7-digit numbers
      for n in irange(9999999, 1000000, step=-1):
      
        # difference between n and its reverse is the cube of a divisor of n
        d = abs(n - nrev(n))
        if d == 0: continue
        x = is_cube(d)
        if x is None or n % x != 0: continue
      
        # output soultion
        printf("{n} [diff = {d} = {x}^3]")
        break  # we want the largest number
      

      Solution: Watson’s number is: 9841788.

      We have:

      abs(9841788 − 8871489) = 970299 = 99³
      9841788 = 99 × 99412


      Because the number is quite large the search doesn’t have to look too far, but we can speed it up.

      Writing the number as ABCDEFG then the difference between it and its reverse is a cube, so:

      abs(ABCDEFGGFEDCBA) = x³
      where x divides ABCDEFG

      And we can rewrite the argument to abs() as:

      99 × (10101(AG) + 1010(BF) + 100(CE))

      From which we see x must have factors of 3 and 11.

      And the original number is divisible by x, so it must be a multiple of 33, which means we can skip candidates that are not divisible by 33. And this gives us a nice compact program, with a much more acceptable runtime.

      The following Python program runs in 76ms. (Internal runtime is 12.4ms).

      from enigma import (irange, nrev, is_cube, printf)
      
      # consider 7-digit numbers
      for n in irange.round(9999999, 1000000, step=-33, rnd='I'):
      
        # difference between n and its reverse is a cube of a divisor of n
        d = abs(n - nrev(n))
        if d == 0: continue
        x = is_cube(d)
        if x is None or n % x != 0: continue
      
        # output solution
        printf("{n} [diff = {d} = {x}^3]")
        break  # we want the largest number
      

      There are 198 such 7-digit numbers, of which the largest is the required answer. The rest can be seen by removing the final [[ break ]] from the program.

      The smallest number with this property is 10164:

      abs(10164 − 46101) = 35937 = 33³
      10164 = 33 × 308

      Like

    • Frits's avatar

      Frits 1:19 pm on 5 June 2025 Permalink | Reply

      I have been discussing this Teaser with Brian Gladman for the last couple of days.
      This program has an interal runtime of 12 resp 135 microseconds (Py/PyCPython).

      from functools import reduce
      
      d2n = lambda *s: reduce(lambda x,  y: 10 * x + y, s)
      
      # the number's digits: abcdefg
      #
      # dif =   99^3.(a - g)                   (credit: Brian Gladman)
      #       + 99^2.{3.(a - g) + 10.(b - f) + (c - e)}
      #       + 99^1.(3.(a - g) + 20.(b - f) + (c - e)}
      #
      # dif must be a multiple of 99. If abs(dif) also must be a cube then
      # dif must be at least a multiple of 33^3.
      mx = int((10**7)**(1/3))
      
      # 4th digit from the end of the difference must be 0 or 9
      cbs = [i**3 for i in range(33, mx + 1, 33) if (cb := i**3) % 99 == 0 and 
                                                     str(cb)[-4] in "09"]
      
      # formula (10^6 - 1).(a - g) + 10.(10^4 - 1).(b - f) + 100.(10^2 - 1).(c - e)
      # equals <cb> if abcdefg > gfedcba otherwise it equals -<cb>
      mx = 0
      # process all possible differences (cubes)
      for cb in cbs:
        cr = round(cb ** (1/ 3))
        # if n % cr = 0 then abc9efg % cr must be one of 10 values 
        # (we can know them in advance)
        d9mods = [(i * (1000 % cr)) % cr for i in range(10)]
        s_d9mods = set(d9mods)
        for a in range(9, 0, -1):
          if mx and a < int(str(mx)[0]): break
          for b in range(9, -1, -1):
            if mx and b < int(str(mx)[1]): break
            for g in range(9, -1, -1):
              # a != g otherwise difference = ...0 and a cube root = ...0 
              #        which is impossible as a != 0 (and thus g != 0)
              if g == a: continue
              p1 = 999999 * (a - g)
              for f in range(9, -1, -1):
                p1p2 = p1 + 99990 * (b - f)
                # 99.(c - e) = (+/-)cb - p1p2  
                c_min_e, r = divmod((cb if a > g else -cb) - p1p2 , 9900)
                if not r and -10 < c_min_e < 10:
                  # list of possible c's
                  cs = (range(c_min_e, 10) if c_min_e > 0 else range(10 + c_min_e))
                  for c in reversed(cs):
                    e = c - c_min_e
                    r = (n9 := d2n(a, b, c, 9, e, f, g)) % cr
                    if r in s_d9mods:
                      d = 9 - d9mods.index(r)
                      n = n9 + 1000 * (d - 9)
                      if n > mx:
                        mx = n
                      break # propagate this break till we have a new <cb>
                  else: continue
                  break  
              else: continue
              break  
            else: continue
            break  
          else: continue
          break  
      
      print(f"answer: {mx}")    
      

      Like

      • Frits's avatar

        Frits 4:03 pm on 5 June 2025 Permalink | Reply

        Instead of using d9mods to determine “d” we can also use:

        d = ((a + c + e + g) - (b + f)) % 11
        

        Like

    • Frits's avatar

      Frits 1:49 pm on 5 June 2025 Permalink | Reply

      Less efficient.

      This program has an interal runtime of 1.1 resp 3.2 ms (PyPy/CPython).

      from collections import defaultdict
      
      # difference must be a multiple of 99. If difference also is a cube then
      # the difference must be at least a multiple of 33^3.
      mx = int((10**7)**(1/3))
      
      # 4th digit from the end of the difference must be 0 or 9
      cbs = {i**3 for i in range(33, mx + 1, 33) if (cb := i**3) % 99 == 0 and 
                                                    str(cb)[-4] in "09"}
      # 7-digit number = abcdefg
      # make a dictionary of possible <fg>'s per <ab> 
      d = defaultdict(set)
      for cube in cbs:
        last2 = (cube % 100)
        for ab in range(10, 100):
          ba = int(str(ab)[::-1])
          # 2 situations: fg < ba or fg > ba
          for i, fg in enumerate([(100 + ba - last2) % 100, (ba + last2) % 100]):
            gf = int(str(fg).zfill(2)[::-1]) 
            if i: 
              if ab > gf: 
                d[ab] |= {fg}
            else: 
              if ab < gf: 
                d[ab] |= {fg}   
            # ab = gf implies dif = ...00 which can't be a multiple of 33^3  
      
      for ab in reversed(range(10, 100)):
        ab_ = 100000 * ab 
        for cde in reversed(range(1000)):
          abcde = ab_ + 100 * cde
          # check all possible fg's
          for fg in sorted(d[ab], reverse=True):
            n = abcde + fg
            r = int(str(n)[::-1])
            # find whether the cube root of the difference
            # is an integer that is a divisor of the number
            if abs(n - r) in cbs:
              # calculate cube root of the difference
              cr = round(abs(n - r) ** (1/3))
              if n % cr == 0: 
                print("answer:", n)
                exit(0)
      

      Like

    • Jim Randell's avatar

      Jim Randell 3:01 pm on 6 June 2025 Permalink | Reply

      I’ve obviously not spent as much time analysing this as Frits. But here is an alternative approach that uses the [[ express() ]] function from the enigma.py library to express the difference between the original number and its reverse in terms of the differences between digits of the number (using the expression given in my original comment).

      It then generates all 198 possible 7-digit numbers with the property, and selects the largest of them.

      It has an internal runtime of 2.3ms.

      from enigma import (Accumulator, irange, inf, cb, express, cproduct, nconcat, group, subsets, unpack, printf)
      
      # collect pairs of digits by their difference
      diff = group(subsets(irange(0, 9), size=2, select='M'), by=unpack(lambda x, y: x - y))
      
      # find n for difference d, must be divisible by x (a multiple of 11)
      def solve(d, x):
        # find d1 = (A - G), d2 = (B - F), d3 = (C - E) values
        ms = [100, 1010, 10101]  # multiples of (C - E), (B - F), (A - G)
        # solve for quantities -9 ... +9
        for (q1, q2, q3) in express(d // 99, ms, min_q=-9, max_q=9):
          # consider +d and -d
          for (d3, d2, d1) in [(q1, q2, q3), (-q1, -q2, -q3)]:
            # find possible digit values
            for (A, G) in diff[d1]:
              if A == 0: continue
              for ((B, F), (C, E)) in cproduct([diff[d2], diff[d3]]):
                # x is a multiple of 11, so there is only one possible value for D
                D = (A - B + C + E - F + G) % 11
                if D > 9: continue
                n = nconcat(A, B, C, D, E, F, G)
                if n % x == 0:
                  yield n
      
      # find the largest value
      r = Accumulator(fn=max)
      
      # consider possible differences
      for x in irange(33, inf, step=33):
        d = cb(x)
        if d > 9999999: break
        # find possible n values
        for n in solve(d, x):
          r.accumulate_data(n, (d, x))
      
      # output solution
      (n, (d, x), t) = (r.value, r.data, r.count)
      printf("max(n) = {n} [diff = {d} = {x}^3] [from {t} values]")
      

      Like

      • Frits's avatar

        Frits 12:15 pm on 7 June 2025 Permalink | Reply

        @Jim,

        An interesting idea to use “express”.

        It looks like “n” is ordered within the (A, G) loop.

        If you use “irange(9, 0, -1)” in the diff formula you can break after the yield and propagate the break until you are out of the (A, G) loop.

        Like

    • ruudvanderham's avatar

      ruudvanderham 1:24 pm on 7 June 2025 Permalink | Reply

      import peek
      
      for i in range(9999999, 1000000 - 1, -1):
          if (diff := i - int("".join(*[reversed(str(i))]))) > 0:
              if (c := round(diff ** (1 / 3))) ** 3 == diff:
                  if i % c == 0:
                      peek(i, diff, c)
                      break

      Like

  • Unknown's avatar

    Jim Randell 8:35 am on 3 June 2025 Permalink | Reply
    Tags: ,   

    Teaser 2541: Lotto luck 

    From The Sunday Times, 5th June 2011 [link]

    Chris was lucky in the lotto, winning a whole number of pounds (under £ 5,000), which he shared with his five children. John got £ 1 more than a fifth of the total; Ciaran got £ 1 more than a fifth of the remainder. After Ciaran got his share, Fergal got £ 1 more than a fifth of the remainder, and after Fergal got his share, Brendan got £ 1 more than a fifth of what was left. After Brendan got his share, Chris gave Grainne £ 1 more than a fifth of what was left and kept the remainder (a whole number of pounds).

    How much did Chris keep?

    [teaser2541]

     
    • Jim Randell's avatar

      Jim Randell 8:36 am on 3 June 2025 Permalink | Reply

      For a starting amount X, the next child’s share is X/5 + 1, and the remainder is 4X/5 − 1.

      Each of the shares and remainders must be an integer, because if we ever get a share that has a fractional part, then the fractional part of the remainder will be further subdivided into a smaller fraction. And as both the starting amount and the final remainder are whole numbers, then all the intermediate numbers are too.

      The following Python program runs in 71ms. (Internal runtime is 3.7ms).

      from enigma import (irange, ediv, printf)
      
      # calculate share and remainder from total X
      def calc(X):
        share = ediv(X, 5) + 1
        return (share, X - share)
      
      # consider possible winnings
      for W in irange(1, 4999):
      
        # calculate share and remainder for each child
        try:
          (J, R) = calc(W)  # John
          (C, R) = calc(R)  # Ciaran
          (F, R) = calc(R)  # Fergal
          (B, R) = calc(R)  # Brendan
          (G, R) = calc(R)  # Grainne
        except ValueError:
          continue
      
        # output solution
        printf("W={W} -> J={J} C={C} F={F} B={B} G={G} -> R={R}")
      

      Solution: Chris kept £ 1019.

      The initial winnings were £ 3120, and they are distributed as:

      John = £ 625
      Ciaran = £ 500
      Fergal = £ 400
      Brendan = £ 320
      Grainne = £ 256
      Chris = £ 1019
      total = £ 3120

      It is easy to see that the initial amount won must be a multiple of 5, which allows us to skip 80% of the cases checked. (And brings the internal runtime down to 1.0ms).


      And with further analysis we can get an even faster solution:

      We find the remainder after step k is:

      R[k] = (4^k W − 5(5^k − 4^k)) / (5^k)

      So after the shares for the 5 sons have been distributed we have:

      R[5] = (1024 W − 10505) / 3125

      1024 W − 3125 R = 10505

      where: 0 < R < W < 5000.

      This is a Linear Diophantine Equation in 2 variables, and can be solved using the Extended Euclidean Algorithm [@wikipedia], implemented as [[ egcd() ]] in the enigma.py library.

      Here is a general solver for this kind of equation:

      from enigma import (egcd, div, divc)
      
      # solve linear diophantine equations in 2 variables:
      def diop_linear(a, b, c, mX=0, fn=0):
        """
        solve the linear Diophantine equation a.X + b.Y = c for integers X, Y
        (where a, b are non-zero, and gcd(a, b) divides c).
      
        return ((X0, Y0), (Xd, Yd)) to give solutions of the form:
      
          (X, Y) = (X0 + t.Xd, Y0 + t.Yd) for integer t
      
        the value of X0 returned is the smallest integer possible above (or equal
        to) mX, and Xd is positive.
      
        however, if <fn> is set, then a function f: t -> (X, Y) is returned instead.
        """
        if a == 0 or b == 0: raise ValueError("diop_linear: invalid equation")
        (X, Y, g) = egcd(a, b)
        if g > 1:
          (a, b, c) = (a // g, b // g, div(c, g))
          if c is None: raise ValueError("diop_linear: no solutions")
        # calculate particular solution (X0, Y0) and deltas (Xd, Yd)
        (X0, Y0) = (c * X, c * Y)
        (Xd, Yd) = ((-b, a) if b < 0 else (b, -a))
        # adjust X0 to be the smallest possible value
        t = divc(mX - X0, Xd)
        X0 += t * Xd
        Y0 += t * Yd
        if fn: return (lambda t: (X0 + t * Xd, Y0 + t * Yd))
        return ((X0, Y0), (Xd, Yd))
      

      Which we can then use to solve the puzzle:

      from enigma import (irange, inf, printf)
      
      # k = number of sons
      k = 5
      # solve the Diophantine equation: 4^k W - 5^k R = 5(5^k - 4^k)
      (p4k, p5k) = (4**k, 5**k)
      fn = diop_linear(p4k, -p5k, 5 * (p5k - p4k), fn=1)
      
      # consider increasing solutions where 0 < W < 5000
      for t in irange(0, inf):
        (W, R) = fn(t)
        if not (W < 5000): break
        if R < 0: continue
        # output solution
        printf("W={W} R={R}")
      

      This program has an internal runtime of just 35µs.

      Like

    • John Crabtree's avatar

      John Crabtree 8:03 pm on 4 June 2025 Permalink | Reply

      This teaser is a variation of the Monkey and the Coconuts puzzle which Martin Gardner wrote about decades ago in his column in the Scientific American. The puzzle is older than that.
      R[1] = 4W/5-1 = 4(W+5)/5 – 5
      And so R[k] = 4^k (W + 5) / (5^k) – 5
      R[5] = 1024(W+5) / 3125 – 5, and so (W+5) = 3125, and the answer follows.

      Like

  • Unknown's avatar

    Jim Randell 7:48 am on 29 May 2025 Permalink | Reply
    Tags:   

    Teaser 2537: Odds and evens 

    From The Sunday Times, 8th May 2011 [link] [link]

    I have taken two three-digit numbers and multiplied them together by long multiplication. [Below] are my workings, but with O marking the positions of the odd digits and E the even digits:

    What are the two three-digit numbers?

    [teaser2537]

     
    • Jim Randell's avatar

      Jim Randell 7:49 am on 29 May 2025 Permalink | Reply

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

      It runs in 76ms. (Internal runtime of the generated program is 478µs).

      #! python3 -m enigma -rr
      
      #      E E E          a b c
      #  *   O O O      *   d e f
      #  ---------      ----------
      #  E E E          g h i
      #    E O E    ->    j k m
      #    O O O E        n p q r
      #  =========      =========
      #  O E O O E      s t u v w
      
      SubstitutedExpression
      
      --distinct=""
      
      "{abc} * {def} = {stuvw}"
      
      "{abc} * {f} = {npqr}"
      "{abc} * {e} = {jkm}"
      "{abc} * {d} = {ghi}"
      
      # odds and evens
      --invalid="0,adefgjknpqsuv"
      --invalid="2|4|6|8,defknpqsuv"
      --invalid="1|3|5|7|9,abcghijmrtw"
      
      # reconstruct the multiplication sum (disable with: -O -A -S)
      --answer="({abc}, {def})"
      --output="lambda p, s, ans: output_mul(*ans, pre='  ', start='', end='')"
      

      Solution: The three-digit numbers are: 226 and 135.

      The completed multiplication sum looks like this:

      Like

    • ELBAZ HAVIV's avatar

      ELBAZ HAVIV 6:06 pm on 9 June 2025 Permalink | Reply

      Hello Jim

      Please help me to understand
      the removing of the 3 E’s

      from the original puzzle

      or why
      the original puzzle add those 3 E’s
      if they are not needed.

      Thank you.

      Like

      • Jim Randell's avatar

        Jim Randell 8:34 pm on 9 June 2025 Permalink | Reply

        The removed Es are the 0 padding in the intermediate multiplications.

        So instead of doing:

        226 × 100 = 22600
        226 × 30 = 6780
        226 × 5 = 1130

        We just do:

        226 × 1 = 226
        226 × 3 = 678
        226 × 5 = 1130

        And shift the results across by the appropriate number of places.

        Like

    • ELBAZ HAVIV's avatar

      ELBAZ HAVIV 9:04 pm on 9 June 2025 Permalink | Reply

      Hello again

      That’s a bit strange.
      but it’s ok

      because regularly long multiplication
      is as you show in the We just do:

      Thank you.

      Like

  • Unknown's avatar

    Jim Randell 8:04 am on 22 May 2025 Permalink | Reply
    Tags:   

    Teaser 2546: Secret agents 

    From The Sunday Times, 10th July 2011 [link] [link]

    Secret agents James and George exchange coded messages. The code is given by an addition sum, with different letters replacing different digits. The message is then written below the sum. James needs to send George the time (24-hour clock) at which their next secret operation is to begin. He texts as follows:

    THREE + THREE + TWO = EIGHT
    time: DATA

    When does this secret operation begin?

    [teaser2546]

     
    • Jim Randell's avatar

      Jim Randell 8:05 am on 22 May 2025 Permalink | Reply

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

      It runs in 77ms. (Internal runtime of the generated program is 94µs).

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      "THREE + THREE + TWO = EIGHT"
      --invalid="0,ET"
      
      # presumably DATA is a time expressed using the 24 hour clock (00:00 - 23:59)
      --extra="DA < 24"
      --extra="TA < 60"
      
      # format answer as a time using a 24 hour clock
      --code="time = lambda h, m: sprintf('{h:02d}:{m:02d}')"
      --answer="time(DA, TA)"
      

      Solution: The operation begins at 15:35.

      Like

    • Ruud's avatar

      Ruud 5:04 pm on 24 May 2025 Permalink | Reply

      import istr
      
      print(*(d|a|':'|t|a for t,h,r,e,w,o,i,g,a,d in istr.permutations(istr.digits()) if d|a<24 and t|a<60 and 2*(t|h|r|e|e)+(t|w|o)==(e|i|g|h|t)))
      

      Like

  • Unknown's avatar

    Jim Randell 8:24 am on 15 May 2025 Permalink | Reply
    Tags:   

    Teaser 2552: Ever-increasing circles 

    From The Sunday Times, 21st August 2011 [link] [link]

    I took a piece of A4 paper and cut it straight across to make a square and a rectangle. I then cut the square along a diagonal and, from the rectangle, cut as large a circle as I could. My friend Ron said that if I needed two triangles of those sizes and as large a circle in one piece as possible from an A4 sheet, then he could do much better. With a fresh sheet of A4, he produced the two triangles and the biggest circle possible.

    What is the area of his circle divided by the area of mine?

    [teaser2552]

     
    • Jim Randell's avatar

      Jim Randell 8:24 am on 15 May 2025 Permalink | Reply

      A-series paper has the aspect ratio 1 : √2.

      Cutting a square from a sheet will leave a 1 × (√2 − 1) rectangle, out of which we can cut out a circle with radius (√2 − 1)/2.

      By rotating one of the triangles so that the √2 side lies along one of the long edges of the sheet, we open up a kite shaped area, in which we can inscribe a circle.

      The radius of a semi-circle inscribed in a right-angled triangle with sides (a, b, h) is given by:

      1/r = 1/a + 1/b; or:
      r = a.b / (a + b)

      (See proof below).

      And in this case we have:

      a = 1
      b = √2 − 1

      r = (√2 − 1) / √2
      r = (√2 − 1)(√2/2)

      And so we see the radius of Ron’s circle is √2 times the radius of the setters original circle.

      And so the area of Ron’s circle is twice the area of the setters circle.

      Solution: The answer is 2.


      Proof:

      Consider the right-angled triangle ABC, where the length of the sides BC = a, AC = b.

      The area of the triangle is: a.b/2.

      But we can also write the area as the sum of the areas of triangles AXC and BXC, both of which have a height of r, the radius of the semicircle.

      So:

      a.r/2 + b.r/2 = a.b/2
      r(a + b) = a.b
      r = a.b / (a + b)

      Like

  • Unknown's avatar

    Jim Randell 8:13 am on 9 May 2025 Permalink | Reply
    Tags:   

    Teaser 2553: The X-Factor 

    From The Sunday Times, 28th August 2011 [link] [link]

    George and Martha’s daughter entered a singing competition. The entrants were numbered from one upwards. The total number of entrants was a three-digit number and their daughter’s number was a two-digit number. The five digits used in those two numbers were all different and non-zero. Martha noted that the number of entrants was a single-digit multiple of their daughter’s number. George realised the same would be true if the two digits of their daughter’s number were reversed.

    How many entrants were there?

    [teaser2553]

     
    • Jim Randell's avatar

      Jim Randell 8:14 am on 9 May 2025 Permalink | Reply

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

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # total number of entrants = ABC
      # daughters number = XY
      
      --digits="1-9"
      
      "ediv(ABC, XY) < 10"
      "ediv(ABC, YX) < 10"
      
      --answer="ABC"
      

      Solution: There were 168 entrants.

      The daughter’s number is either 24 or 42.

      168 / 24 = 7
      168 / 42 = 4

      Like

      • Frits's avatar

        Frits 3:31 pm on 9 May 2025 Permalink | Reply

        I tested that “ediv” is a bit faster than “div” as for “div” an error/exception may occur when comparing “None” with an integer.

        Like

        • Jim Randell's avatar

          Jim Randell 4:19 pm on 9 May 2025 Permalink | Reply

          @Frits: [[ ediv() ]] throws an error if the division is not exact, whereas [[ div() ]] returns [[ None ]].

          In Python 3 you will get type error if you compare [[ None ]] to a number. But in Python 2 [[ None ]] compares as less than any number (including [[ -inf ]]). So using [[ ediv() ]] also allows the run file to work in older versions of Python.

          Like

          • Frits's avatar

            Frits 3:18 pm on 10 May 2025 Permalink | Reply

            or skipping a loop over “J”.

            #! python3 -m enigma -rr
             
            SubstitutedExpression
             
            # total number of entrants = ABC
            # daughters number = XY
             
            --distinct="ABCXY"
            --digits="1-9"
            --invalid="1,I"
            
             
            # find the single digit multiples I, J
            "XY * I = ABC"
            "ABC % YX == 0 and ABC // YX < 10"
             
            --answer="ABC"
            

            Like

      • Jim Randell's avatar

        Jim Randell 8:33 am on 10 May 2025 Permalink | Reply

        Or without using [[ *div() ]] function calls.

        This run file has an internal runtime of 188µs.

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        # total number of entrants = ABC
        # daughters number = XY
        
        --distinct="ABCXY,IJ"
        --digits="1-9"
        
        # find the single digit multiples I, J
        "XY * I = ABC"
        "YX * J = ABC"
        
        --answer="ABC"
        

        (Further speed improvements left as a simple exercise for the reader).

        Like

    • Ruud's avatar

      Ruud 3:01 pm on 9 May 2025 Permalink | Reply

      import peek
      import istr
      
      for daughter in istr(range(10, 100)):
          for n, m in istr.combinations(range(1, 10), r=2):
              if (
                  (entrants := daughter * n) == (daughter_reversed := daughter.reversed()) * m
                  and len(entrants) == 3
                  and ("0" not in entrants)
                  and (entrants | daughter).all_distinct()
                  and (entrants | daughter_reversed).all_distinct()
              ):
                  peek(entrants, daughter, daughter_reversed)
      

      Like

      • Frits's avatar

        Frits 4:09 pm on 9 May 2025 Permalink | Reply

        @Ruud,

        Line 11 doesn’t seem to be logically necessary.

        I read the readme of the peek module at your Salabim site. I wish I had known this before. I will have a look at it (I’ll send you an email concerning an installation issue).

        I made my own simple debugging tool, I don’t need to import anything as I use preprocessing.
        A basic version is described at:

        [ https://enigmaticcode.wordpress.com/notes/#comment-10234 ]

        Like

        • ruudvanderham's avatar

          ruudvanderham 1:16 pm on 10 May 2025 Permalink | Reply

          Yes, you are right. Line 11 could be omitted (although it is not wrong).
          Nice to be in contact (in Dutch!) with you on the subject of peek.

          Like

  • Unknown's avatar

    Jim Randell 7:37 am on 1 May 2025 Permalink | Reply
    Tags:   

    Teaser 2554: Odd and even 

    From The Sunday Times, 4th September 2011 [link] [link]

    Roddy and Eve have a set of cards on which is written a consecutive sequence of whole numbers, one number on each card. Roddy challenges Oliver to choose two cards at random, promising a prize if the sum of the numbers is odd. Eve issues the same challenge, but offers a prize for an even sum. Oliver accepts the challenge that gives him the greater probability of winning. This probability is a whole number percentage and, when reversed, it gives the two-digit number of cards.

    Whose challenge did Oliver accept and how many cards are there?

    [teaser2554]

     
    • Jim Randell's avatar

      Jim Randell 7:37 am on 1 May 2025 Permalink | Reply

      We can consider sets of cards numbered 1 .. n, as if each card has a offset x from these values, then the sum of the pairs is increased by 2x, and this doesn’t change whether the sum is odd or even.

      The percentage must be > 50%, and it is the reverse of the number of cards.

      This Python program runs in 77ms. (Internal runtime is 16ms).

      from enigma import (irange, subsets, div, nrev, printf)
      
      # consider 2-digit percentages >50%
      for p in irange(51, 99):
        # reverse is the number of cards
        n = nrev(p)
        if n < 10: continue
      
        # count odd/even pairs
        t = [0, 0]
        for (x, y) in subsets(irange(1, n), size=2):
          t[(x + y) % 2] += 1
        T = sum(t)
      
        # calculate higher probability as a percentage
        x = max(t)
        q = div(100 * x, T)
        if q == p:
          # output solution
          printf("n={n} -> t={t} p={p}% (= {x}/{T})")
      

      Solution: There are 25 cards. Oliver chooses Roddy’s challenge.

      With 25 cards there are C(25, 2) = 300 ways of choosing a pair of cards.

      144 of them give an even total, and 156 of them give an odd total.

      So the probability of choosing cards that give an odd total is:

      156 / 300 = 0.52 or 52%

      Like

    • Frits's avatar

      Frits 6:06 pm on 1 May 2025 Permalink | Reply

      # only consider odd number of cards, n = 10 * a + b
      # number odd sums = 1st even * 2nd odd + 1st odd * 2nd even
      # ((n - 1) // 2) * ((n + 1) // 2) + ((n + 1) // 2) * ((n - 1) // 2) or
      # (n**2 - 1) / 2
      # percentage = number odd / (n * (n - 1) = (n + 1) / (2 * n)
      
      # a whole number percentage and, when reversed, 
      # it gives the two-digit number of cards
      # 100 * ((10 * a + b) + 1) / ((10 * a + b) * 2) = 10 * b + a
      # 500 * a + 50 * b + 50 = (a + 10 * b) * (10 * a + b)
      # 10 * a**2 + (101 * b - 500) * a + 10 * b**2 - 50 * b - 50 = 0
      
      for b in range(5, 10, 2):
        a, r = divmod(500 - 101 * b + 3 * (1089 * b**2 - 11000 * b + 28000)**.5, 20)
        if r: continue
        print(f"There are {10 * int(a) + b} cards. "
              f"Oliver chooses Roddy's challenge")
      

      Like

  • Unknown's avatar

    Jim Randell 6:45 am on 24 April 2025 Permalink | Reply
    Tags:   

    Teaser 2555: Today’s value 

    From The Sunday Times, 11th September 2011 [link] [link]

    These 10 children’s bricks are numbered from 1 to 10. Where a brick rests on two others its number is the difference of their two numbers. Given that U = 1 …

    What is the number DAY?

    [teaser2555]

     
    • Jim Randell's avatar

      Jim Randell 6:46 am on 24 April 2025 Permalink | Reply

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

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # numbers range from 1 to 10
      --base=11
      --digits="1-10"
      
      "abs(U - N) = S"
      "abs(D - A) = U"
      "abs(A - Y) = N"
      "abs(T - I) = D"
      "abs(I - M) = A"
      "abs(M - E) = Y"
      
      --assign="U,1"
      --answer="(D, A, Y)"
      
      --template=""
      

      Solution: DAY = 672.

      At least the digits have values: D = 6, A = 7, Y = 2.

      But since the digits go from 1 to 10, you could argue that the result is expressed in base 11, so:

      DAY = 672 [base 11] = 805 [base 10]

      Like

      • Ruud's avatar

        Ruud 6:14 am on 28 April 2025 Permalink | Reply

        Brute force one liner:

        import itertools
        
        print(
            *(
                f"{d}{a}{y}"
                for s, n, d, a, y, t, i, m, e in itertools.permutations(range(2, 11), 9)
                if s == abs(1 - n) and 1 == abs(d - a) and n == abs(a - y) and d == abs(t - i) and a == abs(i - m) and y == abs(m - e)
            )
        )
        

        Like

    • Frits's avatar

      Frits 10:51 am on 26 April 2025 Permalink | Reply

      #    S
      #   U N                            U = 1
      #  D A Y
      # T I M E
      
      sols = set()
      U, mx = 1, 10
      r0 = set(range(2, mx + 1)) # remaining digits to use
      
      for N in range(2, mx - 1):  # no using <mx - 1> and <mx> on this level
        S = N - U
        r1 = r0 - {N, S}
        if len(r1) != len(r0) - 2: continue
        for A in r1 - {mx}:       
          r2 = r1 - {A}
          for D in (A - U, A + U):
            if D not in r2 or D == mx: continue
            r3 = r2 - {D}
            for Y in (A - N, A + N):
              if Y not in r3 or Y == mx: continue
              r4 = r3 - {Y}
              if max(r4) - min(r4) < max(D, A, Y): continue
              for I in r4:
                for M in (I - A, I + A):
                  if M not in r4: continue
                  for T in (I - D, I + D):
                    if T not in r4: continue
                    for E in (M - Y, M + Y):
                      if {T, I, M, E} != r4: continue
                      sols.add(100 * D + 10 * A + Y)
      
      print("answer(s):", ' or '.join(str(s) for s in sols))
      

      Like

  • Unknown's avatar

    Jim Randell 8:18 am on 22 April 2025 Permalink | Reply
    Tags:   

    Teaser 2550: Cricket averages 

    From The Sunday Times, 7th August 2011 [link] [link]

    Playing for our local team, Sam and Oliver between them took 5 wickets in each innings, taking 5 wickets each overall. Sam’s averages (i.e. runs per wicket) were lower than Oliver’s in both innings, but overall Sam had the higher average. All six averages were single non-zero digits.

    If you knew Sam’s overall average, it would then be possible to calculate the number of runs scored against Sam and against Oliver.

    What were the total runs scored against: (a) Sam and (b) Oliver?

    [teaser2550]

     
    • Jim Randell's avatar

      Jim Randell 8:19 am on 22 April 2025 Permalink | Reply

      This Python program chooses possible numbers of wickets taken by each bowler in each innings, and then considers possible averages such than Sam has a lower average than Oliver in each innings, but a higher average overall.

      From the possible candidate scenarios we then looks for situations where knowing Sam’s overall average allows you to determine the overall number of runs scored against each bowler.

      It runs in 65ms. (Internal runtime is 2.2ms).

      from enigma import (Record, decompose, subsets, irange, div, filter_unique, printf)
      
      # generate possible scenarios
      def generate():
        # choose numbers of wickets taken by S and O in the first innings
        for (wS1, wO1) in decompose(5, 2, increasing=0, sep=0, min_v=1):
          # numbers of wickets taken in the second innings
          (wS2, wO2) = (5 - wS1, 5 - wO1)
      
          # S and O's averages in the first innings (single digit numbers)
          for (aS1, aO1) in subsets(irange(1, 9), size=2):
            # calculate runs against S and O in the first innings
            (rS1, rO1) = (aS1 * wS1, aO1 * wO1)
      
            # averages in the second innings
            for (aS2, aO2) in subsets(irange(1, 9), size=2):
              # calculate runs against S and O in the second innings
              (rS2, rO2) = (aS2 * wS2, aO2 * wO2)
      
              # overall averages
              (orS, orO) = (rS1 + rS2, rO1 + rO2)
              (oaS, oaO) = (div(orS, 5), div(orO, 5))
              if oaS is None or oaO is None or not (oaS > oaO): continue
      
              # output: first innings; second innings; totals
              printf("[1: S={wS1}w for {rS1}r avg={aS1}, O={wO1}w for {rO1}r avg={aO1}; 2: S={wS2}w for {rS2}r avg={aS2}, O={wO2}w for {rO2}r avg={aO2}; T: S=5w for {orS}r avg={oaS}, O=5w for {orO}r avg={oaO}]")
              yield Record(
                wS1=wS1, wO1=wO1, rS1=rS1, rO1=rO1, aS1=aS1, aO1=aO1,
                wS2=wS2, wO2=wO2, rS2=rS2, rO2=rO2, aS2=aS2, aO2=aO2,
                orS=orS, orO=orO, oaS=oaS, oaO=oaO,
              )
      
      # if you knew oaS, then you could determine (orS, orO)
      rs = filter_unique(generate(), (lambda r: r.oaS), (lambda r: (r.orS, r.orO)))
      for r in rs.unique:
        printf("S={r.orS}r O={r.orO}r [{r}]")
      

      Solution: (a) 35 runs were scored against Sam; (b) 25 runs were scored against Oliver.

      Here is one scenario:

      First innings:
      Sam took 1 wicket, for 3 runs (avg = 3/1 = 3).
      Oliver took 4 wickets, for 16 runs (avg = 16/4 = 4).

      Second innings:
      Sam took 4 wickets, for 32 runs (avg = 32/4 = 8).
      Oliver took 1 wicket, for 9 runs (avg = 9/1 = 9).

      Overall:
      Sam took 5 wickets, for 35 runs (avg = 35/5 = 7).
      Oliver took 5 wickets, for 25 runs (avg = 25/5 = 5).

      In each innings Sam has a lower average than Oliver, but overall Oliver has a lower average than Sam.

      There is a second scenario, with the innings in the opposite order.

      Like

    • Frits's avatar

      Frits 3:35 pm on 22 April 2025 Permalink | Reply

      Inventing the wheel again to determine a duplicate entry. I didn’t feel like grouping entries per overall average.

      from enigma import SubstitutedExpression
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # Sam:    1st: A wickets, avg B, 2nd: C wickets, avg D, overall avg E
          # Oliver: 1st: P wickets, avg Q, 2nd: R wickets, avg S, overall avg T
          
          # between them took 5 wickets in each innings and taking 
          # 5 wickets each overall (assume Sam took more wickets in the 2nd inning)
          "5 - A = P", "5 - P = R", "5 - R = C",
          "A < C", "A + C == 5", 
          # Sam had lower averages ...
          "B < Q", "D < S",
          # total averages"
          "div(trs := A * B + C * D, 5) = E",
          "div(tro := P * Q + R * S, 5) = T",
          # ... but a higher overall average
          "E > T",
        ],
        answer="E, (trs, tro)",
        d2i=dict([(0, "ACPRQS")] +
                 [(k, "ACPR") for k in range(5, 9)] +
                 [(9, "ACPRBD")]),
        distinct="",
        verbose=0,    # use 256 to see the generated code
      )
      
      c, prev, sols = 0, (-1, -1), set()
      # get unique values of Sam's overall average
      for (soa, tr) in sorted(p.answers()) + [(-1, prev)]:
        if soa != prev[0]:
          if c == 1:
            sols.add(prev[1])
          c = 1
        else:
          c += 1
        prev = (soa, tr)
      
      # print total runs
      for trS, trO in sols:  
        print(f"answer: a() = {trS} runs, b() = {trO} runs")
      

      Like

  • Unknown's avatar

    Jim Randell 10:08 am on 17 April 2025 Permalink | Reply
    Tags:   

    Teaser 2535: Eastertide 

    From The Sunday Times, 24th April 2011 [link] [link]

    In the following division sum I have consistently replaced digits with letters, with different letters used for different digits. All three numbers featured are even:

    EASTER ÷ TIDE = EGG

    What is the numerical value of my TEASER?

    [teaser2535]

     
    • Jim Randell's avatar

      Jim Randell 10:08 am on 17 April 2025 Permalink | Reply

      We can solve this puzzle using the [[ SubstitutedExpression ]] solver from the enigma.py library:

      The following command line executes in 83ms. (Internal runtime of the generated code is 5.6ms).

      % python3 -m enigma SubstitutedExpression "EGG * TIDE = EASTER" --answer="TEASER"
      (EGG * TIDE = EASTER) (TEASER)
      (244 * 1062 = 259128) (125928) / A=5 D=6 E=2 G=4 I=0 R=8 S=9 T=1
      TEASER = 125928 [1 solution]
      

      Solution: TEASER = 125928.

      Like

  • Unknown's avatar

    Jim Randell 8:32 am on 11 April 2025 Permalink | Reply
    Tags:   

    Teaser 2557: The legacy 

    From The Sunday Times, 25th September 2011 [link] [link]

    In my will I have set aside a five-digit sum of pounds for charity — a group of four animal charities and a group of seven children’s charities. This five-digit sum consists of five consecutive non-zero digits, not in numerical order. On my death this legacy is to be divided equally between these charities. But I have left it to my executors’ discretion to choose to divide it instead equally among the charities in just one of the groups. Each donation will be a whole number of pounds, whichever course they take.

    What is the five-digit sum?

    [teaser2557]

     
    • Jim Randell's avatar

      Jim Randell 8:32 am on 11 April 2025 Permalink | Reply

      The amount must be divisibly by 4, 7, 11, and LCM(4, 7, 11) = 308.

      So we are looking for a 5-digit number that is a multiple of 308, and is composed from a set of 5 different non-zero consecutive digits, but not in numerical order.

      This Python program looks at 5-digit multiples of 308. It has a runtime of 63ms, and an internal runtime of 1.7ms.

      from enigma import (irange, mlcm, nsplit, is_consecutive, printf)
      
      # look for 5-digit multiples of 4, 7, 11
      k = mlcm(4, 7, 11)
      for n in irange.round(10000, 99999, step=k, rnd='I'):
        # split into digits
        ds = nsplit(n)
        # look for consecutive non-zero digits
        if (0 in ds) or is_consecutive(ds) or not is_consecutive(sorted(ds)): continue
        # output solution
        printf("amount = {n}")
      

      But it is slightly faster (but also slightly longer) to start with the possible non-zero consecutive digits, and then consider rearrangements of those digits.

      This Python program also runs in 63ms, but has an internal runtime of 784µs.

      from enigma import (irange, mlcm, tuples, subsets, nconcat, is_consecutive, printf)
      
      # amount must be divisible by 4, 7, 11
      k = mlcm(4, 7, 11)
      
      # choose 5 consecutive digits
      for ds in tuples(irange(1, 9), 5):
        # consider possible numbers formed from these digits
        for ds in subsets(ds, size=len, select='P'):
          n = nconcat(ds)
          # check divisibility
          if not (n % k == 0): continue
          # check the digits are not consecutive
          if is_consecutive(ds): continue
          # output solution
          printf("amount = {n}")
      

      Solution: The amount is: £ 74536.

      £ 74536 = 4× £ 18634
      £ 74536 = 7× £ 10648
      £ 74536 = 11× £ 6776

      If a 0 digit is permitted there is a second solution of £ 43120.

      Like

    • Frits's avatar

      Frits 4:59 pm on 11 April 2025 Permalink | Reply

      Using divisibility rules.

      from itertools import permutations
      
      # suppose d1 + d3 + d5 = d2 + d4 + k   
      # then k % 11 = 0 as legacy is divisible by 11 (alternating sum)
      # t = sum(d1...d5) = 2 * (d2 + d4) + k
      
      for i, t in enumerate(range(15, 36, 5), 1):
        # it t odd then k = 11, it t even then k = 0
        k = 11 if t % 2 else 0
        d2d4 = (t - k) // 2
        if d2d4 < 2 * i + 1 or d2d4 > 2 * i + 7: continue
        for d2 in range(i, i + 5):
          d4 = d2d4 - d2
          if d4 == d2 or d4 not in range(i, i + 5): continue
          for d1, d3, d5 in permutations(set(range(i, i + 5)) - {d2, d4}):
            if d5 % 2: continue
            n45 = 10 * d4 + d5
            # divisibility by 4
            if n45 % 4: continue
            # divisibility by 7 rule (alternating sum of blocks of three)
            if (100 * d3 + n45 - 10 * d1 - d2) % 7: continue 
      
            print("answer:", ''.join(str(x) for x in (d1, d2, d3, d4, d5)))
      

      Like

  • Unknown's avatar

    Jim Randell 8:27 am on 8 April 2025 Permalink | Reply
    Tags:   

    Teaser 2547: Multiple celebration 

    From The Sunday Times, 17th July 2011 [link] [link]

    Today is my birthday and the birthday of my granddaughter Imogen. My age today is a whole-number multiple of hers, and this has been true on one third of our joint anniversaries.

    If we both live until I am six times her current age, then my age will be a multiple of hers on two more birthdays.

    How old are we today?

    [teaser2547]

     
    • Jim Randell's avatar

      Jim Randell 8:27 am on 8 April 2025 Permalink | Reply

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

      from enigma import (irange, is_divisor, printf)
      
      # find multiple anniversaries for [a, b] and [a+d, b+d]
      def multiples(d, a, b):
        for x in irange(a, b):
          y = x + d
          if is_divisor(y, x):
            yield (x, y)
      
      # consider possible ages for the granddaughter (must be a multiple of 3)
      for g in irange(3, 20, step=3):
        # consider the setters age (a multiple of g, less than 6g)
        for k in [2, 3, 4, 5]:
          s = k * g
      
          # calculate earlier multiples
          d = s - g
          ms = list(multiples(d, 1, g))
          # exactly 1/3 of the joint anniversaries so far are multiples
          if not (len(ms) * 3 == g): continue
      
          # and in there future there will be 2 more multiple anniversaries
          ms_ = list(multiples(d, g + 1, 6 * g - d))
          if not (len(ms_) == 2): continue
      
          # output solution
          printf("g={g} s={s} [k={k} d={d}] -> {ms} {ms_}")
      

      Solution: The setter is 72. The granddaughter is 18.

      So the age difference is 54 years.

      The multiples in the past are:

      1 year old & 55 years old (multiple = 55)
      2 years old & 56 years old (multiple = 28)
      3 years old & 57 years old (multiple = 19)
      6 years old & 60 years old (multiple = 10)
      9 years old & 63 years old (multiple = 7)
      18 years old & 72 years old (multiple = 4)

      Which is 6 anniversaries out of 18, so one third of them are multiples.

      In the future (with the setters age up to 6 × 18 = 108) we can have the following multiples:

      27 years old & 81 years old (multiple = 3)
      54 years old & 108 years old (multiple = 2)

      Like

  • Unknown's avatar

    Jim Randell 10:30 am on 4 April 2025 Permalink | Reply
    Tags: ,   

    Teaser 2558: Round table 

    From The Sunday Times, 2nd October 2011 [link] [link]

    Pat likes to demonstrate his favourite trick shot on the pub pool table, which is 90 inches long and 48 inches wide. Pat hits the ball so that it strikes the long side of the table first, then (after a perfect bounce) it hits the short side, then the other long side, and then finally the other short side, from which it returns to the starting point, making its path the perimeter of a quadrilateral.

    What is the length of the perimeter of that quadrilateral?

    [teaser2558]

     
    • Jim Randell's avatar

      Jim Randell 10:31 am on 4 April 2025 Permalink | Reply

      (See also: Teaser 3184, Teaser 3073, Teaser 1917).

      Wherever the ball starts tracing out the quadrilateral, we can consider its movement in the x and y directions.

      In the x direction it travels to the cushion on one end, back to the other cushion, and then returns to its original position.

      i.e. the total distance travelled in the x direction is twice the length of the table (= 2 × 90 in = 180 in).

      Similarly the distance travelled in the y direction is twice the width of the table (= 2 × 48 in = 96 in).

      Hence the total distance travelled by the ball is: hypot(180, 96):

      >>> hypot(180, 96)
      204.0
      

      Solution: The length of the perimeter of the quadrilateral is 204 in.

      Like

  • Unknown's avatar

    Jim Randell 8:59 am on 2 April 2025 Permalink | Reply
    Tags:   

    Teaser 2561: Winning the double 

    From The Sunday Times, 23rd October 2011 [link] [link]

    Four teams, A, B, C and D, played each other once in a league, then played a knockout competition. They scored a total of 54 goals in the nine games, with a different score in each game; no team scored more than five goals in any game. For each team the average number of goals per game was a different whole number. Team B beat D by a margin of more than one goal in the knockout competition, but team A took the double, winning all their games.

    What was the score in the match CvD?

    [teaser2561]

     
    • Jim Randell's avatar

      Jim Randell 9:00 am on 2 April 2025 Permalink | Reply

      In the tournament the 6 matches are:

      AvB, AvC, AvD, BvC, BvD, CvD

      In the knockout there is a BvD match (which B won), but A won the knockout. So the matches in the knockout must be:

      1st round: AvC (A wins); BvD (B wins)
      Final: AvB (A wins)

      So A and B have played 5 matches, and C and D have played 4.

      A won all their matches, so their goal average must be at least 3.

      B won at least one match, so their goal average must be at least 1.

      The following run file finds possible scorelines in each match. It isn’t very fast though, but it does run in 10.3s under PyPy.

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # tournament:
      #
      #     A B C D
      #  A: - H I J  (A wins all matches)
      #  B: K - L M
      #  C: N P - Q
      #  D: R S T -
      #
      # knockout:
      #
      #  round 1: A v C = U - V (A wins); B v D = W - X (B wins)
      #  final: A v B = Y - Z (A wins)
      #
      # no team scored more than 5 goals, so goals (and goal averages = A, B, C, D) are all 0-5
      --base=6  # allows digits 0-5
      --distinct="ABCD"  # goal averages are all different
      
      # total goals scored by each team
      --macro="@goalsA = (H + I + J + U + Y)"
      --macro="@goalsB = (K + L + M + W + Z)"
      --macro="@goalsC = (N + P + Q + V)"
      --macro="@goalsD = (R + S + T + X)"
      
      # total of all the goals scored is 54
      "@goalsA + @goalsB + @goalsC + @goalsD == 54"
      
      # each game has a different score line
      "seq_all_different(ordered(x, y) for (x, y) in zip([H, I, J, L, M, Q, U, W, Y], [K, N, R, P, S, T, V, X, Z]))"
      
      # goal averages (are all different by --distinct)
      "A * 5 == @goalsA"
      "B * 5 == @goalsB"
      "C * 4 == @goalsC"
      "D * 4 == @goalsD"
      "A > 2"  # A won all their matches
      "B > 0"  # B won at least one match
      # and the total of all goals scored is 54
      "5 * (A + B) + 4 * (C + D) == 54"
      
      # B beat D by more than 1 goal in the knockout
      "W > X + 1"
      
      # A won all their games
      "H > K" "I > N" "J > R" "U > V" "Y > Z"
      
      --template="(H-K I-N J-R L-P M-S Q-T) (U-V W-X; Y-Z) (A B C D)"
      --header="(AvB AvC AvD BvC BvD CvD) (AvC BvD; AvB) (A B C D)"
      --solution=""
      --answer="(Q, T)"  # answer is the score in C vs D match
      --denest=-1  # CPython workaround
      

      Solution: The score in the C vs D match was 5 – 5.

      The program finds 48 ways to assign the scores in each match.

      For example, one of the assignments is:

      Tournament:
      A vs B = 5-1
      A vs C = 5-3
      A vs D = 5-0
      B vs C = 0-4
      B vs D = 0-3
      C vs D = 5-5

      Knockout:
      A vs C = 5-4
      B vs D = 2-0
      A vs B = 5-2

      A wins all their matches by scoring 5 goals, so their goal average is 5. And the goal averages for B, C, D are B = 1 (from 5 goals), C = 4 (from 16 goals), D = 2 (from 8 goals).

      Like

      • Frits's avatar

        Frits 1:20 pm on 2 April 2025 Permalink | Reply

        @Jim, typo in line 48: “J > R”.
        It makes the program slower (and 48 ways to assign the scores in each match)

        Like

      • Frits's avatar

        Frits 2:17 pm on 2 April 2025 Permalink | Reply

        Easy performance enhancement (from Brian’s analysis):

        --invalid="5,BCD"  # teams other than A cannot score 5 goals in all their games
        

        Like

        • Jim Randell's avatar

          Jim Randell 2:27 pm on 2 April 2025 Permalink | Reply

          Yes. With a little analysis there are some easy additional restrictions we can deduce.

          These bring the run time down to a much more reasonable 196ms.

          # more restrictions we can deduce
          --invalid="0,ABHIJUWY"
          --invalid="1,AW"
          --invalid="2,A"
          --invalid="4,X"
          --invalid="5,BCDKNRVXZ"
          

          And with more analysis we can go further. But I prefer to let the computer do the majority of the work.

          Like

    • Frits's avatar

      Frits 1:15 pm on 3 April 2025 Permalink | Reply

      The program loops over the requested score so we can early return as soon as a solution is found (and not report 47 similar solutions).

      The runtime approaches the 10 ms with CPython (as is feasible for most teasers).

      from itertools import product
      
      # Analysis: (credit B. Gladman)
      
      # If the average goals per game for A, B, C, D are a, b, c and d we then have
      #
      #     54 == 5.(a + b) + 4.(c + d) 
      #
      # (since A and B play five games whereas C and D play four). Here the possible
      # solutions (a + b, c + d) = (10, 1), (6, 6) or (2, 11).
       
      # Since the maximum goals a team can score in any game is 5, all the averages
      # are five or less. And, since the averages are all different, a + b and c + d
      # are both less than 10 which means that a + b == c + d == 6. Moreover, since
      # A wins all their games, other teams cannot score 5 goals in all their games
      # and must have goals per match averages of less than five.
      #
      # Hence the possibilities are:
      #
      #      (a, b) = (5, 1), (4, 2), (2, 4)
      #      (c, d) = (4, 2), (2, 4)
      #
      # And since the values are all different 
      #
      #      (a, b, c, d) =  (5, 1, 4, 2) or (5, 1, 2, 4)
      #
      #
      # Hence in goal totals (A, B, C, D) = (25, 5, 16, 8) or (25, 5, 8, 16)
      
      (H, I, J, U, Y, A, B) = (5, 5, 5, 5, 5, 5, 1)
      
      # tournament:
      #
      #     A B C D
      #  A: - H I J  (A wins all matches)
      #  B: K - L M
      #  C: N P - Q
      #  D: R S T -
      #
      # knockout:
      #
      #  round 1: A v C = U - V (A wins); B v D = W - X (B wins)
      #  final: A v B = Y - Z (A wins)
      #
      
      def inner(Q, T):
        for C in [2, 4]:
          D = 6 - C
          # A wins all their games with 5 goals  
          if 5 in {Q, T} and (Q, T) != (5, 5): continue
          for X in range(3):                        # X != 3 as W < 5
            for W in range(X + 2, 5):               # B beat D by more than one goal
              for R in range(5):
                S = D * 4 - (R + T + X)             # team D
                if not (0 <= S <= 4): continue      # S != 5 as M <= 3
                for V in range(5):                  # as W >= 2 then K,L,M,Z <= 3
                  if V == R: continue
                  for N in range(5):
                    if N in {V, R}: continue
                    P = C * 4 - (N + Q + V)         # team C
                    if not (0 <= P <= 4): continue  # P != 5 als L <= 3
                    # as W >= 2 then K,L,M,Z <= 3
                    for L, M in product(range(4), repeat=2): 
                      # different games
                      if (len(set(s := list(zip([L, M, Q, W], [P, S, T, X])))) !=
                          len(s)): continue
                      for Z in range(4):            # as W >= 2 then K,L,M,Z <= 3
                        if Z in {V, R, N}: continue
                        K = B * 5 - (L + M + W + Z) # team B
                        if not (0 <= K <= 3) or K in {V, R, N, Z}: continue
                        if H+I+J+K+L+M+N+P+Q+R+S+T+U+V+W+X+Y+Z != 54: continue
                        return str((Q, T))
      
      sols = []
      # Process scores CvD  (Q, T)
      for C, D in product(range(6), repeat=2):
        if (s := inner(C, D)):
          sols.append(s)
      
      print("answer(s):", ' or '.join(sols))  
      

      Like

    • Frits's avatar

      Frits 6:20 pm on 3 April 2025 Permalink | Reply

      Another one using more analysis. Internal runtime 22 microseconds (under PyPy).

      from itertools import product, permutations
      
      stup = lambda x, y: (x, y) if x < y else (y, x)
        
      def diffgames(a, b):
        return len(set(s := list(stup(x, y) for x, y in zip(a, b)))) == len(s)
        
      # Analysis: (credit B. Gladman)
      
      # If the average goals per game for A, B, C, D are a, b, c and d we then have
      #
      #     54 == 5.(a + b) + 4.(c + d) 
      #
      # (since A and B play five games whereas C and D play four). Here the possible
      # solutions (a + b, c + d) = (10, 1), (6, 6) or (2, 11).
       
      # Since the maximum goals a team can score in any game is 5, all the averages
      # are five or less. And, since the averages are all different, a + b and c + d
      # are both less than 10 which means that a + b == c + d == 6. Moreover, since
      # A wins all their games, other teams cannot score 5 goals in all their games
      # and must have goals per match averages of less thsn five.
      #
      # Hence the possibilities are:
      #
      #      (a, b) = (5, 1), (4, 2), (2, 4)
      #      (c, d) = (4, 2), (2, 4)
      #
      # And since the values are all different 
      #
      #      (a, b, c, d) =  (5, 1, 4, 2) or (5, 1, 2, 4)
      #
      #
      # Hence in goal totals (A, B, C, D) = (25, 5, 16, 8) or (25, 5, 8, 16)
      #
      #
      # In the knockout 1st round game BvD team B can score a maximum of 4 goals
      # and we have B - D >= 2. Team D can only score a maximum of 2 goals in this 
      # knockout round.
      # This means the average of team D cannot be 4 (as it would require at least 
      # 2 wins with 5 goals). So the averages of team C and D are 2 resp. 4.
      # In all games of team C they score at least 3 goals.
      
      (H, I, J, U, Y, A, B, C, D) = (5, 5, 5, 5, 5, 5, 1, 4, 2)
      (B5, C4, D4) = (5 * B, 4 * C, 4 * D)
      
      # tournament:
      #
      #     A B C D
      #  A: - H I J  (A wins all matches)
      #  B: K - L M
      #  C: N P - Q
      #  D: R S T -
      #
      # knockout:
      #
      #  round 1: A v C = U - V (A wins); B v D = W - X (B wins)
      #  final: A v B = Y - Z (A wins)
      #
      
      # inner loops
      def inner(Q, T):
        # team A wins all their games scoring 5 goals in each game
        if 5 in {Q, T} and (Q, T) != (5, 5): return
        for X in range(3):                      # X != 3 as W < 5
          for R in range(3):                    # as N and V (team C) use 3 and 4
            S = D4 - (R + T + X)             # team D
            if not (0 <= S <= 4): continue      # S != 5 as M <= 3
            for N, V in permutations(range(3, 5), 2): # team C goals
              P = C4 - (N + Q + V)           # team C
              if not (3 <= P <= 4): continue    # P != 5 als L <= 3
              for W in range(X + 2, 5):         # B beat D by more than one goal
                # K + N + R + V + Z = 10  and  K + L + M + W + Z = 5
                for L in range((LM := (V + N + R) - W - 5) + 1):       
                  M = LM - L
                  
                  # different games
                  if not diffgames([L, M, Q, W], [P, S, T, X]): continue
      
                  for Z in range(6 - W - L - M):  # K,L,M,Z <= 3 as W >= 2
                    if Z in {V, R, N}: continue
                    K = B5 - (L + M + W + Z) # team B
                    if not (0 <= K <= 3) or K in {V, R, N, Z}: continue
      
                    if H+I+J+K+L+M+N+P+Q+R+S+T+U+V+W+X+Y+Z != 54: continue
                    return str((Q, T))
      
      sols = []
      # process scores CvD, team C scores at least 3 goals in each game
      for Q, T in product(range(3, 6), range(6)):
        if (s := inner(Q, T)):
          sols.append(s)
      
      print("answer(s):", ' or '.join(sols))   
      

      Like

  • Unknown's avatar

    Jim Randell 3:50 pm on 28 March 2025 Permalink | Reply
    Tags:   

    Teaser 2563: Prime time 

    From The Sunday Times, 6th November 2011 [link] [link]

    In this addition sum I have consistently replaced digits by letters, with different letters for different digits, and I have ended up with another correct addition sum.

    What is the prime number NOW?

    [teaser2563]

     
    • Jim Randell's avatar

      Jim Randell 3:51 pm on 28 March 2025 Permalink | Reply

      We can use the [[ SubstitutedExpression.split_sum ]] solver from the enigma.py library to solve this puzzle.

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      "ONE + TWO + FIVE = EIGHT"
      
      --extra="is_prime(NOW)"
      --answer="NOW"
      

      Solution: NOW = 467.

      Like

    • Ruud's avatar

      Ruud 9:17 am on 29 March 2025 Permalink | Reply

      import istr
      
      
      for o, n, e, t, w, f, i, v, g, h in istr.permutations(range(10)):
          if (o | n | e) + (t | w | o) + (f | i | v | e) == (e | i | g | h | t) and (n | o | w).is_prime():
              print("now =", n | o | w)
      

      Like

    • GeoffR's avatar

      GeoffR 10:12 am on 29 March 2025 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      %      O N E
      %      T W O
      %    F I V E
      %  ---------
      %  E I G H T
      %  ---------
      %     c3c2c1
       
      var 1..9:O; var 1..9:N; var 1..9:E; var 1..9:T; var 0..9:W; 
      var 1..9:F; var 0..9:I; var 0..9:V; var 0..9:G; var 0..9:H;
      var 0..2:c1; var 0..2:c2; var 0..2:c3;
      var 100..999:NOW = 100*N + 10*O + W;
      
      constraint E == 1;
      
      constraint all_different([O, N, E, T, W, F, I, V, G, H]);
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      constraint (E + O + E ) mod 10 == T 
      /\ (E + O + E) div 10 == c1;
      
      constraint (N + W + V + c1) mod 10 == H 
      /\ (N + W + V + c1) div 10 == c2;
      
      constraint (O + T + I + c2) mod 10 == G 
      /\ (O + T + I + c2) div 10 == c3;
      
      constraint (F + c3) mod 10 == I /\ (F + c3) div 10 == E; 
      
      constraint is_prime(NOW);
      
      solve satisfy;
      output[ "NOW = " ++ show(NOW)];
      
      % NOW = 467
      % ----------
      % ==========  
         
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:39 am on 25 March 2025 Permalink | Reply
    Tags:   

    Teaser 2559: Inconsequential 

    From The Sunday Times, 9th October 2011 [link] [link]

    Most whole numbers can be expressed as the sum of two or more consecutive whole numbers. For example:

    35 = 17 + 18
    36 = 11 + 12 + 13
    40 = 6 + 7 + 8 + 9 + 10

    I have written down two whole numbers. Between them they use each of the digits 0 to 9 exactly once. But neither of the numbers can be expressed as the sum of two or more consecutive whole numbers.

    What are my two numbers?

    [teaser2559]

     
    • Jim Randell's avatar

      Jim Randell 8:39 am on 25 March 2025 Permalink | Reply

      I am assuming that by “whole numbers” the setter means the positive integers.

      We have encountered the polite numbers [@wikipedia] before, see: BrainTwister #43, Enigma 776, Teaser 2994, Enigma 914, Enigma 1231, Enigma 1.

      The “impolite” numbers are the powers of 2, so we need to find two powers of 2 that between them use each of the 10 digits exactly once.

      This Python program runs in 62ms. (Internal runtime is 134µs).

      from enigma import (distinct_chars, printf)
      
      # record powers of 2 (as strings)
      ps = list()
      n = 1
      while True:
        s = str(n)
        if len(s) > 9: break
        # look for a pair that use all 10 digits between them
        for x in ps:
          if distinct_chars(x, s) == 10:
            printf("{x}, {n}")
        ps.append(s)
        n *= 2
      

      Solution: The numbers are: 4, 536870912.

      Where:

      2^2 = 4
      2^29 = 536870912

      Like

  • Unknown's avatar

    Jim Randell 7:44 am on 21 March 2025 Permalink | Reply
    Tags:   

    Teaser 2564: 11/11/11 

    From The Sunday Times, 13th November 2011 [link] [link]

    As it was 11/11/11 last Friday, I have been looking at numbers that are divisible by 11.

    I have written down an eight-figure number using the digits 2, 3, 4, 5, 6, 7, 8 and 9 in some order. For any two adjacent digits in the number, either: one is a multiple of the other; or: the two digits differ by one.

    My number is divisible by its first digit, its last digit, and (of course) by 11.

    What is the eight-figure number?

    [teaser2564]

     
    • Jim Randell's avatar

      Jim Randell 7:44 am on 21 March 2025 Permalink | Reply

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # suppose the 8-digit number is: ABCDEFGH
      --digits="2-9"
      
      # for any adjacent digits, either:
      # - one is a multiple of the other; or:
      # - the two digits differ by 1
      --code="f = lambda a, b: a % b == 0 or b % a == 0 or abs(a - b) == 1"
      "f(A, B)" "f(B, C)" "f(C, D)" "f(D, E)" "f(E, F)" "f(F, G)" "f(G, H)"
      
      # the number is divisible by A, H and 11
      "ABCDEFGH % A = 0"
      "ABCDEFGH % H = 0"
      "ABCDEFGH % 11 = 0"
      
      --answer="ABCDEFGH"
      --verbose="A"
      

      Solution: The number is: 76398245.

      Like

    • Frits's avatar

      Frits 3:33 am on 22 March 2025 Permalink | Reply

      from functools import reduce
      
      # convert digit list to number
      d2n = lambda s: reduce(lambda x,  y: 10 * x + y, s) 
      
      # check if digit <n> can be adjacent to digit <a> 
      chk = lambda n, a: n % a == 0 or a % n == 0 or abs(a - n) == 1
      
      # dictionary of possible neighbours
      adj = {i: [j for j in range(2, 10) if j != i and 
                 chk(i, j)] for i in range(2, 10)}
      
      # generate valid sequences a, b, c, d, e, f, g, h
      def solve(dgts, k, ss):
        # do we know a, b, c, d, e, f?
        if k == 2:
          # N = sum(2..9), remaining even sum: e = N - (o := (a + c + e + g))
          # divisibility by 11 rule: (o - e) % 11 = 0 or (2.o - 44) % 11 = 0
          g = 22 - ss[0] - ss[2] - ss[4]
          if not (g in adj[ss[-1]] and g in dgts): return
          h = dgts.difference([g]).pop()
          # divisibility by 3 rule (as sum(2..9) = 44)
          if h in {3, 6, 9} or not h in adj[g]: return
          yield ss + [g, h]
        else:  
          for n in adj[ss[-1]]:
            if n not in ss:
              yield from solve(dgts - {n}, k - 1, ss + [n])
      
      # the solution has to start or end with 5 or 7
      # (if both are in the middle then either 3 or 9 is at an end)      
      for f in (5, 7):
        for s in solve(set(range(2, 10)) - {f}, 7, [f]):
          # add reverse if necessary
          rng = (s, s[::-1]) if s[-1] not in {5, 7} else (s, )
          for s_ in rng:
            abcdefgh = d2n(s_)
            if abcdefgh % s_[0]: continue
            if abcdefgh % s_[-1]: continue
            print("answer:", abcdefgh)      
      

      Like

  • Unknown's avatar

    Jim Randell 7:29 am on 14 March 2025 Permalink | Reply
    Tags:   

    Teaser 2568: Ending 2011 

    From The Sunday Times, 11th December 2011 [link] [link]

    I have written down three positive whole numbers consisting of two primes and a perfect square. Between them they use nine digits and no digit is repeated.

    If you form an addition sum with the three numbers, then what do you end up with as the total? Appropriately enough you end up with 2011.

    What, in increasing order, are the three numbers?

    [teaser2568]

     
    • Jim Randell's avatar

      Jim Randell 7:30 am on 14 March 2025 Permalink | Reply

      This Python program looks at possible squares (without repeated digits), and then checks to see if the remainder can be made from the sum of two primes.

      The year to be made can be specified on the command line.

      It runs in 62ms. (Internal runtime is 3.4ms).

      from enigma import (powers, inf, primes, distinct_chars, arg, printf)
      
      # total to find
      N = arg(2011, 0, int)
      
      # consider possible squares
      for s in powers(1, inf, 2):
        r = N - s
        if r < 5: break
        if distinct_chars(s) is None: continue
      
        # look for pairs of primes that sum to r
        for p in primes.between(2, r // 2):
          q = r - p
          # q is also a prime
          if q not in primes: continue
          # check for 9 different digits
          if distinct_chars(p, q, s) != 9: continue
          # output solution
          printf("{ns} -> {N} [primes = {p}, {q}; square = {s}]", ns=sorted([p, q, s]))
      

      Solution: The numbers are: 263, 841, 907.

      263 and 907 are prime, and 841 = 29^2.


      We could be a bit cleverer with finding pairs of primes that sum to r. For instance, if r is odd, then one of the primes would have to be 2. But it makes the code a bit longer, and the version above is already fast enough, so I haven’t included it.

      Like

    • Frits's avatar

      Frits 5:05 am on 15 March 2025 Permalink | Reply

      Designed for a low internal run time.

      from itertools import compress
      
      # total to find
      N = 2011
      
      # 2 < primes < n 
      def primesbelow(n):
        # rwh_primes1v2(n):
        """ Returns a list of 2 < 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 {*compress(range(3, n, 2), sieve[1:])}
      
      P = primesbelow(1885)  # max prime is 2011 - 23 - 104
      
      # split number <n> in two different odd prime endings, unequal to <o>
      split = lambda n, o: [(i, i2) for i in range(1, 11, 2) 
                           if (i2 := (10 + n - i) % 10) > i and
                           {i, i2}.isdisjoint([5, o])]
      
      # odd prime number endings per odd square number ending
      pes = {se: split(11 - se, se) for se in [1, 5, 9] for i in range(1, 10, 2)}
      pes = {k: {x for v in vs for x in v} for k, vs in pes.items()}
      
      # all three numbers must be odd as 2 + abcd + efgh > 4000
      
      # consider odd possible squares (with at least 2 digits)
      for n in range(5, 45, 2):
        r = N - (sq := n * n)
        # square ending must allow for valid prime endings
        if not (se := pes[sq % 10]): continue
        if len(set(s := str(sq))) != len(s): continue
        
        # look for pairs of odd primes that sum to r 
        for p in sorted(P):
          # a 2-digit square requires a prime of 1xxx
          if '1' in s and len(s) == 2: break
          if p > r // 2: break
          # valid prime number ending
          if (p % 10) not in se: continue
      
          q = r - p
          # q is also a prime, no repeated digits
          if q not in P: continue
          if len(set(s1 := s + str(p) + str(q))) == len(s1) == 9: 
            # output solution
            print("answer:", sorted([sq, p, q]))
      

      Like

    • ruudvanderham's avatar

      ruudvanderham 11:31 am on 15 March 2025 Permalink | Reply

      import istr
      
      primes = [n for n in istr(range(100, 1000)) if n.is_prime() and n.all_distinct()]
      squares = [n * n for n in istr(range(10, 32)) if (n * n).all_distinct()]
      
      for (n1, n2), n3 in istr.product(istr.combinations(primes, r=2), squares):
          if (n1 | n2 | n3).all_distinct() and n1 + n2 + n3 == 2011:
              print(sorted((n1, n2, n3)))
      

      Like

      • Jim Randell's avatar

        Jim Randell 5:01 pm on 15 March 2025 Permalink | Reply

        @Ruud: Note that it is not specified that the three numbers are all 3-digit numbers (although it turns out that the numbers in the solution are).

        But if the puzzle had been set in 1990, the solution would not be three 3-digit numbers:

        % python3 teaser2568.py 1990
        [59, 324, 1607] -> 1990 [primes = 59, 1607; square = 324]
        

        Like

        • Ruud's avatar

          Ruud 5:13 pm on 15 March 2025 Permalink | Reply

          @Jim
          You are right. I had misread the problem description.
          The code below does not assume that the numbers have three digits.

          import istr
          
          primes = [n for n in istr(range(1, 2011)) if n.is_prime() and n.all_distinct()]
          squares = [n * n for n in istr(range(1, 45)) if (n * n).all_distinct()]
          
          for (n1, n2), n3 in istr.product(istr.combinations(primes, r=2), squares):
              if (n1 | n2 | n3).all_distinct() and n1 + n2 + n3 == 2011:
                  print(sorted((n1, n2, n3)))
          

          Like

          • Frits's avatar

            Frits 2:53 am on 16 March 2025 Permalink | Reply

            @Ruud, you would have reported three solutions for year 1990 .

            “Between them they use nine digits” means exactly nine digits.

            Like

    • Frits's avatar

      Frits 4:21 am on 16 March 2025 Permalink | Reply

      712 is the first year that there is a solution.
      Up to year 2011 there are 454 solutions.
      The program runs in 163ms but is not valid for years a little higher than 2011.

      from itertools import compress
      
      # 2 < primes < n 
      def primesbelow(n):
        # rwh_primes1v2(n):
        """ Returns a list of 2 < 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 {*compress(range(3, n, 2), sieve[1:])}
      
      M = 2011
      # max prime is N - 23 - 104
      P = {p for p in primesbelow(M - 126) if len(set(s := str(p))) == len(s)}
      P2 = sorted(p for p in P if p < 100)
      P3 = sorted(p for p in P if 100 <= p < 1000)
      
      c = 0
      for N in range(1, M + 1):
        # consider possible squares (with at least 2 digits)
        for n in range(4 + (N % 2), int(N**.5) + 1, 2):
          r = N - (sq := n * n)
          if r < 120: break # at least a 2-digit and a 3-digit prime 
          if len(set(s := str(sq))) != (ln := len(s)): continue
          
          # determine which primes to use
          # consider the number of digits in the square number
          # 2 : p + q = 7 so 3 and 4
          # 3 : p + q = 6 so 2 and 4  or  3 and 3
          # 4 : p + q = 5 so 2 and 3
          plst = []
          if ln != 2:
            # add list of 2-digit primes
            plst.append([] if '1' in s and ln == 3 else P2)
          if ln != 4:    
            # add list of 3-digit primes
            plst.append([p for p in P3 if r - p < 1000] if ln == 3 else 
                        [] if '1' in s else P3)
          
          for prms in plst:   
            # look for pairs of primes that sum to r
            for p in prms: 
              q = r - p
              if q < p: break
              # q is also a prime, no repeated digits
              if q not in P: continue
              # between them they use nine digits
              if len(set(s1 := s + str(p) + str(q))) == len(s1) == 9: 
                c += 1
                # output solution
                print(c, f"{N = }", "answer:", sorted([sq, p, q])) 
      

      Like

  • Unknown's avatar

    Jim Randell 10:23 am on 11 March 2025 Permalink | Reply
    Tags:   

    Teaser 2556: Dotty squares 

    From The Sunday Times, 18th September 2011 [link] [link]

    On a piece of paper I have drawn a neat, evenly spaced square array of 36 dots, namely six rows of six.

    If I were to ask you how many squares are formed, you might say just 25, for the array seems to consist of 25 little squares. However, lots of sets of four of the dots form the vertices of a square.

    How many ways are there of choosing four of the dots so that they form the vertices of a square?

    [teaser2556]

     
    • Jim Randell's avatar

      Jim Randell 10:25 am on 11 March 2025 Permalink | Reply

      See: Enigma 1723.

      The number of possible squares on an n × n grid of dots is given by:

      S(n) = n²(n² − 1)/12

      See: OEIS A002415.

      So, with a 6×6 grid of dots:

      S(6) = 36 × 35 / 12 = 105

      Solution: There are 105 sets of 4 points that will make a square.


      Here is a constructive program using some routines from the enigma.py library.

      It runs the N=6 case in 74ms. (Internal runtime is 5.6ms).

      from enigma import (irange, subsets, line_bisect, rdiv, arg, printf)
      
      # number of dots on each side of the square
      N = arg(6, 0, int)
      
      # co-ordinates of the dots [0 .. N-1]
      dots = sorted(subsets(irange(N), size=2, select='M'))
      
      # choose 2 of the dots (will be ordered)
      n = 0
      for (a, b) in subsets(dots, size=2):
        # find the vertices of the other diagonal
        (c, d) = sorted(line_bisect(a, b, div=rdiv))
        if not (c in dots and d in dots): continue
        # eliminate duplicates (otherwise each square will appear twice)
        if (c, d) < (a, b): continue
        # output square
        n += 1
        printf("[{n}: ({a}, {b}) -> ({c}, {d})]")
      
      # output total
      printf("N={N}: {n} squares")
      

      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