Updates from July, 2025 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 7:20 am on 20 July 2025 Permalink | Reply
    Tags:   

    Teaser 3278: Page weight 

    From The Sunday Times, 20th July 2025 [link] [link]

    An A5 booklet is produced by printing the pages onto sheets of A4 (both sides), then binding and folding along the middle. The booklet contains two chapters. The longer first chapter runs to 34 pages.

    The pages in each chapter are numbered sequentially from 1 upwards. The front cover is page 1 of chapter one. Page 1 of chapter two follows directly after page 34 of chapter one. Any blank pages at the end of the booklet are not numbered.

    Without changing their order, the sheets can be split into two piles, where the sums of the page numbers in each pile are equal.

    How many pages does the second chapter have?

    [teaser3278]

     
    • Jim Randell's avatar

      Jim Randell 7:52 am on 20 July 2025 Permalink | Reply

      Although not explicitly stated, I am assuming the booklet consists of the minimum number of A4 sheets required to fit all pages of both chapters.

      Here is a constructive solution, that constructs the pairs of pages and organises them into A4 sheets, and then looks for a scenario where the sheets can be split as required. (This allows easy output of the page numbers on each A4 sheet, if desired).

      The following Python program runs in 60ms. (Internal runtime is 539µs).

      Run: [ @codepad ]

      from enigma import (irange, divc, div, chunk, chain, printf)
      
      # pages for chapter 1
      n1 = 34
      
      # consider the number of pages in chapter 2 (< n1)
      for n2 in irange(1, n1 - 1):
      
        # pair up the pages
        pairs = list(chunk(chain(irange(1, n1), irange(1, n2)), 2))
        # total sum of all page numbers divided by 2
        s = div(sum(map(sum, pairs)), 2)
        if s is None: continue
      
        # total number sheets of A4 required
        t = divc(n1 + n2, 4)
      
        # collect the page numbers for each sheet
        sheets = [()] * t
        for (i, p) in zip(chain(irange(t), irange(t, step=-1)), pairs):
          sheets[i] += p
      
        # can we find some sheets that sum to s ?
        x = 0
        for k in irange(t):
          x += sum(sheets[k])
          if x == s:
            # output solution
            printf("chapter 2 has {n2} pages [{t} sheets in total; first {k} sheets sum to {x}]", k=k + 1)
          if x >= s:
            break
      

      Solution: Chapter 2 has 25 pages.

      There are 15 A4 sheets, and the page numbers are as follows:

      1: (1, 2) (25, -)
      2: (3, 4) (23, 24)
      3: (5, 6) (21, 22)
      4: (7, 8) (19, 20)
      5: (9, 10) (17, 18)
      6: (11, 12) (15, 16)
      7: (13, 14) (13, 14)
      8: (15, 16) (11, 12)
      9: (17, 18) (9, 10)

      10: (19, 20) (7, 8)
      11: (21, 22) (5, 6)
      12: (23, 24) (3, 4)
      13: (25, 26) (1, 2)
      14: (27, 28) (33, 34)
      15: (29, 30) (31, 32)

      The sum of the page numbers of sheets 1-9 and sheets 10-15 both come to 460.


      If we can construct the booklet with more than the minimum required number of A4 sheets, then it is possible to construct a further solution:

      If chapter 2 has 14 pages and the booklet was printed from 17 sheets, then the sum of the page numbers on sheets 1-12 and sheets 13-17 would both come to 350 (and there would be 20 blank pages at the end of the booklet).

      This justifies the initial assumption.


      The program can be used to investigate similar puzzles where the number of pages in the first chapter is other than 34.

      An interesting case is when chapter 1 has 19 pages. There are then 2 solutions:

      Chapter 2 has 4 pages.
      The booklet consists of 6 sheets.
      The first 4 (and last 2) sheets have page numbers which sum to 100.

      Chapter 2 has 16 pages.
      The booklet consists of 9 sheets.
      The first 5 (and last 4) sheets have page numbers which sum to 163.

      Like

      • Jim Randell's avatar

        Jim Randell 9:15 am on 20 July 2025 Permalink | Reply

        Here is a faster version of my program that works with a list of the sum of pairs of pages on a leaf.

        It has an internal runtime of 124µs.

        from enigma import (irange, chunk, div, divc, seq_get, printf)
        
        # pages for chapter 1
        n1 = 34
        
        # page pair sums from chapter 1
        pairs = list(map(sum, chunk(irange(1, n1), 2)))
        t = sum(pairs)
        get = seq_get(pairs, default=0)
        
        # consider the number of pages in chapter 2 (< n1)
        for (i, n2) in enumerate(irange(1, n1 - 1), start=n1):
          # make the pair sum of the final page
          if i % 2:
            pairs[-1] += n2
          else:
            pairs.append(n2)
          t += n2
          s = div(t, 2)
          if s is None: continue
        
          # now look for sheets that sum to s, by pairing up page sums
          (x, k) = (0, len(pairs))
          if k % 2 == 0: k -= 1
          for i in irange(divc(k, 2)):
            x += get(i) + get(k - i)
            if x == s:
              # output solution
              printf("chapter 2 has {n2} pages [first {i} sheets sum to {x}]", i=i + 1)
            if x >= s:
              break
        

        Like

    • Frits's avatar

      Frits 2:45 pm on 20 July 2025 Permalink | Reply

      One loop.

      The sum of 4 page numbers per sheet can only have 2, 3 or 4 different values (per number of pages in chapter 2).

      It took a while before I figured out how the booklet was constructed.

      # one folded A4 sheet results in 4 pages
      
      t = 17 * 35        # sum(1...34)
      # number of pages in chapter 2
      for n in range(1, 34):
        # sum of all pages
        t += n
        # both <isum> as <osum> are even so <h> must also be even
        if t % 4: continue
        h = t // 2   
        
        n_sheets = (37 + n) // 4
        n_inner_sheets = 17 - n_sheets
        
        # calculate starting page number of most inner sheet
        st = 2 * ((n + 1) // 4) + 17
        
        # calculate sum of 4 outer sheet page numbers (except most outer sheet)
        osum = (2 * (n + n % 2) - 1) + 3 if n > 1 else -1
        # calculate sum of most inner sheet page numbers
        isum = 4 * st + 6 if n_sheets < 17 else osum
        
        # can we make <h> using <k> sheets (beginning from the center)?
       
        # h = a.isum + b.osum or  b = (h - a.isum) / osum
        # use maximum <n_inner_sheets> inner sheets and possible <b> outer sheets
        d, r = divmod(h, isum)
        if not r and d <= n_inner_sheets:
          print("answer:", n)
        else:  
          # h - n_inner_sheets * inner = b * osum
          b, r = divmod(h - n_inner_sheets * isum, osum)
          if not r and 0 < b <= n_sheets - n_inner_sheets:
            if osum > 0: 
              print("answer:", n)
      

      Like

  • Unknown's avatar

    Jim Randell 8:19 am on 18 July 2025 Permalink | Reply
    Tags:   

    Teaser 2518: [Empty boxes] 

    From The Sunday Times, 26th December 2010 [link] [link]

    For an appropriate Boxing Day exercise, write a digit in each of the empty boxes so that (counting all the digits from here to the final request) the following statements are true:

    Number of occurrences of 0 = [ _ ]
    Number of occurrences of 1 = [ _ ]
    Total occurrences of 2s and 3s = [ _ ]
    Total occurrences of 4s and 5s = [ _ ]
    Total occurrences of 6s and 7s = [ _ ]
    Total occurrences of 8s and 9s = [ _ ]
    A digit you have written in another box = [ _ ]
    The average of all your written digits = [ _ ]

    What, in order, are the digits in your boxes?

    This puzzle was originally published with no title.

    [teaser2518]

     
    • Jim Randell's avatar

      Jim Randell 8:20 am on 18 July 2025 Permalink | Reply

      There are 8 boxes to be filled out with a digit (from 0 to 9), and each of the digits 0 to 9 already occurs once before the boxes are filled out.

      The first six boxes count the 10 given digits, plus those in the 8 boxes, so must sum to 18.

      The first 2 boxes must contain 1 or more, and the next 4 boxes must contain 2 or more.

      And the box with the mean must also be 2 or more.

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

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

      Run: [ @codepad ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # let the 8 empty boxes be: A B C D E F G H
      --distinct=""
      --digits="1-9"
      --invalid="1,CDEFH"
      --macro="@boxes = [A, B, C, D, E, F, G, H]"
      
      # first 2 boxes
      "@boxes.count(0) + 1 = A"
      "@boxes.count(1) + 1 = B"
      
      # next 4 boxes
      "@boxes.count(2) + @boxes.count(3) + 2 = C"
      "@boxes.count(4) + @boxes.count(5) + 2 = D"
      "@boxes.count(6) + @boxes.count(7) + 2 = E"
      "@boxes.count(8) + @boxes.count(9) + 2 = F"
      
      # final 2 boxes
      "G in [A, B, C, D, E, F, H]"
      "iavg(@boxes) = H"
      
      --template="@boxes"
      --solution=""
      
      # [optional] additional constraints to improve run time
      # the sum of the first 6 boxes must be 18
      "A + B + C + D + E + F = 18"
      # we can't write 0 in any of the boxes, so there is only 1 zero
      --assign="A,1"
      

      Solution: The digits are: 1, 2, 8, 2, 2, 3, 3, 3.

      The statements then become:

      Number of occurrences of 0 = [ 1 ]
      Number of occurrences of 1 = [ 2 ]
      Total occurrences of 2s and 3s = [ 8 ]
      Total occurrences of 4s and 5s = [ 2 ]
      Total occurrences of 6s and 7s = [ 2 ]
      Total occurrences of 8s and 9s = [ 3 ]
      A digit you have written in another box = [ 3 ]
      The average of all your written digits = [ 3 ]

      Like

      • Frits's avatar

        Frits 2:04 pm on 18 July 2025 Permalink | Reply

        @Jim, you can also add B in line 8 (ao because of line 32).

        Like

  • Unknown's avatar

    Jim Randell 8:21 am on 15 July 2025 Permalink | Reply
    Tags:   

    Teaser 2489: [Digit circle] 

    From The Sunday Times, 6th June 2010 [link] [link]

    Your task today is to put more than two digits evenly spaced around a circle so that:

    (a) Any three adjacent digits clockwise form a prime number.

    (b) Any three adjacent digits anti-clockwise form a prime number.

    (c) If a digit occurs in the circle, then it occurs a prime number of times.

    (d) The total of all the digits around the circle is a prime.

    What is the total of all the digits?

    This puzzle was originally published with no title.

    [teaser2489]

     
    • Jim Randell's avatar

      Jim Randell 8:21 am on 15 July 2025 Permalink | Reply

      Each digit is the last digit of a 3-digit prime, so the only possible digits are 1, 3, 9, 7.

      This Python program looks for solutions using increasing numbers of digits, and stops after the smallest possible sequences are found.

      It runs in 60ms. (Internal runtime is 841µs).

      Run: [ @codepad ]

      from enigma import (irange, inf, primes, multiset, subsets, tuples, rev, nconcat, wrap, uniq, printf)
      
      # up to 3-digit primes
      primes.expand(999)
      
      # possible digits
      digits = [1, 3, 7, 9]
      
      # generate numbers from <k> adjacent digits of <ns> (in both directions)
      @wrap(uniq)  # skip duplicate numbers
      def adj(ns, k=3):
        for ds in tuples(ns, k, circular=1):
          yield nconcat(ds)
          yield nconcat(rev(ds))
      
      # look for solutions using <n> digits
      def solve(n):
        # choose the n digits
        for ds in subsets(digits, size=n, select='R'):
          # the sum of the digits is prime
          if not (sum(ds) in primes): continue
          # each digit occurs a prime number of times
          m = multiset.from_seq(ds)
          if not all(v in primes for v in m.values()): continue
      
          # start with the smallest number
          n0 = m.min()
          # and choose an arrangement of the rest
          for ns in subsets(m.difference([n0]), size=len, select='mP', fn=list):
            if rev(ns) < ns: continue  # skip duplicate solutions
            ns.insert(0, n0)
            # check each 3 adjacent digits form a prime (in both directions)
            if not all(x in primes for x in adj(ns)): continue
            yield ns
      
      # find the smallest possible sequences
      for n in irange(3, inf):
        s = 0
        for ns in solve(n):
          printf("[n={n}] {ns} -> {t}", t=sum(ns))
          s += 1
        if s: break
      

      Solution: The total of the digits is 29.

      The digits in the circle are: (1, 9, 1, 9, 9). Consisting of 2 occurrences of the digit 1, and 3 occurrences of the digit 9.

      The 3-digit numbers formed are: 191, 199, 919, 991. Which are all prime.

      Like

    • Ruud's avatar

      Ruud 8:03 am on 16 July 2025 Permalink | Reply

      import istr
      import collections
      import itertools
      
      primes = [i for i in istr.range(2, 1000) if i.is_prime()]
      candidates = {str(prime) for prime in primes if len(prime) == 3 and all(c in "1379" for c in prime)}
      for n in itertools.count(2):
          for x in itertools.product("1379", repeat=int(n)):
              s = "".join(x)
              if all(count in primes for count in collections.Counter(s).values()):
                  s2 = str(s + s[0:2])
                  if (
                      all(s2[i : i + 3] in candidates for i in range(len(s)))
                      and all(s2[i : i + 3][::-1] in candidates for i in range(len(s)))
                      and (sumx:=sum(map(int, x))) in primes
                  ):
                      print(s,sumx)
                      assert False
      

      Like

  • Unknown's avatar

    Jim Randell 6:24 am on 13 July 2025 Permalink | Reply
    Tags: ,   

    Teaser 3277: Croquet equipment 

    From The Sunday Times, 13th July 2025 [link] [link]

    Our non-standard croquet lawn has six hoops, at positions A to F, and a central peg at P. The equipment storage box is in the southwest corner at S, and the lawn is symmetrical in both the east-west and north-south directions. The diagram is not to scale, but D is south of the projected line from S to E.

    Also AB is half the width of the lawn. When setting out the equipment, I walk along the route SEFDPCBAS. All of the distances on my route between positions are whole numbers of feet (less than 60), and both D and E are whole numbers of feet north of the south boundary.

    What is the total distance of my route?

    [teaser3277]

     
    • Jim Randell's avatar

      Jim Randell 8:46 am on 13 July 2025 Permalink | Reply

      I get two solutions, so here is a straightforward solution to make sure I haven’t missed any thing.

      But if we disallow distances of 60ft or more between any two positions on the walk (not just adjacent positions), then we can eliminate the larger of these solutions.

      This Python program runs in 65ms. (Internal runtime is 5ms).

      from enigma import (irange, ihypot, printf)
      
      M = 59  # max length of a path segment
      
      # let AB = EF = 2x
      for x in irange(1, M // 2):
        # e = distance E north from southern edge
        for e in irange(1, M):
          SE = ihypot(x, e)
          if SE is None or SE > M: continue
      
          # d = distance D is north of EF
          for d in irange(1, e - 1):
            FD = ihypot(x, d)
            if FD is None or FD > M: continue
      
            # p = distance P north of D
            for p in irange(1, M):
              SA = ihypot(x, e + d + p + p + d)
              if SA is None or SA > M: continue
      
              # total distance walked
              T = SE + SA + 2*FD + 4*x + 2*p
              printf("T={T} [SE={SE} FD={FD} SA={SA}; x={x} e={e} d={d} p={p}]")
      

      Solution: There are two viable solutions to the puzzle: 142 ft, and 220 ft.

      Apparently the size of a croquet lawn is not fixed, but it should be a rectangle with sides in the ratio of 1.25.

      The first of the solutions involves a playing area of 48 ft × 44 ft (ratio = 1.09).

      And all positions on the route are within 60 ft of S (as indicated by the dotted line).

      The second of the solutions involves a playing area of 96 ft × 42 ft (ratio = 2.29).

      And B and F are further than 60 ft from S.


      The published solution is: “142 feet”.

      But a correction was issued with Teaser 3280:

      There were in fact two possible answers: 142 and 220 feet.
      Either was accepted as correct.

      .

      Like

      • Frits's avatar

        Frits 11:38 am on 13 July 2025 Permalink | Reply

        @Jim, shouldn’t we have: e + d + p = 2.x ?

        Like

      • Jim Randell's avatar

        Jim Randell 4:19 pm on 13 July 2025 Permalink | Reply

        Alternatively, using the [[ pythagorean_triples() ]] function from the enigma.py library.

        The following Python program has an internal runtime of 122µs.

        from enigma import (defaultdict, pythagorean_triples, subsets, div, printf)
        
        M = 59  # maximum length of a path segment
        
        # collect (<other>, <hypot>) sides for pythagorean triples
        t = defaultdict(list)
        for (x, y, z) in pythagorean_triples(M):
          t[x].append((y, z))
          t[y].append((x, z))
        
        # consider possible x values
        for x in sorted(t.keys()):
          if 2 * x > M: break
          # FD, SE, SA all have base x, and increasing vertical sides
          ts = sorted(t[x])
          for ((d, FD), (e, SE), (a, SA)) in subsets(ts, size=3):
            p = div(a - e - 2*d, 2)
            if p is None or p < 1: continue
        
            # total distance walked
            T = SE + SA + 2*FD + 4*x + 2*p
            printf("T={T} [SE={SE} FD={FD} SA={SA}; x={x} e={e} d={d} p={p}]")
        

        Like

    • Hugo's avatar

      Hugo 10:34 am on 21 July 2025 Permalink | Reply

      If hoop P has coordinates (0, 0) then it seems
      A is at (12, -13), B is at (12, 13)
      C is at (0, 8), D is at (0, -8)
      E is at (-12, -13), F is at (-12, 13)
      S is at (-24, -22).

      It would have been kind of them to tell us that all the distances, including x and y separations, are integers.

      Like

  • Unknown's avatar

    Jim Randell 10:12 am on 11 July 2025 Permalink | Reply
    Tags: ,   

    Teaser 2475: [Stopped clock] 

    From The Sunday Times, 28th February 2010 [link]

    “I see that our long case has stopped”, said Watson, who had been reading Holmes’s monograph on chronometers. “Is there anything significant about the time?” Holmes replied: “Apart from the obvious facts that just one hand is precisely on 12, and clockwise the hands are in the order “second” , “minute”, “hour”, I see that the time read as a 3- or 4-digit number is the product of two whole numbers, the larger of which is the number of minutes clockwise from the minute hand to the hour hand”.

    At what time did the clock stop?

    This puzzle was originally published with no title.

    [teaser2473]

     
    • Jim Randell's avatar

      Jim Randell 10:12 am on 11 July 2025 Permalink | Reply

      If one of the hands is on the 12, then the clock must be showing an exact number of minutes, and so the second hand must be on 12.

      Then as we go clockwise from the second hand we encounter the minute hand (which is on an exact minute marking), and then the hour hand (which must also be on an exact minute marking, so the number of minutes must be a multiple of 12).

      This Python program considers possible hours and minutes, calculates the number of minute divisions the hour hand is ahead of the minute hand and then checks that this divides into the time (read as a 3- or 4-digit number) to give a smaller number.

      It runs in 60ms. (Internal runtime is 46µs).

      from enigma import (irange, cproduct, divc, div, printf)
      
      # possible hours and minutes (must be a multiple of 12)
      for (h, m) in cproduct([irange(1, 11), irange(12, 59, step=12)]):
      
        # number of minutes pointed to by the hour hand
        p = (5 * h) + (m // 12)
        # minute divisions the hour hand is ahead of the minute hand
        d = p - m
        if not (d > 0): continue
      
        # time read as a 3- or 4-digit number
        n = 100 * h + m
        r = div(n, d)
        if r is None or not (d > r): continue
      
        # output solution
        printf("{h:02d}:{m:02d} -> {n} = {d} * {r}")
      

      Solution: The clock stopped at 8:12.

      The second hand points to 12 (= 0 minutes), the minute hand to 12 minutes, and the hour hand to 8 + 12/60 hours (= 41 minutes).

      The hour hand is 29 minute divisions ahead of the minute hand, and: 812 = 29 × 28.

      Like

  • Unknown's avatar

    Jim Randell 8:25 am on 8 July 2025 Permalink | Reply
    Tags: by: Bert Brooke   

    Brainteaser 1031: Definitive darts 

    From The Sunday Times, 2nd May 1982 [link]

    We of the Private Bar of the Wapping Boar are an exclusive elite in darting fraternity; albeit we play the conventional darts of commoner folk and employ their customary board and rules. Our two opposing players throw three darts each, in turn. We require that the last (winning) dart of any game must score some double or the bull (50) — and must thereby raise the player’s score precisely to 501.

    Last evening, two of our virtuosi played an epic exhibition; a series of games were all won by the first-to-throw and each game, required the least possible number of darts. The precision was faultless and no dart thrown by the eventual winner of any game ever exceeded the score of any preceding dart in that game. Later, our President eulogised and vouched that in the suite but recently enjoyed, every possible scoring sequence of winning games (within our definition) had been played just once.

    “What about my record?” interceded that parvenu from the “public” (one we import to manage the chalks and call the 3 dart total following each throw). The low fellow claims a maximum possible of top score shouts “One hundred and eighty!!” in a demonstration match such as we’d just had; he insists his feat be recognised. The oaf must have his due if we are to use him anew, but there’s the rub — who scores for a scorer?

    Readers, please say the maximum of correct “180” shouts to be credited in our circumstance.

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

    [teaser1031]

     
    • Jim Randell's avatar

      Jim Randell 8:25 am on 8 July 2025 Permalink | Reply

      It is not possible to score 501 in fewer than 9 darts, as 60 is the maximum possible score with a single dart, and 501 / 60 > 8.

      But it is possible to score 501 in 9 darts, as 501 = 3 × 167, and 167 = 60 (treble 20) + 57 (treble 19) + 50 (bull). So we can finish the 9 dart run on a bull.

      We need to find sequences of 9 darts, with scores in non-increasing order, that score a total of 501, and finish on a double.

      And this corresponds to the winners throws. The winner went first, so the loser in each match only threw 6 darts, and as the scorer shouted “180” the maximum possible number of times, the loser must have thrown 6×60 for 2 shouts of “180”.

      This Python 3 program runs in 67ms. (Internal runtime is 1.3ms).

      Run: [ @codepad ]

      from enigma import (irange, union, seq_items, chunk, printf)
      
      # possible scores with 3 darts
      single = union([irange(1, 20), [25]])
      double = set(2 * x for x in single)
      treble = set(3 * x for x in single if x != 25)
      darts = sorted(union([single, double, treble]), reverse=1)
      
      # score <t> in <k> darts (finishing with a double)
      # using non-increasing subsets of darts[i..]
      def score(t, k, i=0, ss=[]):
        x = darts[i]
        # finish on a double
        if k == 1:
          if t in double and not (x < t):
            yield ss + [t]
        elif not (x * k < t):
          # score the next dart
          for (j, x) in seq_items(darts, i):
            if x < t:
              yield from score(t - x, k - 1, j, ss + [x])
      
      # score 501 in 9 darts, count the number of matches, and "180" shouts
      n = s = 0
      for ss in score(501, 9):
        n += 1
        gs = list(chunk(ss, 3))
        x = sum(sum(g) == 180 for g in gs)
        s += x
        printf("[{n}: {gs}; {x} shouts]")
      
      # total number of shouts
      t = s + 2 * n
      printf("total shouts = {t} [from {n} matches]")
      

      Solution: There were 62 shouts of “180”.

      There were 18 matches. The winners scores as follows:

      1: [(60, 60, 60), (60, 60, 60), (60, 57, 24)]; 2 shouts
      2: [(60, 60, 60), (60, 60, 60), (60, 51, 30)]; 2 shouts
      3: [(60, 60, 60), (60, 60, 60), (60, 45, 36)]; 2 shouts
      4: [(60, 60, 60), (60, 60, 60), (57, 54, 30)]; 2 shouts
      5: [(60, 60, 60), (60, 60, 60), (57, 50, 34)]; 2 shouts
      6: [(60, 60, 60), (60, 60, 60), (57, 48, 36)]; 2 shouts
      7: [(60, 60, 60), (60, 60, 60), (54, 51, 36)]; 2 shouts
      8: [(60, 60, 60), (60, 60, 60), (51, 50, 40)]; 2 shouts
      9: [(60, 60, 60), (60, 60, 57), (57, 57, 30)]; 1 shout
      10: [(60, 60, 60), (60, 60, 57), (57, 51, 36)]; 1 shout
      11: [(60, 60, 60), (60, 60, 57), (54, 54, 36)]; 1 shout
      12: [(60, 60, 60), (60, 60, 57), (54, 50, 40)]; 1 shout
      13: [(60, 60, 60), (60, 60, 51), (50, 50, 50)]; 1 shout
      14: [(60, 60, 60), (60, 57, 57), (57, 54, 36)]; 1 shout
      15: [(60, 60, 60), (60, 57, 57), (57, 50, 40)]; 1 shout
      16: [(60, 60, 60), (60, 57, 54), (50, 50, 50)]; 1 shout
      17: [(60, 60, 60), (57, 57, 57), (57, 57, 36)]; 1 shout
      18: [(60, 60, 60), (57, 57, 57), (50, 50, 50)]; 1 shout

      This accounts for 26 of the shouts.

      The loser in each match accounts for another 2 shouts per match, giving 26 + 18×2 = 62 shouts in total.

      Like

      • Frits's avatar

        Frits 12:12 pm on 10 July 2025 Permalink | Reply

        @Jim, as “darts” is non-increasing the check in line 20 can be done outside the loop (if needed at all as x always seems to be less than t).

        Like

        • Jim Randell's avatar

          Jim Randell 12:36 pm on 11 July 2025 Permalink | Reply

          @Frits: Yes, we could skip any elements that are not less than the remaining total, and then just process all the remaining elements without testing (as they are in decreasing order). But I didn’t think it was worth the additional complication.

          But we do need the test, for example, if we wanted to find a 2 dart finish from 37 we would call [[ score(37, 2) ]].

          Like

    • Frits's avatar

      Frits 6:08 pm on 8 July 2025 Permalink | Reply

      N = 9 # number of throws needed
      
      # return a valid loop structure string, indent after every "for" statement
      def indent(s):
        res, ind = "", 0
        for ln in s:
          res += " " * ind + ln + "\n"
          if len(ln) > 2 and ln[:3] == "for":
            ind += 2
        return res  
      
      syms = "ABCDEFGHIJKLMNOPQR"
      throws = ["X"] + list(syms[:N])
      varstring = "[" + ', '.join(throws[1:]) + "]"
      # possible scores with 3 darts
      single = set(range(1, 21)) | {25}
      double = {2 * x for x in single}
      treble = {3 * x for x in single if x != 25}
      
      # determine mininum for the first 8 throws
      mn = 501 - (7 * max(treble) + max(double))
      # darts to be used for the first 8 throws
      darts = sorted([x for x in single | double | treble if x >= mn], reverse=True)
      
      # generate an execution string with 8 loops
      exprs = [f"X = {max(treble)}; cnt = 0"]           # X is a dummy variable
      for i, t in enumerate(throws[1:], 1):
        if i < N:
          exprs.append(f"for {t} in [x for x in {darts} if x <= {throws[i - 1]}]:") 
          if i < N - 1:
            exprs.append(f"if sum([{', '.join(throws[1:i + 1])}]) + "
                         f"{N - i - 1} * {throws[i]} + {max(double)} < 501: break")
        else:
          # most inner loop
          exprs.append(f"{throws[N]} = 501 - sum([{', '.join(throws[1:-1])}])")   
          exprs.append(f"if {throws[N]} > {throws[N - 1]}: break")    
          exprs.append(f"if not ({throws[N]} in {double}): continue")   
          # the loser in each game accounts for another 2 shouts per game
          exprs.append(f"cnt += sum({varstring}[3 * i:3 * (i + 1)] == "
                       f"[max(treble)] * 3 for i in range(3)) + 2")
          #exprs.append(f"print({varstring})")
             
      exprs = indent(exprs)
      exprs += f"print('answer:', cnt)"
      #print(exprs)
      exec(exprs)
      

      The program generates (and executes) the following code:

      X = 60; cnt = 0
      for A in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= X]:
        if sum([A]) + 7 * A + 50 < 501: break
        for B in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= A]:
          if sum([A, B]) + 6 * B + 50 < 501: break
          for C in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= B]:
            if sum([A, B, C]) + 5 * C + 50 < 501: break
            for D in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= C]:
              if sum([A, B, C, D]) + 4 * D + 50 < 501: break
              for E in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= D]:
                if sum([A, B, C, D, E]) + 3 * E + 50 < 501: break
                for F in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= E]:
                  if sum([A, B, C, D, E, F]) + 2 * F + 50 < 501: break
                  for G in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= F]:
                    if sum([A, B, C, D, E, F, G]) + 1 * G + 50 < 501: break
                    for H in [x for x in [60, 57, 54, 51, 50, 48, 45, 42, 40, 39, 38, 36, 34, 33, 32] if x <= G]:
                      I = 501 - sum([A, B, C, D, E, F, G, H])
                      if I > H: break
                      if not (I in {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 50}): continue
                      cnt += sum([A, B, C, D, E, F, G, H, I][3 * i:3 * (i + 1)] == [max(treble)] * 3 for i in range(3)) + 2
      print('answer:', cnt)
      

      Like

  • Unknown's avatar

    Jim Randell 6:41 am on 6 July 2025 Permalink | Reply
    Tags:   

    Teaser 3276: First and last multiple 

    From The Sunday Times, 6th July 2025 [link] [link]

    Johann enjoys playing with numbers and making up numerical challenges for his elder brother Jacob. He has worked out a new challenge, which involves Jacob trying to find the whole-number square root of a six-digit number. He explained that the sum of the individual digits [of the six-digit number] is exactly five times the sum of its first and last digits. To make it easier, he tells him that no number is repeated in the six digits, there are no zeros, and the last three digits are in descending numerical order.

    What is the square root of the six digit number?

    [teaser3276]

     
    • Jim Randell's avatar

      Jim Randell 6:48 am on 6 July 2025 Permalink | Reply

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

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

      Run: [ @codepad ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # sqrt(ABCDEF) = XYZ
      --distinct="ABCDEF"
      --invalid="0,ABCDEFXZ"
      --answer="XYZ"
      
      # XYZ is the square root of ABCDEF
      "sq(XYZ) = ABCDEF"
      
      # the sum of the digits of the square is 5 times the
      # sum of the first and last digit
      "A + B + C + D + E + F == 5 * (A + F)"
      
      # the final three digits are in descending order
      "D > E > F"
      
      # [optional] performance tweaks
      --reorder="[1]"
      --invalid="1|2,X"
      

      Solution: The square root is 661.

      And so the 6-digit number is: 661^2 = 436921.

      And we have:

      4 + 3 + 6 + 9 + 2 + 1 = 25 = 5 × (4 + 1)
      9 > 2 > 1

      Like

    • GeoffR's avatar

      GeoffR 10:17 am on 6 July 2025 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:a; var 1..9:b; var 1..9:c; var 1..9:d;
      var 1..9:e; var 1..9:f; 
      var 316..999:ans; % square root of abcdef
      
      var 100000..999999: abcdef == 100000*a + 10000*b 
      + 1000*c + 100*d + 10*e + f;
      
      constraint all_different([a, b, c, d, e, f]);
      
      constraint a + b + c + d + e + f == 5 * (a + f);
      constraint d > e /\ e  > f;
      
      predicate is_sq(var int: c) =
         let {
            var 0..ceil(pow(int2float(ub(c)),(1/2))): z
         } in z*z = c;
      
      constraint ans * ans == abcdef;
      constraint is_sq(abcdef);
      
      solve satisfy;
      
      output["Square root of the six digit number = " ++  show(ans)];
      
      
      

      Like

    • Frits's avatar

      Frits 3:03 pm on 6 July 2025 Permalink | Reply

      # last 2 digits y and z  (abcdef = xyz^2)
      last2 = set(i for i in range(11, 100) 
                  if i % 10 and 9 < (m := (i * i) % 100) < 90 and m // 10 > m % 10)
      
      for yz in last2:
        # A + F <= 7 so X < 9
        for x in range(3, 9):
          xyz = 100 * x + yz
          abcdef = xyz * xyz
          if len(set(s := str(abcdef))) != 6: continue
          if s[3] <= s[4] or "0" in s: continue
          dgts6 = [int(x) for x in s]
          if sum(dgts6) != 5 * (dgts6[0] + dgts6[-1]): continue
      
          print("answer:", xyz)
      

      Like

      • Frits's avatar

        Frits 5:57 pm on 6 July 2025 Permalink | Reply

        Processing six-digit numbers to find a valid square root.

        from enigma import is_square
        
        # last 2 digits e and f  (abcdef = xyz^2)
        last2 = set(divmod(m, 10) for i in range(11, 100) 
                    if i % 10 and 9 < (m := (i * i) % 100) < 90 and m // 10 > m % 10)
        for (e, f) in last2:
          ef = 10 * e + f
          for d in range(e + 1, 10):
            _def = 100 * d + ef
            # 21 <= 5 * (a + f) <= 39  or  4 < a + f < 8
            for a in set(range(max(1, 5 - f), 8 - f))- {d, e, f}:
              # remaining for b + c
              if not (3 <= (bc := 5 * (a + f) - (a + d + e + f)) <= 17): continue
              adef = 10**5 * a + _def
              # determine missing numbers x and y (x + y = bc with x < y)
              for x in set(range(max(1, bc - 9), (bc + 1) // 2)) - {a, d, e, f}:
                if (y := bc - x) in {a, x, d, e, f}: continue
                for b, c in ((x, y), (y, x)):
                  n = adef + 10**4 * b + 10**3 * c
                  if (xyz := is_square(n)):
                    print("answer:", xyz)
        

        Like

    • Ruud's avatar

      Ruud 4:22 pm on 6 July 2025 Permalink | Reply

      import math
      import istr
      
      for i in range(int(math.sqrt(100_000)), int(math.sqrt(1_000_000))):
          if (sum((sq := istr(i * i))) == 5 * (sq[0] + sq[-1])) and sq.all_distinct() and "0" not in sq and sq[-3] > sq[-2] > sq[-1]:
              print(sq, i)
      

      Like

    • GeoffR's avatar

      GeoffR 9:17 am on 9 July 2025 Permalink | Reply

      sq6 = [ n**2 for n in range(317, 1000)    
              if len(set(str(n**2))) == 6 ]
      
      for num in sq6:
        ds = str(num)
        if '0' in ds: continue
        dig = [int(x) for  x in ds]
        if (dig [3] > dig[4] > dig[5]):
          if sum(dig[:6]) == 5 * (dig[0] + dig[5]):
            print (f"Square root is {int(num ** 0.5)}.")
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:55 am on 4 July 2025 Permalink | Reply
    Tags:   

    Teaser 2478: [Palindromic primes] 

    From The Sunday Times, 21st March 2010 [link]

    An article in The Times of January 2 commented that 2010 was the sum of four primes. As a reader pointed out in a letter on January 5, this was hardly worthy of comment: by Goldbach’s Conjecture, any even number greater than two is the sum of just two primes.

    What is worthy of note is that 2010 is the sum of four palindromic primes. I found one such set of primes and each of my two grandsons, Oliver and Sam found different sets. My set had two primes in common with Oliver’s set and two in common with Sam’s set.

    What are my four palindromic primes?

    This puzzle was originally published with no title.

    [teaser2478]

     
    • Jim Randell's avatar

      Jim Randell 10:55 am on 4 July 2025 Permalink | Reply

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

      from enigma import (primes, is_npalindrome, subsets, intersect, printf)
      
      # target value
      T = 2010
      
      # collect palindromic primes
      ps = list(p for p in primes.between(2, T) if is_npalindrome(p))
      
      # find 4-subsets with the required sum
      ts = list(ss for ss in subsets(ps, size=4) if sum(ss) == T)
      
      # choose a set for the setter
      for V in ts:
        # find other sets with 2 values in common with V
        gs = list(ss for ss in ts if ss != V and len(intersect([ss, V])) == 2)
        if len(gs) == 2:
          # output solution
          printf("V = {V} [O,S = {gs}]")
      

      Solution: Victor’s set was: (11, 151, 919, 929).

      And the grandsons sets were:

      (11, 313, 757, 929) [shares: 11, 929]
      (11, 353, 727, 919) [shares: 11, 919]

      And these are the only sets of 4 palindromic primes that sum to 2010.

      Like

    • Ruud's avatar

      Ruud 4:32 pm on 4 July 2025 Permalink | Reply

      import istr
      
      solutions = [set(p) for p in istr.combinations((i for i in istr.range(2010) if i.is_prime() and i == i[::-1]), 4) if sum(p) == 2010]
      print([solution for solution in solutions if sum(len(solution & solution1) == 2 for solution1 in solutions) == 2])
      

      Like

  • Unknown's avatar

    Jim Randell 8:42 am on 1 July 2025 Permalink | Reply
    Tags:   

    Brain teaser 1029: Staff factors 

    From The Sunday Times, 18th April 1982 [link]

    The Manager of a large company greeted his twelve new computer staff. “You have been given consecutive, five-digit, Staff Reference Numbers (SRN). I remember numbers by finding their prime factors, using mental arithmetic — pre-computer vintage. Why not try it? If you would each tell me what factors you have, without specifying them (and ignoring unity), I should be able to work out your SR numbers”.

    John said: “My number is prime.”

    Ted said ” I have two prime factors. Your number follows mine doesn’t it, Les?”

    Ian said: “I also have two, one of which squared. Alan’s just before me on the list.”

    Sam said: “One of mine is to the power four. The last two digits of my SRN give me the other prime factor.”

    Pete said: “I’ve got one factor to the power four as well. The other one is my year of birth.”

    Brian said: “My number has one prime factor cubed and two others, both squared.”

    Chris said: “I’m the only one with four factors, one of which is squared. Fred’s number is one less than mine.”

    Dave started to say: “Kevin’s SRN is the one after mine, which …” when the Manager interrupted. “I can now list all twelve!”

    List the twelve people, by initials, in increasing order of SRNs. What is Sam’s SRN?

    This was the final puzzle to go by the title “Brain teaser“. The next puzzle was “Brainteaser 1030“.

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

    [teaser1029]

     
    • Jim Randell's avatar

      Jim Randell 8:43 am on 1 July 2025 Permalink | Reply

      Instead of trying all possible 5-digit SRNs we can reduce the number of candidates considered by only considering numbers in the neighbourhood of P, as there are only a few numbers that are the 4th power of a prime multiplied by a viable prime birth year. (I considered primes between 1912 and 1982 as viable birth years).

      This Python program uses the [[ SubstitutedExpression ]] solver from the enigma.py library, to solve the puzzle given a candidate start number.

      It runs in 308ms (although I haven’t looked at optimising the evaluation order of the expressions).

      from enigma import (SubstitutedExpression, irange, primes, cache, sprintf as f, join, printf)
      
      # prime factors of n -> ((p1, e1), (p2, e2), ...)
      factors = cache(lambda n: tuple(primes.prime_factor(n)))
      
      # I has exactly 2 prime factors; one is a power of 2
      def checkI(n):
        ((p1, e1), (p2, e2)) = factors(n)
        return 2 in {e1, e2}
      
      # S has exactly 2 prime factors; one is a power of 4, and the other is the last 2 digits of S
      def checkS(n):
        ((p1, e1), (p2, e2)) = factors(n)
        return (4, n % 100) in {(e1, p2), (e2, p1)}
      
      # P has exactly 2 prime factors; one is a power of 4, and the other is his year of birth
      def checkP(n):
        ((p1, e1), (p2, e2)) = factors(n)
        return (e1 == 4 and 1911 < p2 < 1983) or (e2 == 4 and 1911 < p1 < 1983)
      
      # B has exactly 3 prime factors; a power of 3 and two powers of 2
      def checkB(n):
        ((p1, e1), (p2, e2), (p3, e3)) = factors(n)
        return sorted([e1, e2, e3]) == [2, 2, 3]
      
      # C has exactly 4 prime factors; one is a power of 2
      def checkC(n):
        ((p1, e1), (p2, e2), (p3, e3), (p3, e4)) = factors(n)
        return 2 in {e1, e2, e3, e4}
      
      def solve(start, s2d=dict()):
        # expressions to evaluate
        exprs = [
          # J is prime
          "is_prime(J + start)",
          # T has exactly 2 prime factors
          "len(factors(T + start)) == 2",
          # all the others (except C) have < 4 prime factors
          "all(len(factors(x + start)) < 4 for x in [A, D, F, K, L])",
          # adjacent values
          "T + 1 = L",
          "I - 1 = A",
          "C - 1 = F",
          "D + 1 = K",
          # checks on the remaining values
          "checkI({I} + start)",
          "checkS({S} + start)",
          "checkP({P} + start)",
          "checkB({B} + start)",
          "checkC({C} + start)", 
        ]
      
        # environment
        env = dict(
          start=start,
          factors=factors, is_prime=primes.is_prime,
          checkI=checkI, checkS=checkS, checkP=checkP, checkB=checkB, checkC=checkC,
        )
      
        # construct the puzzle
        p = SubstitutedExpression(exprs, base=12, s2d=s2d, env=env)
      
        # solve the puzzle for the offsets
        for s in p.solve(verbose=''):
          # fill out the actual numbers from the offsets
          s = dict((k, v + start) for (k, v) in s.items())
          # output the numbers in order
          fmt = (lambda fs: join((f("{p}" if e == 1 else "{p}^{e}") for (p, e) in fs), sep=", "))
          for k in sorted(s.keys(), key=s.get):
            n = s[k]
            printf("{k} = {n} [{fs}]", fs=fmt(factors(n)))
          printf()
      
      # consider possible prime birth years for P
      for y in primes.between(1912, 1982):
        # consider other prime factor, raised to the 4th power
        for p in primes:
          P = p**4 * y
          if P > 99999: break
          # consider possible start numbers
          for start in irange(max(10000, P - 11), min(99988, P)):
            solve(start, dict(P=P - start))
      

      Solution: The order is: D K A I B S T L P F C J. Sam’s SRN is 31213.

      The SRNs [along with their prime factors] are given below:

      D = 31208 [2^3, 47, 83]
      K = 31209 [3, 101, 103]
      A = 31210 [2, 5, 3121]
      I = 31211 [23^2, 59]
      B = 31212 [2^2, 3^3, 17^2]
      S = 31213 [7^4, 13]
      T = 31214 [2, 15607]
      L = 31215 [3, 5, 2081]
      P = 31216 [2^4, 1951]
      F = 31217 [19, 31, 53]
      C = 31218 [2, 3, 11^2, 43]
      J = 31219 [31219]

      So we see P was born in 1951, which is 31 years before the puzzle was set.


      With a bit of work, we can further reduce the number of cases considered significantly.

      There are only 7 candidate values for P, and there are only 11 candidate values for S. And there is only a single pair where P and S are close enough together to give a solution. This also gives a way to tackle puzzle manually.

      Like

      • Frits's avatar

        Frits 1:44 pm on 1 July 2025 Permalink | Reply

        @Jim, is there a typo in “which is 42 years before the puzzle was set”?

        Like

      • Frits's avatar

        Frits 7:19 pm on 1 July 2025 Permalink | Reply

        from enigma import is_prime, prime_factor as pfactor, ceil, subsets
        
        # possible SRN's for people in list <s>
        SRNs = lambda s: {i for i in range(s[0] - 11, s[0] + 12) 
                          if i not in s and all(abs(x - i) < 12 for x in s)}
                          
        # mandatory SRN's for people in list <s>
        m_SRNs = lambda s: {i for i in range(min(s) + 1, max(s)) if i not in s}                  
        
        mn = 1912 # minimum birth date
        
        # set of prime numbers up to 99
        P = {3, 5, 7}
        P |= {2} | {x for x in range(11, 100, 2) if all(x % p for p in P)}
        sP = sorted(P)
        
        # check last item in sequence <s>
        def check(s, ln, vs, fs4={}):
          if fs4:
            # mandatory missing numbers plus last item  may not have 4 factors
            if not (m_SRNs(s) | {s[-1]}).isdisjoint(fs4): return False
          
          # check length and exponents
          if len(pfs := list(pfactor(s[-1]))) != ln or {y for x, y in pfs} != vs:
            return False
          return True  
        
        # start with P ([p1^4, 19xx])
        for p1 in sP:
          if p1**4 * mn > 99999: break
          ns = [n for y in range(mn, 1965) if 9999 < (n := p1**4 * y) <= 99999]
          mn, mx = ns[0] - 11 , ns[-1] + 11
          # check S ([s1^4, s2 with s2 last 2 digits])
          # S = xxxxab and S = ab.s1^4,  100 * xxxx = ab.(s1^4 - 1) so s1^4 = ...1
          for s1 in sP[1:]:
            if (pow4 := s1**4) * 11 > 99999: break
            if pow4 % 10 != 1 or mx / pow4 > 99: continue
            # calculate possible numbers for S
            for s in [s for ab in range(ceil(mn / pow4), mx // pow4 + 1) 
                      if ab in P and (s := ab * pow4) % 100 == ab]:
              # only C may have exactly 4 factors
              fs4 = {x for x in SRNs([s]) if len(list(pfactor(x))) == 4}
              for j in [j for j in SRNs([s]) if is_prime(j)]:
                for c in SRNs([s, j]):
                  if not check([s, j, c], 4, {1, 2}): continue
                  for b in SRNs([s, j, c]):
                    if not check([s, j, c, b], 3, {2, 3}, fs4): continue
                    for i in SRNs([s, j, c, b]):
                      if not check([s, j, c, b, i], 2, {1, 2}, fs4): continue
                      for t in SRNs([s, j, c, b, i]):
                        if not check([s, j, c, b, i, t], 2, {1}, fs4): continue
                        for p in SRNs([s, j, c, b, i, t]):
                          if not check([s, j, c, b, i, t, p], 2, {1, 4}, fs4): continue
                          # before I is A, after T is L, before C is F
                          a, l, f = i - 1, t + 1, c - 1
                          n10 = sorted([s, j, c, b, i, t, p, a, l, f])
                          if len(set(n10)) != 10: continue
                          # K after D, find places to insert (D, K)
                          for d, k in subsets(SRNs(n10), 2):
                            if k != d + 1: continue
                            if not {d, k}.isdisjoint(fs4): continue
                            # 12 consecutive numbers
                            if ((n12 := sorted(n10 + [d, k])) == 
                                 list(range(n12[0], n12[0] + 12))):
                              # final check   
                              if not (set(n12) - {c}).isdisjoint(fs4): continue
                              map = dict(list(zip([s, j, c, b, i, t, p, a, l, f, d, k],
                                            "SJCBITPALFDK")))
                              for k, v in sorted(map.items()):
                                print(f"{v}: {k} = "
                                      f"{' * '.join(['^'.join(str(y) for y in x)
                                                     for x in pfactor(k)])}")
        

        Like

  • Unknown's avatar

    Jim Randell 7:22 am on 29 June 2025 Permalink | Reply
    Tags:   

    Teaser 3275: Against the odds 

    From The Sunday Times, 29th June 2025 [link] [link]

    I have taken a number of playing cards (more red than black) from a standard pack of 52 cards and redistributed them in a certain way in two piles, each containing both red and black cards.

    A card is taken at random from the smaller pile and placed in the larger pile. The larger pile is shuffled; a card is taken at random from it and placed in the smaller pile.

    There is a one in eight chance that the number of red and black cards in each pile will be the same as they were originally.

    How many black cards in total were used?

    [teaser3275]

     
    • Jim Randell's avatar

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

      (See also: Teaser 2571).

      If we have two piles:

      pile 1, with b1 black cards, and r1 red cards; and:
      pile 2, with b2 black cards, and r2 red cards.

      where pile 1 (of size n1) is smaller than pile 2 (of size n2).

      Then the probability of the same colour card being chosen for both transfers is the sum of the following probabilities:

      P(both transfers black) = (b1 / n1) × ((b2 + 1) / (n2 + 1))
      P(both transfers red) = (r1 / n1) × ((r2 + 1) / (n2 + 1))

      P(both transfers same colour) = (b1 × (b2 + 1) + r1 × (r2 + 1)) / (n1 × (n2 + 1))

      The following Python program constructs all possible arrangements of cards and calculates the probability of the same coloured card moving in both directions.

      We can find the inverse of the combined probability, and this allows us to perform all the calculations using integers.

      It runs in 84ms. (Internal runtime is 22ms).

      from enigma import (irange, subsets, Decompose, cproduct, div, printf)
      
      # choose number of red and black cards (B < R)
      for (B, R) in subsets(irange(2, 26), size=2, select='C'):
      
        # split into 2 piles (pile1 < pile2)
        decompose = Decompose(2, increasing=0, sep=0)
        for ((b1, b2), (r1, r2)) in cproduct([decompose(B), decompose(R)]):
          (n1, n2) = (b1 + r1, b2 + r2)
          if not (n1 < n2): continue
      
          # calculate probability of the same coloured card in both transfers
          # r = 1/p
          r = div(n1 * (n2 + 1), b1 * (b2 + 1) + r1 * (r2 + 1))
      
          # look for solution
          if r == 8:
            printf("B={B} R={R}")
            printf("  b1={b1} r1={r1} -> {n1}; b2={b2} r2={r2} -> {n2}")
            printf("    p=1/{r}")
            printf()
      

      Solution: There were 21 black cards.

      And 23 red cards.

      The cards are distributed into piles as follows:

      pile 1 = 20 black + 1 red (21 cards)
      pile 2 = 1 black + 22 red (23 cards)

      The probability of a black card moving in both directions is:

      pB = 20/21 × 2/24 = 40/504

      And the probability of a red card moving in both directions is:

      pR = 1/21 × 23/24 = 23/504

      And we have:

      pB + pR = 63/504 = 1/8

      Like

      • Jim Randell's avatar

        Jim Randell 9:48 am on 30 June 2025 Permalink | Reply

        Or we can use the [[ SubstitutedExpression ]] solver from the enigma.py library, for a more declarative solution.

        from enigma import (SubstitutedExpression, irange, printf)
        
        # assign symbols
        macro = dict(b1='W', r1='X', b2='Y', r2='Z')
        
        # expressions to solve
        exprs = [
          # more reds than blacks; no more than 26 cards per colour
          "@b1 + @b2 < @r1 + @r2 < 27",
          # pile 1 is smaller than pile 2
          "@b1 + @r1 < @b2 + @r2",
          # probability P = 1/8; P = Pn/Pd => Pd = 8 Pn
          "(@b1 + @r1) * (@b2 + @r2 + 1) == 8 * (@b1 * (@b2 + 1) + @r1 * (@r2 + 1))",
        ]
        
        # construct the solver
        p = SubstitutedExpression(exprs,
              base=26, digits=irange(1, 25), distinct=[], macro=macro,
              answer="(@b1, @r1, @b2, @r2)",
            )
        
        # solve the puzzle
        for (b1, r1, b2, r2) in p.answers(verbose=0):
          (B, R) = (b1 + b2, r1 + r2)
          printf("B={B} R={R} [b1={b1} r1={r1}; b2={b2} r2={r2}]")
        

        Like

  • Unknown's avatar

    Jim Randell 8:28 am on 27 June 2025 Permalink | Reply
    Tags: ,   

    Teaser 2471: [Cutting a rod] 

    From The Sunday Times, 31st January 2010 [link]

    In the woodwork class, students were being taught how to measure and cut accurately. Pat was given a rod that was a whole number of feet long and was told to saw it into three pieces, each a different whole number of inches long. While he was cutting his set of three pieces, Pat calculated how many different sets it would be possible to make. He discovered that the answer was equal to his father’s year of birth.

    What year was that?

    This puzzle was originally published with no title.

    [teaser2471]

     
    • Jim Randell's avatar

      Jim Randell 8:29 am on 27 June 2025 Permalink | Reply

      We assume the cuts themselves make a negligible difference to the length of the pieces.

      Here is a constructive program that counts the number of dissections of an n-foot rod into 3 dissimilar whole inch lengths.

      I decided that as the puzzle was set in 2010, viable years are in the range [1940, 1985].

      The program runs in 71ms. (Internal runtime is 6.1ms).

      from enigma import (irange, inf, decompose, icount, printf)
      
      # generate number of dissections
      def generate():
        # consider number of feet
        for n in irange(1, inf):
          # divide into different sets of inches
          k = icount(decompose(12 * n, 3, increasing=1, sep=1))
          printf("[{n} ft -> {k} sets]")
          yield (n, k)
      
      # find lengths that give up to 1985 sets
      ss = list()
      for (n, k) in generate():
        ss.append((n, k))
        if k > 1985: break
      
      # output solution (between 1940 and 1985)
      for (_, y) in ss:
        if 1939 < y < 1986:
          printf("year = {y}")
      

      Solution: Pat’s father was born in 1951.

      1951 is the number of dissections of a 13-foot rod.


      We can use the values accumulated by the program to determine a polynomial equation to generate the sequence:

      % python3 -i teaser/teaser2471.py
      [1 ft -> 7 sets]
      [2 ft -> 37 sets]
      [3 ft -> 91 sets]
      [4 ft -> 169 sets]
      [5 ft -> 271 sets]
      [6 ft -> 397 sets]
      [7 ft -> 547 sets]
      [8 ft -> 721 sets]
      [9 ft -> 919 sets]
      [10 ft -> 1141 sets]
      [11 ft -> 1387 sets]
      [12 ft -> 1657 sets]
      [13 ft -> 1951 sets]
      [14 ft -> 2269 sets]
      year = 1951
      
      >>> from enigma import Polynomial
      >>> p = Polynomial.interpolate(ss)
      >>> print(p)
      Polynomial[(+12)x^2 (-6)x (+1)]
      >>> print(p(13))
      1951
      >>> print(p(5280))
      334509121
      

      So, for an n-foot rod (integer n ≥ 1), the number of dissections D(n) is given by:

      D[n] = 12n^2 − 6n + 1

      This corresponds to OEIS A154105 [@oeis.org].

      Like

      • Frits's avatar

        Frits 3:32 pm on 27 June 2025 Permalink | Reply

        It is not too difficult to find this formula for the sum 1 + 2+4 + 5+7 + 8+10 + 11+13 + 14+16 + ….

        Like

      • Frits's avatar

        Frits 4:08 pm on 27 June 2025 Permalink | Reply

        Another formula for D(n) is:

        sum(6 * n – (k + 3) // 2 – k – 1 for k in range(4 * n – 1))

        Like

    • Jim Randell's avatar

      Jim Randell 8:10 am on 28 June 2025 Permalink | Reply

      See also: BrainTwister #25.

      Where we had:

      t[n] = intr(sq(n) / 12)

      Then D[n] appears as a subsequence of t[n] taking every 12th element (as there are 12 inches in 1 foot).

      Specifically:

      D[n] = t[12n − 3]

      And this gives rise to the quadratic equation given above.

      Like

  • Unknown's avatar

    Jim Randell 8:26 am on 24 June 2025 Permalink | Reply
    Tags:   

    Teaser 2467: [Some primes] 

    From The Sunday Times, 3rd January 2010 [link]

    To celebrate the start of a new decade, I have written down a list of prime numbers whose sum is equal to one of the years of this decade (in other words, it is one of the numbers from 2010 to 2019, inclusive). Overall, the numbers in this list contain just eight digits, namely four different even digits and four different odd digits, each used once.

    With simple logic, rather than complicated calculations, you should be able to work out the list of numbers.

    What are they?

    This puzzle was originally published with no title.

    [teaser2467]

     
    • Jim Randell's avatar

      Jim Randell 8:26 am on 24 June 2025 Permalink | Reply

      This Python 3 program looks for sets of primes with the required property.

      It runs in 120ms. (Internal run time is 53ms).

      from enigma import (primes, nsplit, seq_items, join, printf)
      
      # find possible primes, and record digits
      ps = list()
      for p in primes.between(2, 2018):
        ds = nsplit(p)
        ss = set(ds)
        if len(ss) == len(ds):
          ps.append((p, ss))
      
      # odd and even digits
      (evens, odds) = ({0, 2, 4, 6, 8}, {1, 3, 5, 7, 9})
      
      # solve for primes in <ps>
      # i = start index in <ps>
      # t = total to far
      # ds = digits used so far
      # ns = primes used so far
      def solve(ps, i=0, t=0, ds=set(), ns=[]):
        if t > 2009:
          if len(ds) == 8:
            yield ns
        if len(ds) < 8:
          # add in another prime
          for (j, (p, ss)) in seq_items(ps, i):
            t_ = t + p
            if t_ > 2019: break
            if not ss.isdisjoint(ds): continue
            ds_ = ds.union(ss)
            if evens.issubset(ds_) or odds.issubset(ds_): continue
            yield from solve(ps, j + 1, t_, ds_, ns + [p])
      
      # solve the puzzle
      for ns in solve(ps):
        # output solution
        printf("{ns} = {t}", ns=join(ns, sep=" + "), t=sum(ns))
      

      Solution: The numbers are: 2, 947, 1063.

      And:

      2 + 947 + 1063 = 2012

      The unused digits are 5 (odd) and 8 (even).

      Like

    • Frits's avatar

      Frits 11:01 am on 24 June 2025 Permalink | Reply

      from itertools import compress
      
      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:])]
      
      # decompose <t> into increasing numbers from <ns> so that <k> different
      # digits have been used and the sum of the numbers is in range (t-9) ... t
      def decompose(t, ns, k=8, ds=set(), s=[]):
        if k <= 0 or t < 10:
          if k == 0 and t < 10:
            # not using one odd and one even digit
            if sum(int(x) for x in set("0123456789") - ds) % 2:
              yield s 
        else:
          for i, (n, dgts) in enumerate(ns):
            if n > t: break 
            if ds.isdisjoint(dgts):
              yield from decompose(t - n, ns[i + 1:], k - len(dgts), 
                                   ds | dgts, s + [n])
      
      m = 2000
      P = [(p, s) for p in primesbelow(m) if len(s := set(str(p))) == len(str(p))]
      
      # look for prime numbers that add up to 2010 ... 2019
      for ps in decompose(2019, P):
        print(f"answer: {' + '.join(str(x) for x in ps)} = {sum(ps)}")
      

      Like

  • Unknown's avatar

    Jim Randell 6:33 am on 22 June 2025 Permalink | Reply
    Tags:   

    Teaser 3274: Anarithm 

    From The Sunday Times, 22nd June 2025 [link] [link]

    If you rearrange the letters of a word to form another word, it’s called an anagram. So I think that if you rearrange the digits of a number to form another number, it could be called an anarithm.

    Some numbers when expressed in two different number bases have representations that are anarithms of each other. For instance, the decimal number 66 is represented as 123 in base 7 and 231 in base 5.

    I’ve recently been looking at numbers in base 8 and base 5 and found that it is possible to have a number whose representations in base 8 and base 5 are anarithms of each other.

    In decimal notation, what is the largest such number?

    [teaser3274]

     
    • Jim Randell's avatar

      Jim Randell 6:39 am on 22 June 2025 Permalink | Reply

      The following Python program produces non-trivial (i.e. >1-digit) representations that are “anarithms”, in numerical order.

      It runs in 66ms. (Internal runtime is 494µs).

      from enigma import (irange, inf, nsplit, join, printf)
      
      # the two bases (A < B)
      (A, B) = (5, 8)
      
      # consider increasing numbers of digits
      for k in irange(2, inf):
        (x, y) = (B**(k - 1), (A**k) - 1)
        if x > y: break
        for n in irange(x, y):
          (a, b) = (nsplit(n, base=A), nsplit(n, base=B))
          if sorted(a) == sorted(b):
            # output solution
            printf("k={k}: {n} -> {a} [base {A}], {b} [base {B}]", a=join(a), b=join(b))
      

      Solution: The number is 540 (decimal).

      In base 5 it is represented: 4130.

      And in base 8 it is represented: 1034.

      Like

      • Jim Randell's avatar

        Jim Randell 2:56 pm on 22 June 2025 Permalink | Reply

        Or, as we want the largest possible number, we can consider the candidate values starting with the largest. Then we can stop after the first solution is found.

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

        from enigma import (irange, inf, nsplit, rev, join, printf)
        
        # find largest number for bases A, B (where A < B)
        def solve(A, B):
          assert A < B
          # intervals to consider
          ivs = list()
          for k in irange(2, inf):
            (x, y) = (B**(k - 1), (A**k) - 1)
            if x > y: break
            ivs.append((x, y))
        
          # consider each interval from largest to smallest
          for (x, y) in rev(ivs):
            for n in irange(y, x, step=-1):
              (a, b) = (nsplit(n, base=A), nsplit(n, base=B))
              if sorted(a) == sorted(b):
                # output solution
                printf("{n} -> {a} [base {A}], {b} [base {B}]", a=join(a), b=join(b))
                return
        
        # solve for base 5 and base 8
        solve(5, 8)
        

        Like

    • Frits's avatar

      Frits 8:00 pm on 23 June 2025 Permalink | Reply

      Not my fastest version but still fast and relatively simple.

      # get digits of a number in base <b> (converted from <n> in base 10)
      def int2base(n, b):
        ds = []
        while n:
            ds.append(int(n % b))
            n //= b
        return ds
      
      b1, b2 = 5, 8
      rng, invalid = [], set(range(b1, 10))
      # get a list of valid ranges per number of digits
      for n, _ in enumerate(iter(bool, 1), 2): # infinite  loop
        mn, mx = b2**(n - 1), b1**n - 1
        if mx < mn: break
        rng.append((mn, mx))
      
      # for decreasing number of digits
      for mn, mx in reversed(rng):
        # check all numbers in the valid range
        for n in range(mx, mn - 1, -1):
          # check for invalid digits
          if not invalid.isdisjoint(n_b2 := int2base(n, b2) ): continue
      
          # base 8 and base 5 are anarithms of each other
          if sorted(int2base(n, b1)) == sorted(n_b2):
            print(f"answer: {n}")
            exit(0)
      

      Like

  • Unknown's avatar

    Jim Randell 8:12 am on 19 June 2025 Permalink | Reply
    Tags:   

    Teaser 2519: [Greek alphametic] 

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

    This week’s letters-for-digits Teaser has a Greek theme. All words in capitals are numbers that have been coded by consistently using different letters for different digits.

    Our story concerns the prime THALLO (goddess of spring buds), who was also good at sums, and produced:

    ALPHA + BETA + GAMMA = OMEGA

    What number is represented by OMEGA?

    This puzzle was originally published with no title.

    This completes the archive of Teaser puzzles originally published in 2011. There is now a complete archive of puzzles (and solutions) from the start of 2011 to present, along with many older puzzles.

    [teaser2519]

     
    • Jim Randell's avatar

      Jim Randell 8:13 am on 19 June 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 1.2ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      "ALPHA + BETA + GAMMA = OMEGA"
      
      --extra="is_prime(THALLO)"
      
      --answer="OMEGA"
      

      Solution: OMEGA = 76915.

      The sum is:

      52305 + 8945 + 15665 = 76915

      And we have: THALLO = 405227, which is prime.

      Like

  • Unknown's avatar

    Jim Randell 7:51 am on 17 June 2025 Permalink | Reply
    Tags:   

    Teaser 2524: [Whispered birthday] 

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

    My birthday is on the first of a month. My three logical friends Lettice, Daisy and Bertha wanted to know which month. So I whispered to Lettice the number of letters in the spelling of the month, I whispered to Daisy the number of days in the month, and I whispered to Bertha the day of the week of my birthday this year. I explained to all three what I had done and then I asked them in turn whether they were able to work out the month yet. Their replies were:

    Lettice: no
    Daisy: no
    Bertha: no
    Lettice: no
    Daisy: no
    Bertha: yes

    What is my birthday month?

    This puzzle was originally published with no title.

    [teaser2524]

     
    • Jim Randell's avatar

      Jim Randell 7:53 am on 17 June 2025 Permalink | Reply

      This Python program uses the [[ filter_unique() ]] function from the enigma.py library to eliminate candidate solutions at each step.

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

      from enigma import (filter_unique, printf)
      
      # month names
      months = "January February March April May June July August September October November December".split()
      
      # maps month names to: L = number of letters; D = number of days; B = day of week of 1st
      L = dict((k, len(k)) for k in months)
      D = dict(zip(months, [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]))  # for 2011
      B = dict(zip(months, "Sa Tu Tu Fr Su We Fr Mo Th Sa Tu Th".split()))  # for 2011
      
      # start with all possible months
      ss = months
      
      # answers: 1 = "no" (non_unique), 0 = "yes" (unique)
      for (d, i) in [(L, 1), (D, 1), (B, 1), (L, 1), (D, 1), (B, 0)]:
        ss = filter_unique(ss, d.get)[i]
      
      # output solution(s)
      for m in ss:
        printf("month = {m}")
      

      Solution: The birthday month is March.

      Like

  • Unknown's avatar

    Jim Randell 6:47 am on 15 June 2025 Permalink | Reply
    Tags:   

    Teaser 3273: A good deal? 

    From The Sunday Times, 15th June 2025 [link] [link]

    Delia plays a game with a stack of different pieces of card. To shuffle the stack she deals them quickly into four equal piles (face-downwards throughout) — the top card starts the first pile, the next card starts the second pile, then the third starts the third pile and likewise the fourth. She continues to place the cards on the four piles in order. Then she puts the four piles back into one big stack with the first pile on the bottom, the second pile next, then the third, with the fourth pile on the top.

    She is very pleased with her shuffling method because each card moves to a different position in the stack, so she decides to repeat the shuffle a few more times. However, this is counter-productive because after fewer than six such shuffles the cards will be back in their original order.

    How many cards are in her stack?

    There are now 1200 Teaser puzzles available on the S2T2 site.

    [teaser3273]

     
    • Jim Randell's avatar

      Jim Randell 7:00 am on 15 June 2025 Permalink | Reply

      See also: Enigma 710, Teaser 2446.

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

      from enigma import (irange, inf, flatten, rev, printf)
      
      # perform a shuffle using <p> piles
      def shuffle(cs, p=4):
        return rev(flatten(cs[i::p] for i in irange(p)))
      
      # find when cards return to original order
      def solve(cs, fn, M=inf):
        cs0 = list(cs)
        for k in irange(1, M):
          cs = fn(cs)
          if cs == cs0:
            return k
      
      # consider sets of cards
      for n in irange(8, inf, step=4):
        cs = list(irange(1, n))
        # check the shuffle leaves no card in its original position
        if any(x == y for (x, y) in zip(cs, shuffle(cs))): continue
        # find number of shuffles (< 6) to return to original order
        k = solve(cs, shuffle, 5)
        if k is None or k < 3: continue
        # output solution
        printf("{n} cards -> {k} shuffles")
        break  # stop at first solution
      

      Solution: There are 52 cards in the stack.

      And the stack returns to the original order after 4 shuffles. (This can be verified with a standard pack of cards).

      Like

      • Jim Randell's avatar

        Jim Randell 11:26 am on 15 June 2025 Permalink | Reply

        Alternatively (shorter, and doesn’t do any unnecessary shuffles):

        from enigma import (irange, inf, flatten, rev, repeat, cachegen, peek, printf)
        
        # perform a shuffle using <p> piles
        def shuffle(cs, p=4):
          return rev(flatten(cs[i::p] for i in irange(p)))
        
        # consider sets of cards
        for n in irange(8, inf, step=4):
          cs = list(irange(1, n))
          # generate the result of repeated shuffles
          ss = cachegen(repeat(shuffle, cs))
          # check shuffle 1 leaves no card in its original position
          if any(x == y for (x, y) in zip(ss(1), cs)): continue
          # find number of shuffles (2 .. 5) to return to original order
          k = peek((k for k in irange(2, 5) if ss(k) == cs), default=-1)
          if k < 2: continue
          # output solution
          printf("{n} cards -> {k} shuffles")
          break  # stop at first solution
        

        The [[ cachegen() ]] function has been in enigma.py for a while, waiting for a use to come up.

        Like

      • Jim Randell's avatar

        Jim Randell 1:56 pm on 16 June 2025 Permalink | Reply

        Here is a neat alternative approach.

        It analyses the cycle representation of the permutation that represents the shuffle, to determine the number of shuffles required to return to the original order, without needing to actually perform the shuffles.

        It has an internal runtime of 286µs.

        from enigma import (irange, inf, flatten, rev, cycles, mlcm, printf)
        
        # perform a shuffle using <p> piles
        def shuffle(cs, p=4):
          return rev(flatten(cs[i::p] for i in irange(p)))
        
        # consider sets of cards
        for n in irange(8, inf, step=4):
          cs = list(irange(1, n))
          # find the length of cycles in a shuffle
          cycs = set(len(cyc) for cyc in cycles(shuffle(cs), cs))
          # there are no 1-cycles
          if 1 in cycs: continue
          # calculate the number of shuffles to return to the original order
          k = mlcm(*cycs)
          if k < 6:
            printf("{n} cards -> {k} shuffles")
            break  # stop at the first solution
        

        Liked by 1 person

    • Frits's avatar

      Frits 10:28 am on 15 June 2025 Permalink | Reply

      Also printing the first solution.

      # Delia's shuffle algorithm for cards <s>
      def shuffle(s, ln):
        # make 4 piles (4th pile first)
        s_ = [[s[i] for i in reversed(range(ln)) if i % 4 == (3 - j)] 
              for j in range(4)]
        # put the 4 piles back into one big stack with the 1st pile on the bottom
        return [y for x in s_ for y in x]
      
      for m, _ in enumerate(iter(bool, 1), 1): # infinite loop, starting from 1
        n = m * 4         # number of cards in the stack
        orig, new = list(range(n)), []
        
        # Delia shuffles for the first time
        new = shuffle(orig, n)
        # each card moves to a different position in the stack
        if any(new[i] == i for i in range(n)): continue
        shuffle_count = 1
        # Delia shuffles a few more times (meaning more than once)
        while new != orig and shuffle_count < 6:
          new = shuffle(new, n)
          shuffle_count += 1
        
        if 2 < shuffle_count < 6:
          print(f"answer: {n} (first solution)")
          break
      

      Like

      • Frits's avatar

        Frits 10:50 am on 15 June 2025 Permalink | Reply

        The program prints the expected answer.

        The way I read the teaser there is another solution (allowing to already return back to the original order after two shuffles).

        Like

        • Jim Randell's avatar

          Jim Randell 11:07 am on 15 June 2025 Permalink | Reply

          I considered this, but decided the phrase “a few more times” excludes the possibility of just 1 additional shuffle reverting the original stack.

          Like

          • Frits's avatar

            Frits 11:21 am on 15 June 2025 Permalink | Reply

            I see. I regard this as an additional requirement. I can’t determine that for sure from the puzzle text.

            Delia will not check the cards after each shuffle so in my opinion nothing is to be excluded.

            Like

    • Frits's avatar

      Frits 9:57 am on 16 June 2025 Permalink | Reply

      Using a dictionary

      # Delia's shuffle algorithm for cards <s> using a dictionary
      dct = lambda s: dict(enumerate(sum((s[i::4] for i in range(4)), [])[::-1]))
      shuffle = lambda s: [d[x] for x in s]
        
      # infinite loop, starting from 2 (avoiding trivial case of four cards)
      for m, _ in enumerate(iter(bool, 1), 2): 
        n = m * 4         # number of cards in the stack
        # dictionary of position changes
        d = dct(orig := list(range(n)))
        # Delia shuffles for the first time
        new = shuffle(orig)
        # each card moves to a different position in the stack
        if any(new[i] == i for i in range(n)): continue
        shuffle_count = 1
        # Delia shuffles a few more times 
        while new != orig and shuffle_count < 6:
          new = shuffle(new)
          shuffle_count += 1
        
        if shuffle_count < 6:
          print(f"answer: {n} (first solution after trivial case n=4)")
          break
      

      or using map()

      # Delia's shuffle algorithm for cards <s> using a dictionary
      shuffle = lambda s: sum((s[i::4] for i in range(4)), [])[::-1]
        
      # infinite loop, starting from 2 (avoiding trivial case of four cards)
      for m, _ in enumerate(iter(bool, 1), 2): 
        n = m * 4         # number of cards in the stack
        # Delia shuffles for the first time
        s1 = shuffle(orig := list(range(n)))
        # each card moves to a different position in the stack
        if any(s1[i] == i for i in range(n)): continue
        new = s1
        shuffle_count = 1
        # Delia shuffles a few more times 
        while new != orig and shuffle_count < 6:
          new = list(map(lambda x: s1[x], new))
          shuffle_count += 1
        
        if shuffle_count < 6:
          print(f"answer: {n} (first solution after trivial case n=4)")
          break
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:31 pm on 16 June 2025 Permalink | Reply

        @Frits: I can’t recommend the (ab)use of [[ sum() ]] for joining lists. As a quick hack it may be acceptable to save a bit of typing, but the behaviour of [[ sum() ]] is only defined for numeric types, so it may not work at all (in CPython strings are explicitly rejected). And if it does work, it uses an inefficient algorithm for sequences (constructing a new sequence at each intermediate stage).

        Like

    • Frits's avatar

      Frits 12:03 pm on 19 June 2025 Permalink | Reply

      A more efficient version. Instead of always shuffling the whole stack of cards we keep track of the position of only one specific card.

      flat = lambda group: [x for g in group for x in g]
      
      # Delia's shuffle algorithm for cards <s> using a dictionary
      shuffle = lambda s: flat(s[i::4] for i in range(4))[::-1]
      
      # determine new position of number at position <i> 
      # in a sequence of 4 * m numbers
      shuffle1 = lambda i, m: m * (4 - i % 4) - i // 4 - 1
      
      # check when number 1 will return to position 1 again (numbers 0...n-1)
      
      # infinite loop, starting from 2 (avoiding trivial case of four cards)
      for m, _ in enumerate(iter(bool, 1), 2):  
        n = m * 4         # number of cards in the stack
        new, cnt = 1, 1
        while cnt < 6:
          # shuffle one card (let's say the 2nd card)
          new = shuffle1(new, m)
          cnt += 1
          # shuffle the whole stack 
          if new == 1:
            # Delia shuffles for the first time
            s1 = shuffle(orig := list(range(n)))
            # each card moves to a different position in the stack
            if any(s1[i] == i for i in range(n)): break
            new, shuffle_count = s1, 1
            # Delia shuffles a few more times 
            while new != orig and shuffle_count < 6:
              new = list(map(lambda x: s1[x], new))
              shuffle_count += 1
      
            if shuffle_count < 6:
              print(f"answer: {n} (first solution after trivial case n=4)")
              exit(0)
            break  
      

      Like

  • Unknown's avatar

    Jim Randell 8:45 am on 12 June 2025 Permalink | Reply
    Tags:   

    Teaser 2521: [The Dudeney Stakes] 

    From The Sunday Times, 16th January 2011 [link] [link]

    In The Dudeney Stakes at the local racecourse, six horses were rather special. A bookie, Isaac Conway, told me how much, in pence, he had taken on each of the six (less than £500 on each). But he translated the totals using a letter-for-digit substitution and the six figures were FIRST, THIRD, FIFTH, SIXTH, NINTH and TENTH, which happened to correspond to their positions in the race! Isaac also told me the total amount taken on the horses finishing first and third equalled that taken on the sixth.

    How much did he take on the tenth?

    This puzzle was originally published with no title.

    [teaser2521]

     
    • Jim Randell's avatar

      Jim Randell 8:47 am on 12 June 2025 Permalink | Reply

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

      A straightforward recipe has an internal runtime of 6.9ms. However using the [[ SubstitutedExpression.split_sum ]] solver brings this down to 129µs (50× faster).

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      # leading digits must be 1-4
      --invalid="0|5|6|7|8|9,FNST"
      
      "FIRST + THIRD = SIXTH"
      
      # make sure all the letters can be allocated
      --extra="true(FIRST, THIRD, FIFTH, SIXTH, NINTH, TENTH)"
      
      --answer="TENTH"
      

      Solution: The amount taken on the tenth horse was: £ 203.29.

      The amounts taken (in pence) on each horse are as follows:

      FIRST = 16842
      THIRD = 29687
      FIFTH = 16129
      SIXTH = 46529
      NINTH = 36329
      TENTH = 20329

      Like

    • Ruud's avatar

      Ruud 1:12 pm on 13 June 2025 Permalink | Reply

      import istr
      
      istr.repr_mode("int")
      
      
      for f, i, r, s, t, h, d, x, n, e in istr.permutations(istr.digits()):
          p = {1: f | i | r | s | t, 3: t | h | i | r | d, 5: f | i | f | t | h, 6: s | i | x | t | h, 9: n | i | n | t | h, 10: t | e | n | t | h}
          if p[1] + p[3] == p[6]:
              if all(amount < 50000 and amount[0] != "0" for amount in p.values()):
                  print(p)
      

      Like

  • 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 7:10 am on 8 June 2025 Permalink | Reply
    Tags:   

    Teaser 3272: Festive visitors 

    From The Sunday Times, 8th June 2025 [link] [link]

    George and Martha’s five daughters left home some years ago but have all moved into the same road, Square Avenue, which has 25 normally numbered houses. At Christmas last year, the parents drove to the road to visit but, with age rolling on, George’s memory was beginning to fail him. “Can’t remember the house numbers!” he moaned. “Quite easy!” retorted Martha, “if you remember three things. If you take the house numbers of Andrea, Bertha and Caroline, square each of them and add up the three squares, you get the square of Dorothy’s house number. Furthermore, the average of Andrea’s and Dorothy’s house numbers equals the average of Bertha’s and Caroline’s house numbers. Finally, Elizabeth’s house number is the average of the other four, and could not be smaller”.

    Which five houses got a knock on the door?

    [teaser3272]

     
    • Jim Randell's avatar

      Jim Randell 7:15 am on 8 June 2025 Permalink | Reply

      Here’s a quick solution using the [[ SubstitutedExpression ]] solver from the enigma.py library to implement the requirements directly. (More efficient formulations are available).

      The following Python program executes in 129ms. (Internal runtime is 65ms).

      from enigma import (SubstitutedExpression, Accumulator, irange, printf)
      
      p = SubstitutedExpression(
        [
          # "sum of the squares of A, B, C equals square of D"
          "sq(A) + sq(B) + sq(C) == sq(D)",
          # "mean of A and D equals mean of B and C"
          "A + D == B + C",
          # "E's number is the mean of the other four"
          "A + B + C + D == 4 * E",
        ],
        # allow house numbers from 1-25
        base=26,
        digits=irange(1, 25),
        # return the house numbers
        answer="(A, B, C, D, E)",
        template=""
      )
      
      # minimise the value of E
      r = Accumulator(fn=min, collect=1)
      
      # find candidate solutions
      for (A, B, C, D, E) in p.answers():
        r.accumulate_data(E, (A, B, C, D, E))
      
      # output solution
      printf()
      printf("min(E) = {r.value}")
      for (A, B, C, D, E) in r.data:
        printf("-> A={A} B={B} C={C} D={D} E={E}")
      

      Solution: The house numbers are: 3, 4, 8, 12, 13.

      There are four candidate solutions:

      A=3 B=4 C=12 D=13 E=8
      A=3 B=12 C=4 D=13 E=8
      A=4 B=6 C=12 D=14 E=9
      A=4 B=12 C=6 D=14 E=9

      The minimum value for E is 8, and so the other numbers are: A = 3, (B, C) = (4, 12) (in some order), D = 13.

      Like

    • Ruud's avatar

      Ruud 4:00 pm on 8 June 2025 Permalink | Reply

      import itertools
      
      min_e = 25
      for a, b, c, d in itertools.permutations(range(1, 26), 4):
          if a * a + b * b + c * c == d * d and a + d == b + c and (e := (a + b + c + d) / 4) % 1 == 0:
              if e < min_e:
                  min_numbers = set()
                  min_e = e
              if e <= min_e:
                  min_numbers.add(tuple(sorted((a, b, c, d, int(e)))))
      print(*min_numbers)
      

      Like

    • Ruud's avatar

      Ruud 7:04 am on 9 June 2025 Permalink | Reply

      One liner:

      import itertools
      
      print(
          *next(
              solutions
              for e in range(1, 26)
              if (
                  solutions := [
                      (a, b, c, d, e)
                      for a, b, c, d in itertools.permutations(range(1, 26), 4)
                      if a * a + b * b + c * c == d * d and a + d == b + c and e == (a + b + c + d) / 4
                  ]
              )
          )
      )
      

      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

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