Tagged: by: Victor Bryant Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:48 am on 23 January 2026 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 2462: [What NOW?] 

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

    Puzzlers are often confused about whether the number 1 is prime or square, so perhaps this Teaser will clarify the issue!

    • ONE is not prime, but it is an odd perfect square;
    TWO is divisible by 2 but not 4;
    • The number of odd primes less than SIX is TWO.

    In those statements digits have consistently been replaced by capital letters, with different letters being used for different digits.

    What is the number NOW?

    This puzzle was originally published with no title.

    [teaser2462]

     
    • Jim Randell's avatar

      Jim Randell 9:49 am on 23 January 2026 Permalink | Reply

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

      The following run-file executes in 98ms. (Internal runtime of the generated code is 14.4ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # no leading zeros (ONE, TWO, SIX, NOW)
      --invalid="0,OTSN"
      
      # collect primes < 1000
      --code="primes.expand(999)"
      
      # ONE is not prime, but it is an odd perfect square
      "ONE not in primes"
      "E % 2 = 1"
      "is_square(ONE)"
      
      # TWO is divisible by 2, but not 4
      "O % 2 = 0"
      "WO % 4 != 0"
      
      # the number of odd primes less than SIX is TWO
      "icount(primes.between(3, SIX - 1)) = TWO"
      
      --template="(ONE) (TWO) (SIX)"
      --answer="NOW"
      

      Solution: NOW = 820.

      There are two possible assignments of digits to letters:

      ONE = 289, TWO = 102, SIX = 564
      ONE = 289, TWO = 102, SIX = 567

      The next prime after 563 is 569, so we count 102 odd primes if X is 4 or 7.

      Like

      • Frits's avatar

        Frits 9:09 am on 24 January 2026 Permalink | Reply

        @Jim, your code allows N to be zero. Is this what you wanted?

        Like

    • ruudvanderham's avatar

      ruudvanderham 11:24 am on 23 January 2026 Permalink | Reply

      import peek
      import istr
      
      for o, n, e, t, w, s, i, x in istr.permutations(range(10), 8):
          if o * t * s != 0:
              if not (one := istr("=one")).is_prime() and one.is_odd() and one.is_square():
                  if (two := istr("=two")).is_divisible_by(2) and not two.is_divisible_by(4):
                      if len(istr.primes(3, (six := istr("=six")))) == two:
                          peek(one, two, six)
      

      Like

  • Unknown's avatar

    Jim Randell 7:54 am on 14 December 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 3299: Who wins mostly? 

    From The Sunday Times, 14th December 2025 [link] [link]

    The teams in our local football league play each other once during the season, getting three points for a win and one for a draw. On the last day of the season each team played a game and then I worked out the league table with the teams in alphabetical order. Here is part of three rows of that league table in which digits have consistently been replaced by letters, with different letters used for different digits.

    How many teams are there in the league and what number is LOST?

    [teaser3299]

     
    • Jim Randell's avatar

      Jim Randell 7:56 am on 14 December 2025 Permalink | Reply

      It is the end of the season, and so each team has played each of the other teams once. So, for each team, the sum of the “won”, “drawn” and “lost” values must be one less than the total number of teams in the league (and if each team plays a match on the last day, then the number of teams must be even).

      Also in order to win a game, the winning team must score at least one goal. So the value in “goals for” must be greater than or equal to “won”. And the value in “goals against” must be greater than or equal to “lost”.

      This is sufficient analysis to provide a single candidate solution to the puzzle.

      Here is a programmed solution using the [[ SubstitutedExpression ]] solver from the enigma.py library. (Which took me less than 2 minutes to write).

      It executes in 81ms, and the internal runtime of the generated code is 439µs.

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # end of the season
      "W + H + O == W + I + N == M + O + S"
      
      # 3 points for win, 1 point for a draw
      "3 * W + I = S"
      "3 * M + O = T"
      
      # "goals for" >= "won"; "goals against" >= "lost"
      "L >= M"
      "Y >= S"
      
      --answer="(W + H + O + 1, LOST)"
      --template=""
      

      Solution: There are 12 teams in the league. LOST = 3467.

      Like

    • Frits's avatar

      Frits 5:51 pm on 14 December 2025 Permalink | Reply

      # n = number of teams - 1 = number of games per team
      # 8 <= n = 3W + M + I + O   with 0 < W < 3 and I <= 5 (3W + I < Y)
      # n != 17 otherwise 
      #   if W = 1 no correct values exist for  {H, O, I, N} 
      #   if W = 2 then {H, O, I, N} = {6, 7, 8, 9} but 3W + I < 10 --> invalid
      
      dgts = set(range(10))
      # consider odd number of games per team
      for n in range(9, 17, 2):
        for W in range(1, 3):       # W != 3 as Y > (S = 3.W + I)
          for I in range(6):        # I != 6 as S < 9
            if len(n6 := dgts - {W, I, (N := n - W - I), (S := 3 * W + I)}) != 6:
              continue
            for M in range(1, 4):
              H, O, T = M + 2 * W + I, n - M - S, n + 2 * M - S
              if len(LY := list(n6 - {M, H, O, T})) != 2: continue
              # assert L > M, Y > S
              for L, Y in [LY, LY[::-1]]:
                if L <= M or Y <= S: continue
                LOST = 1000 * L + 100 * O + 10 * S + T  
                print(f"{n + 1} teams, {LOST = }")
      

      Like

  • Unknown's avatar

    Jim Randell 8:14 am on 21 November 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 2498: [Party trick] 

    From The Sunday Times, 8th August 2010 [link]

    Keith and I used to do a party trick. We had a set of cards labelled A, B, C, D, E, F, G and H. We would ask someone to choose three cards without Keith seeing. I would pass Keith one of the chosen cards, followed by a second. Amazingly, he would announce what the third chosen card was. Keith and I have phenomenal memories, so I then thought of a way to improve the trick. On each of the two occasions, I pass a card with either my left or right hand. Using this extra piece of trickery, we increased the number of cards in the set, ending with a letter way beyond H.

    What is the letter on the last card?

    This puzzle was originally published with no title.

    [teaser2498]

     
    • Jim Randell's avatar

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

      See also: Enigma 1214 (which was set by Keith Austin).

      There are 3 cards chosen, so the setter has P(3, 2) = 6 ways they can choose to present two of the cards to Keith. If each of these ways is used to identify the remaining card there can be up to 6 + 2 = 8 cards in the pack. And this is the initial scenario.

      In the second part of the puzzle each of the two passes can be made with the left (L) or right (R) hand, so each of the 6 ways of presenting the cards can have 4 variations (LL, LR, RL, RR), so there are now a total of 6 × 4 = 24 different ways of identifying the missing card. So there can be up to 26 cards in the pack. Hence:

      Solution: The last card has the letter Z on it.

      Like

  • Unknown's avatar

    Jim Randell 10:02 am on 11 November 2025 Permalink | Reply
    Tags: by: Victor Bryant,   

    Teaser 2481: [Coach trip] 

    From The Sunday Times, 11th April 2010 [link]

    My wife and I took a coast-to-coast coach tour of the USA, starting at New York and travelling steadily westwards to San Francisco. The coach had rows of seats, each row consisting of a double seat on the left and a double seat on the right. In New Jersey we were in the left-hand seat of the middle row. For each new state we moved two seats clockwise. Just as we were about to move to the best seat (front right) Clive – the courier – told us thereafter to move three seats clockwise for each state. But we did get the best seat later in the holiday.

    In which state were we sitting in the best seat?

    This puzzle was originally published with no title.

    [teaser2481]

     
    • Jim Randell's avatar

      Jim Randell 10:03 am on 11 November 2025 Permalink | Reply

      I’m not sure how this puzzle is meant to work, as it surely depends on the route taken.

      I tried using Google and Apple Maps to give routes from New York to San Francisco, and the 4 suggested routes visited the following states:

      NY → NJ → PA → OH → IN → IL → IA → NE → WY → UT → NV → CA (I80)
      NY → NJ → PA → WV → OH → IN → IL → MO → NE → WY → UT → NV → CA (I70/I80)
      NY → NJ → PA → WV → OH → IN → IL → MO → OK → TX → NM → AZ → CA (I40/I5)
      NY → NJ → PA → MD → WV → VA → TN → AR → OK → TX → NM → AZ → CA (I40/I5)

      And these are just the “direct” routes. On a coach tour it is quite possible that it takes a less direct route, and visits more states.

      This Python program considers possible numbers of seats on the coach and looks for configurations where the front right seat is missed the first time (when the increment changes from +2 to +3) but hit subsequently. It then checks what that means for the four routes given above.

      from enigma import (irange, seq_get, seq2str, printf)
      
      # possible routes from New York to San Francisco
      routes = list(x.split() for x in [
        "NY NJ PA OH IN IL IA NE WY UT NV CA", # I80
        "NY NJ PA WV OH IN IL MO NE WY UT NV CA", # I70/I80
        "NY NJ PA WV OH IN IL MO OK TX NM AZ CA", # I40/I5
        "NY NJ PA MD WV VA TN AR OK TX NM AZ CA", # I40/I5
      ])
      
      # suppose the coach has n rows in front of the middle row and n rows
      # behind it, so (2n + 1) rows in total, and (4n + 2) seats, which we
      # will number 0 .. 4n + 1, going clockwise starting from the left hand
      # seat, middle row
      
      def solve(n):
        # layout of the coach
        rows = 2 * n + 1
        seats = 2 * rows
        best = n + 1
        # we start in seat 0, and move 2 each state
        k = 0
        i = 2
        # consider visiting state j (starting at 1 = NJ)
        for j in irange(2, 50):
          # move to the next seat
          k = (k + i) % seats
          # are we going to get the best seat?
          if k == best:
            if i == 2:
              # we start to move 3 seats instead
              i += 1
              k += 1
            else:
              # we got the best seats in state j
              printf("{rows} rows; {seats} seats; best @ {j}")
              # check against the routes
              for route in routes:
                r = seq_get(route, j)
                if r is not None:
                  printf("-> {route} @ {r}", route=seq2str(route))
      
      # consider possible coach configurations
      for n in irange(1, 20):
        solve(n)
      

      There appear to be two possible scenarios:

      (1) In a coach with 7 rows of seats (= 28 individual seats).

      The seats are laid out as follows (viewed from above):

      [01] - [02] = front
      [03] - [04]
      [05] - [06]
      [07] - [08]
      [09] - [10]
      [11] - [12]
      [13] - [14] = back

      In NJ (state 1) the setter is in seat 07 and proceeds as follows:

      07 (+2) 03 (+3) 04 (+3) 10 (+3) 13 (+3) 07 (+3) 01 (+3) 06 (+3) 12 (+3) 11 (+3) 05 (+3) 02 [best]

      Which means being in the best seat in state 12.

      The shortest route given above only has 10 states after NJ, so is not possible, and for the remaining three routes this is the final state, California.

      (2) In a coach with 11 rows of seats (= 44 individual seats).

      The seats are laid out as:

      [01] - [02] = front
      [03] - [04]
      [05] - [06]
      [07] - [08]
      [09] - [10]
      [11] - [12]
      [13] - [14]
      [15] - [16]
      [17] - [18]
      [19] - [20]
      [21] - [22] = back

      In NJ (state 1) the setter is in seat 11 and proceeds as follows:

      11 (+2) 07 (+2) 03 (+3) 04 (+3) 10 (+3) 16 (+3) 22 (+3) 17 (+3) 11 (+3) 05 (+3) 02 [best]

      Which means being in the best seat in state 11.

      For the routes given this could be California, Nevada, or Arizona.

      And if the route were to visit more states there are further solutions.

      The next is in a coach with 23 rows of seats (= 92 individual seats), and happens in state 22 (which would require a less direct route, possibly revisiting states).


      If the list of states visited had been given as:

      NY → NJ → PA → OH → IN → IL → IA → NE → WY → UT → NV → CA (I80)

      (i.e. the most direct of the suggested routes).

      Then the only possible solution is a coach with 11 rows of seats, and the setter gets the best seat in state 11 = California.

      And the published solution is “California”, so perhaps this is what the setter had in mind.

      Like

  • Unknown's avatar

    Jim Randell 7:02 am on 2 November 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 3293: Three sisters 

    From The Sunday Times, 2nd November 2025 [link] [link]

    I have three granddaughters Jay, Kay and Elle. I set Jay and Kay a test and asked each of them to produce a list of positive numbers that used just nine digits [in total] with no digit occurring more than once. I wanted the majority of the numbers in each list to be odd and the majority of the numbers in each list to be perfect squares.

    Jay’s list (which contained more numbers than Kay’s) added up to give the year of Elle’s birth, whereas Kay’s list added up to give the year in which Elle will be 25.

    In one of the lists the highest number was a perfect square.

    What (in increasing order) were the numbers in that list?

    [teaser3293]

     
    • Jim Randell's avatar

      Jim Randell 8:36 am on 2 November 2025 Permalink | Reply

      Assuming the puzzle is set at the current time (i.e. during 2025). Then the latest Elle can be born is 2025, and if she has not reached her 25th birthday yet, the earliest she can be born is 2000. So Jay’s numbers must sum to between 2000 and 2025. (And Kay’s numbers sum to between 2025 and 2050).

      I also assumed that Jay and Kay completed their test successfully, and the lists of numbers they produced satisfied the given requirements.

      My first program was a bit slow. This one is a bit more involved, but both find the same answer.

      The following Python program runs in 225ms. (Internal runtime is 155ms).

      from enigma import (
        defaultdict, irange, powers, nsplit, seq_is_distinct, nconcat,
        divc, subsets, disjoint_union, intersect, cproduct, printf
      )
      
      # map squares to digit contents
      sq = dict()
      for s in powers(1, 45, 2):
        ds = nsplit(s)
        if seq_is_distinct(ds):
          sq[s] = ds
      
      # available digits
      digits = set(irange(0, 9))
      
      # allocate remaining digits <rs> for units digits <us>
      def allocate(us, rs, ns=list()):
        k = len(rs)
        if k == 1:
          # there should be 1 digit remaining unused
          yield us + ns
        elif k > 1 and us:
          # choose some digits to add to the next unit digit
          u = us[0]
          for ds in subsets(rs, max_size=3, select='P', fn=list):
            if ds and ds[0] == 0: continue
            n = nconcat(ds + [u])
            if 0 < n <= 2050:
              yield from allocate(us[1:], rs.difference(ds), ns + [n])
      
      # record candidate sequences for J and K
      (Js, Ks) = (defaultdict(set), defaultdict(set))
      
      # consider the length of the list
      for k in irange(3, 6):
        # majority size
        m = divc(k + 1, 2)
      
        # choose some squares
        for sqs in subsets(sq.keys(), size=m, fn=list):
          # digits used
          ds = disjoint_union(sq[s] for s in sqs)
          if not ds: continue
      
          # count odd numbers so far, and remaining digits
          odd = sum(s % 2 for s in sqs)
          rs = digits.difference(ds)
      
          # choose units digits for the remaining numbers
          for us in subsets(rs, size=k - m, fn=list):
            if odd + sum(u % 2 for u in us) < m: continue
            # allocate the remaining digits
            for ns in allocate(us, rs.difference(us)):
              ss = tuple(sorted(sqs + ns))
              t = sum(ss)
              # record candidates by birth year
              if 2000 <= t <= 2025: Js[t].add(ss)
              if 2025 <= t <= 2050: Ks[t - 25].add(ss)
      
      # find solutions
      for j in intersect([Js.keys(), Ks.keys()]):
        for (J, K) in cproduct([Js[j], Ks[j]]):
          if len(J) > len(K) and (max(J) in sq or max(K) in sq):
            printf("J={J} [{j}]; K={K} [{k}]", k=j + 25)
      

      Solution: The numbers are: 305, 784, 961.

      Jay’s list is:

      [4, 9, 625, 1387]; sum = 2025
      (4 numbers; 3 squares; 3 odd)

      So Elle was born this year (2025), and will be 25 in the year 2050.

      Kay’s list is:

      [305, 784, 961]; sum = 2050
      (3 numbers; 2 squares; 2 odd)

      In Kay’s list the highest number (961) is a square (= sq(31)), and so this list is the required answer.

      Like

    • Jim Olson's avatar

      Jim Olson 4:11 pm on 10 November 2025 Permalink | Reply

      I am perhaps missing something but I can’t see how the solution meets the requirements of the puzzle. Each list is supposed to be a list of positive numbers. Zero is not a positive number.

      Like

      • Jim Randell's avatar

        Jim Randell 4:33 pm on 10 November 2025 Permalink | Reply

        @Jim: The numbers in the lists are all positive ((4, 9, 625, 1387) and (305, 784, 961)), but that doesn’t mean the individual digits have to be non-zero. Many positive numbers are written using a 0 digit.

        Like

  • Unknown's avatar

    Jim Randell 6:11 am on 24 August 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 3283: Die hard? 

    From The Sunday Times, 24th August 2025 [link] [link]

    I have three identical standard dice (1 to 6 spots on each face, with 1 opposite 6, 2 opposite 5 and 3 opposite 4). I have placed them together in a row on a table and looked at the row from various sides. Regarding each face of a die as a digit (so that, for example, five spots would be regarded as a 5), I can read three 3-digit primes; one along the front of the row, one (the largest of the three) along the top of the row, and one along the back viewed from behind. Furthermore, the total number of  spots on the 11 visible faces is also a prime.

    What, in increasing order, are the three 3-digit primes?

    [teaser3283]

     
    • Jim Randell's avatar

      Jim Randell 6:26 am on 24 August 2025 Permalink | Reply

      See also: Teaser 2598.

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

      It assumes the dice are “right-handed”, but you get the same answer if you use “left-handed” dice.

      The following run-file executes in 93ms. (Internal runtime of the generated code is 498µs).

      Run: [ @codepad ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # layout (viewed from above):
      #
      #       I H G
      #     +-------+
      #   X | A B C | Y
      #     +-------+
      #       D E F
      
      --digits="1-6"
      --distinct="ADIX,BEH,CFGY"
      
      # check the top
      "is_prime(ABC)"
      "ABC > max(DEF, GHI)"
      
      # the front and back
      "D + I = 7" "E + H = 7" "F + G = 7"
      "is_prime(DEF)"
      "is_prime(GHI)"
      
      # total number of exposed spots is also prime
      "is_prime(sum([A, B, C, D, E, F, G, H, I, X, Y]))"
      
      # generate die corners (clockwise around a corner)
      --code="""
      def die_corners(x, y, z, k=7):
        for (y1, z1) in tuples([y, z, k - y, k - z, y], 2):
          for t in [(x, y1, z1), (k - x, k - z1, k - y1)]:
            yield from repeat(rotate, t, 2)
      """
      --code="corners = set(die_corners(3, 2, 1))"  # (3, 2, 1) = RH; (1, 2, 3) ]] = LH
      
      # check corner configurations
      "(A, D, X) in corners"
      "(A, X, I) in corners"
      "(C, Y, F) in corners"
      "(C, G, Y) in corners"
      
      --answer="ordered(ABC, DEF, GHI)"
      --template="(A B C) (D E F) (G H I) (X Y)"
      --solution=""
      

      Solution: The 3-digit primes are: 433, 443, 661.

      Here is a layout using right-handed dice:

      The sum of the spots on the exposed faces is 41.

      The die in the middle can be rotated through 180° to give another layout with 433 on the front and 443 on the back, but this doesn’t change the answer to the puzzle.

      With left-handed dice the layout is the same, but with the 5 on the left end and the 2 on the right end.

      Like

      • Jim Randell's avatar

        Jim Randell 9:25 am on 24 August 2025 Permalink | Reply

        Here is a Python program that finds the same layout(s).

        It has an internal runtime of 499µs.

        from enigma import (primes, nsplit, tuples, repeat, rotate, find, seq_items, chain, nconcat, printf)
        
        # generate die corners (clockwise around a corner) from example (x, y, z)
        def die_corners(x, y, z, k=7):
          for (y1, z1) in tuples([y, z, k - y, k - z, y], 2):
            for t in [(x, y1, z1), (k - x, k - z1, k - y1)]:
              yield from repeat(rotate, t, 2)
        
        # make map (x, y) -> z for (x, y, z) clockwise values round a corner
        # [use: (3, 2, 1) for RH dice; (1, 2, 3) for LH dice]
        corner = dict(((x, y), z) for (x, y, z) in die_corners(3, 2, 1))
        
        # look for 3-digit primes that can be formed using the digits 1-6
        digits = { 1, 2, 3, 4, 5, 6 }
        prs = list(ds for ds in map(nsplit, primes.between(111, 666)) if digits.issuperset(ds))
        
        # choose a prime for the front
        for (i, front) in enumerate(prs):
          # determine the number on the back
          back = tuple(7 - x for x in reversed(front))
          j = find(prs, back)
          if j == -1: continue
          # choose a larger number for the top
          for (_, top) in seq_items(prs, max(i, j) + 1):
        
            # check for valid dice
            xs = tuple(corner.get((t, f)) for (t, f) in zip(top, front))
            if None in xs: continue
        
            # left and right exposed faces
            (x, y) = (xs[0], corner.get((front[-1], top[-1])))
            # sum of exposed faces is a prime
            if not (sum(chain(top, front, back, (x, y))) in primes): continue
        
            # output solution
            ns = sorted(map(nconcat, (top, front, back)))
            printf("{ns} [top={top} front={front} back={back}; left={x} right={y}]")
        

        Like

    • Frits's avatar

      Frits 9:37 am on 24 August 2025 Permalink | Reply

      #       I H G
      #     +-------+
      #   X | A B C | Y
      #     +-------+
      #       D E F
       
      # given two dice faces anti-clockwise at a vertex, find the third
      # face at this vertex (using a western die if 'same' is True)
      def third_face(fi, se, same=True):
        f, s = int(fi), int(se)
        if s in {f, 7 - f}:
          raise ValueError
        oth = [i for i in range(1, 7) if i not in {f, s, 7 - f, 7 - s}]
        return oth[((f < s) == ((s - f) % 2)) == (same == (s + f > 7))]
       
      dgts = "123456"
      # opposite side dictionary
      opp = {str(i): str(7 - i) for i in range(1, 7)}  
          
      # determine valid 3-digit primes
      prms1 = {3, 5, 7}
      prms1 |= {x for x in range(11, 52, 2) if all(x % p for p in prms1)}
      prms = {str(i) for i in range(101, 667, 2) if all(i % p for p in prms1) and
              all(x in dgts for x in str(i))}
       
      # front and back: DEF and IHG
      fb = [(DEF, GHI[::-1]) for DEF in prms if DEF[0] not in "16" and
            (GHI := (''.join(opp[x] for x in DEF))[::-1]) in prms]
      
      # combine front DEF and back IHG with top ABC
      ftb = [(f, t, b) for t in prms for f, b in fb if t > f and t > b[::-1]
             and all(y not in {x, z} for x, y, z in zip(f, t, b))]       
      
      # determine side faces X and Y and check for prime total of 11 spots
      ftbxy = [(f, t, b, x, y) for (f, t, b) in ftb if 
               (x := third_face(f[0], t[0])) + (y := third_face(t[2], f[2])) +
               sum(sum(int(b) for b in a) for a in (f, b, t)) in prms1]
      
      sols = set(tuple(sorted([f, t, b[::-1]])) for f, t, b, _, _ in ftbxy)
      print("answer:", ' or '.join(str(s) for s in sols))
      

      Like

  • Unknown's avatar

    Jim Randell 10:46 am on 15 August 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 2492: [Paper cut] 

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

    I started with a rectangular piece of paper and made a [straight] cut across it, dividing it into a triangle and a pentagon. Then I discarded the triangle and measured the lengths of the sides of the pentagon, all of which were whole numbers of centimetres.

    I remember that the lengths of the shortest two sides [of the pentagon] were 17 cm and 19 cm, and that the length of the longest side was 33 cm.

    What were the lengths of the other two sides?

    This puzzle was originally published with no title.

    [teaser2492]

     
    • Jim Randell's avatar

      Jim Randell 10:46 am on 15 August 2025 Permalink | Reply

      To make a rectangle into a pentagon with a single cut, we cut one of the corners off, which means we discard a right-angled triangle. And all sides of the pentagon have integer lengths, which means so do the sides of the triangle, so its sides are a Pythagorean triple.

      This Python program runs in 60ms. (Internal runtime is 91µs).

      from enigma import (irange, cproduct, pythagorean_triples, ordered, printf)
      
      # the hypotenuse of the removed triangle cannot be more than 33
      for (x, y, z) in pythagorean_triples(33):
        if z < 17: continue  # or less than 17
        # consider possible rectangles (a, b)
        for (a, b) in cproduct([irange(x + 17, 33), irange(y + 17, 33)]):
          # the sides of the pentagon
          P = ordered(a, b, a - x, b - y, z)
          if not (P[0] == 17 and P[1] == 19 and P[-1] == 33): continue
          # output solution
          printf("rectangle={R} triangle={T} -> pentagon={P}", R=(a, b), T=(x, y, z))
      

      Solution: The remaining sides of the pentagon have lengths of 20 cm and 31 cm.

      The scenario is this:

      Like

    • Frits's avatar

      Frits 2:54 pm on 15 August 2025 Permalink | Reply

      from enigma import pythagorean_triples
      
      m1, m2, M = 17, 19, 33
       
      # the hypotenuse of the removed triangle cannot be more than M
      for (x, y, z) in pythagorean_triples(33):
        if z < m1: continue
        # consider possible rectangles (w, h)
        for w in range(m1 + x, M + 1):
          lo, mi, hi = sorted([w, w - x, z])
          if mi < m2: continue
          if {m1, m2, M}.isdisjoint({lo, mi, hi}): continue 
          if hi != M: 
            h_rng = [M] 
          elif lo != m1: 
            h_rng = [m1 + y] if m1 + y <= M else []
          else:
            h_rng = range(m1 + y, M + 1)
          
          for h in h_rng:
            # five sides of the pentagon
            s1, s2, s3, s4, s5 = sorted([lo, mi, hi, h, h - y])
            if not (s1 == m1 and s2 == m2 and s5 == M): continue
            print("answer:", [s3, s4])
      

      Like

  • Unknown's avatar

    Jim Randell 9:06 am on 5 August 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Brainteaser 1153: Teasing square 

    From The Sunday Times, 7th October 1984 [link]

    The diagram shows part of a magic square (a 3×3 array of positive whole numbers each of whose rows, columns and long diagonals adds up to the same total). But in this case only five of the numbers are shown. And each digit has been replaced by a letter, different letters representing different digits.

    To add to the squareness of this magic I can tell you also that the sum of each row is a perfect square.

    What is the full (numerical) version of the magic square?

    [teaser1153]

     
    • Jim Randell's avatar

      Jim Randell 9:07 am on 5 August 2025 Permalink | Reply

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

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # [ @a | @b | @c ]
      # [ BR | AI |  N ]
      # [ TE |  A | @d ]
      
      # magic sum is a square number
      --macro="@sum = (3 * AI)"
      "is_square(@sum)"
      
      # missing values are positive integers
      --macro="@a = (@sum - BR - TE)"  # @a + BR + TE == @sum  [col 1]
      --macro="@b = (2 * AI - A)"      # @b + AI + A  == @sum  [col 2]
      --macro="@c = (2 * AI - TE)"     # @c + AI + TE == @sum  [diag 2]
      --macro="@d = (@sum - TE - A)"   # TE +  A + @d == @sum  [row 3]
      "@a > 0" "@b > 0" "@c > 0" "@d > 0"
      
      # remaining rows / cols / diags give the magic sum
      "@a + @b + @c == @sum"  # [row 1]
      "BR + AI + N == @sum"   # [row 2]
      "@c + N + @d == @sum"   # [col 3]
      "@a + AI + @d == @sum"  # [diag 1]
      
      --answer="((@a, @b, @c), (BR, AI, N), (TE, A, @d))"
      --output="lambda p, s, ans: print(ans)"
      

      Solution: The completed square is:

      And so the magic constant is 81.

      Which would correspond to: [X, XA, AB], [BR, AI, N], [TE, A, BY] for some unused symbols X and Y.

      Like

    • Frits's avatar

      Frits 2:07 pm on 5 August 2025 Permalink | Reply

      Minimising the number of iterations.

      # magic constant M = 3 * AI or AI = 3 * k^2
      for AI in [3 * k * k for k in range(2, 6)]:
        A, I = divmod(AI, 10)
        # A can never be equal to I as AI is not a multiple of 11
        M = 3 * AI
        sum2 = M - AI
        # BR + N = sum2 
        if sum2 > 105: continue
        # BR = A + 2.(c33 - AI) so R and A have the same parity
        # (sum2 - N) has same parity as A so N must have the same parity as A
        for N in set(range(A % 2, 10, 2)) - {A, I}: 
          B, R = divmod(sum2 - N, 10)
          if not (0 < B < 10) or not {A, I, N}.isdisjoint({B, R}): continue
          c33 = ((BR := 10 * B + R) - A + sum2) // 2
          T, E = divmod(M - c33 - A, 10)
          if not (0 < T < 10) or not {A, I, N, B, R}.isdisjoint({T, E}): continue
          c11 = sum2 - c33
          c12 = sum2 - A
          c13 = sum2 - (TE := 10 * T + E)
          
          mat = [(c11, c12, c13), (BR, AI, N), (TE, A, c33)]
          # double check matrix
          if not all(sum(r) == M for r in mat): continue
          if len({sum2, c11 + c33, TE + c13}) > 1: continue
          if not all(sum(r) == M for r in zip(*mat)): continue
          for c1, c2, c3 in mat:
            print(f"|{c1:>2} | {c2:>2} | {c3:>2} |")
      

      Like

    • GeoffR's avatar

      GeoffR 12:22 pm on 6 August 2025 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      %   w   x   y
      %   BR  AI  N
      %   TE  A   z
      
      predicate is_sq(var int: y) =
      exists(z in 1..ceil(sqrt(int2float(ub(y))))) (z*z = y );
      
      var 1..9:A; var 1..9:B; var 1..9:T; var 1..9:N; 
      var 0..9:R; var 0..9:I; var 0..9:E;
      var 10..99:BR; var 10..99:AI; var 10..99:TE;
      var 1..99:w; var 1..99:x; var 1..99:y; var 1..99:z;
      var 20..210:m_const;
      
      constraint all_different ([B, R, A, I, N, T, E]);
      constraint BR = 10*B + R /\ AI = 10*A + I /\ TE = 10*T + E;
      
      % All rows are squares
      constraint is_sq(BR + AI + N) /\ is_sq(TE + A + z) /\ is_sq(w + x + y);
      
      constraint m_const  == BR + AI + N;
      
      % Check conditions for a magic square
      constraint m_const == w + x + y /\ m_const == TE + A + z
      /\ m_const == w + BR + TE /\ m_const == x + AI + A /\ m_const == y + N + z
      /\ m_const == w + AI + z /\ m_const == y + AI + TE;
      
      solve satisfy;
      
      output[ show([w, x, y]) ++ "\n" ++ show([BR, AI, N]) ++ "\n" ++ show([TE, A, z]) ];
      
      % [5, 52, 24]
      % [46, 27, 8]
      % [30, 2, 49]
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

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

    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: by: Victor Bryant   

    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 10:55 am on 4 July 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    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:26 am on 24 June 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    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 7:51 am on 17 June 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    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: by: Victor Bryant   

    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 7:48 am on 29 May 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 2537: Odds and evens 

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

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

    What are the two three-digit numbers?

    [teaser2537]

     
    • Jim Randell's avatar

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

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

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

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

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

      The completed multiplication sum looks like this:

      Like

    • ELBAZ HAVIV's avatar

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

      Hello Jim

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

      from the original puzzle

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

      Thank you.

      Like

      • Jim Randell's avatar

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

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

        So instead of doing:

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

        We just do:

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

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

        Like

    • ELBAZ HAVIV's avatar

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

      Hello again

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

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

      Thank you.

      Like

  • Unknown's avatar

    Jim Randell 9:06 am on 20 May 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 2522: [Letter values] 

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

    I have numbered the letters of the alphabet from 1 to 26, but not in alphabetical order, giving each letter its own unique value. So, for any word, I can work out its value by adding up the values of its letters. In this way, I have found some words with equal values. For example, the following nine words all have the same value:

    I
    SAY
    ALL
    THESE
    HERE
    HAVE
    EQUAL
    VALUES
    TOO

    What are the values of Y, O, U, R, S?

    This puzzle was originally published with no title.

    [teaser2522]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 20 May 2025 Permalink | Reply

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

      It runs in 356ms. (Internal runtime of the generated code is 206ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # use values 1-26
      --base=27
      --digits="1-26"
      
      # all the words give sums equal to I
      "S + A + Y = I"
      "A + L + L = I"
      "T + H + E + S + E = I"
      "H + E + R + E = I"
      "H + A + V + E = I"
      "E + Q + U + A + L = I"
      "V + A + L + U + E + S = I"
      "T + O + O = I"
      
      --template=""
      --answer="(Y, O, U, R, S)"
      

      Solution: Y = 20; O = 10; U = 3; R = 8; S = 2.

      The complete list of values determined are:

      A = 4
      E = 1
      H = 16
      I = 26
      L = 11
      O = 10
      Q = 7
      R = 8
      S = 2
      T = 6
      U = 3
      V = 5
      Y = 20

      Like

  • Unknown's avatar

    Jim Randell 7:14 am on 4 May 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 3267: Leaf-eaters 

    From The Sunday Times, 4th May 2025 [link] [link]

    My encyclopaedia consists of many volumes, each containing the same number (over 40) of leaves. (Each leaf has a page number on each side. With n leaves Volume 1 would have pages 1 to 2n, Volume 2 would have pages 2n+1 to 4n, etc.) I keep the encyclopaedia on one long shelf so that on their spines I can read “Volume 1”, “Volume 2”, etc, from left to right.

    A voracious bookworm started at the left-hand end of the shelf and ate through some covers and leaves, finishing by eating through the leaf with a page number double the number of leaves it had eaten through. Meanwhile, another bookworm started at the right-hand end of the shelf and ate through twice as many leaves as the first bookworm. Then in two of the volumes, the percentage of the nibbled leaves was equal to the volume number.

    How many volumes are there in the set? And how many leaves per volume?

    [teaser3267]

     
    • Jim Randell's avatar

      Jim Randell 8:57 am on 4 May 2025 Permalink | Reply

      See also: Enigma 660, Tantalizer 386. (In particular the note about page numbering).

      If the worms pass each other, then 100% of the pages in each volume will have been nibbled, and the only possibility in this case is that vol 100 is 100% nibbled (providing there are at least 100 volumes).

      However if they both reach partially into the same volume (from opposite sides), then we could have a situation where this volume has a smaller percentage nibbled, while all the remaining volumes are 100% nibbled, so if we have at least 100 volumes, this could lead to a viable scenario.


      For volumes with n leaves per volume. If worm 1 nibbles v whole volumes, and x (= 1 .. n) additional leaves in the final volume it visits, then the total number of leaves nibbled is:

      t1 = nv + x

      and the even page number of the final leaf nibbled is:

      p1 = 2n(v + 1) − 2(x − 1)

      and this page number is twice the number of leaves nibbled, hence:

      2n(v + 1) − 2(x − 1) = 2(nv + x)

      x = (n + 1) / 2

      Hence n must be an odd number. (This effectively eliminates worm 1 from making the other partially nibbled volume by itself).

      And worm 2 must nibble twice as many leaves:

      t2 = 2(nv + x)
      t2 = (2v + 1)n + 1

      Hence worm 2 nibbles (2v + 1) whole volumes and 1 additional page of volume (v + 1). (And this effectively eliminates worm 2 from making the other partially nibbled volume by itself).

      We want there to be at least 100 volumes, and so:

      k = v + (2v + 1) + 1
      k ≥ 100 ⇒ v ≥ 33

      And the volume nibbled from both ends (v + 1) must have the percentage of its pages nibbled that is the number of the volume.

      100 (x + 1) / n = v + 1

      n(v − 49) = 150

      So we can determine the solution by looking at divisor pairs of 150, such that n is greater than 40 and odd. It turns out there is only one possibility:

      n = 75, v = 51 → x = 38, p = 52%

      Programatically:

      from enigma import (divisors_pairs, div, printf)
      
      # consider possibilities for n = leaves per volume (odd divisor of 150)
      for (v, n) in divisors_pairs(150):
        if not (n > 40): break
        if not (n % 2 == 1): continue
        v += 49  # v = number of whole volumes nibbled by worm 1
        x = div(n + 1, 2)  # x = number of extra leaves nibbled by worm 1
        p = v + 1  # p = volume where percentage nibbled = volume number
        v2 = 2 * v + 1  # v2 = number of whole volumes nibbled by worm 2
        x2 = 1  # x2 = number of extra leaves nibbled by worm 2
        k = v + v2 + 1  # k = total number of volumes
        # output solution
        printf("n={n} k={k} [v={v} x={x} -> v2={v2} x2={x2} p={p}%]")
      

      Solution: There are 155 volumes in the set. And 75 leaves per volume.

      Each volume has 150 pages, and the complete set consists of 23250 pages.

      Worm 1 has eaten through vols 1 → 51, and 38 leaves of vol 52, so the last leaf consumed is pages 7725/7726. The total number of leaves consumed is 51×75 + 38 = 3863, and 3863×2 = 7726.

      Worm 2 starts from the other end of the shelf and eats through 7726 leaves, which is 103 volumes and 1 leaf. It eats through vols 155 → 53, and 1 leaf of vol 52 (= page 7651/7652).

      So vol 52 has had 38 leaves eaten by worm 1 and 1 leaf eaten by worm 2, for a total of 39/75 leaves = 52% of the leaves in the volume. And this percentage is the same as the volume number.

      The remaining volumes, vols 1-51 and 53-155, have been eaten through entirely by worm 1 or worm 2 respectively. In particular vol 100 has been eaten through 100% by worm 2.

      And so there are exactly 2 volumes where the percentage of leaves eaten is equal to the volume number.

      Like

      • Frits's avatar

        Frits 4:37 pm on 4 May 2025 Permalink | Reply

        I have gotten a similar manual solution, I didn’t want to publish it. The formula and variables are based on Jim’s original program.

         
        # n = 2x - 1
        # percentage 100(x + x2) = n(v + 1)
        # 50(n + 1) = n.v + n - 100x2
        # v = 49 + (100.x2 + 50) / n
        
        # 2t/n = (2nv + 2x) / n = 2v + (n + 1) / n = 2v + 1 + 1/n
        # so v2 = 2v + 1 and x2 must be 1
        
        # x2 = 1 implies v = 49 + 150 / n
        # as n must be odd and > 40 only one value remains for n
        # all other values can now be calculated.
        

        Like

      • Frits's avatar

        Frits 6:48 pm on 4 May 2025 Permalink | Reply

        We should also prove that there is/are no solution(s) if the percentage of the nibbled leaves for worm 1 was equal to the volume number (v < 100).
        As we have v = 50 + 50 / n this can only happen for n = 50 (not odd).

        So we can disregard this option.

        Like

  • Unknown's avatar

    Jim Randell 7:13 am on 6 April 2025 Permalink | Reply
    Tags: by: Victor Bryant   

    Teaser 3263: Snakes and ladders 

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

    My “Snakes and Ladders” board has numbers from 1 to 100 — you throw a die repeatedly and work your way up. There are three “snakes”, each taking you down from one perfect square to another, and three “ladders”, each taking you up from one prime to another. The twelve ends of the snakes and ladders are different two-digit numbers. You must go down a snake if you land on its top-end, and up a ladder if you land on its bottom-end.

    For any number from 1 to 6, if I throw that same number repeatedly then I eventually land on 100. The number of throws of 4 needed (with first stops 4, 8, …) is one more than when throwing 5s, which is more than when throwing 3s.

    What are the three ladders (in the form “p to q“)?

    [teaser3263]

     
    • Jim Randell's avatar

      Jim Randell 7:47 am on 6 April 2025 Permalink | Reply

      Here is my first go at a solution.

      It constructs all possible arrangements of snakes and ladders, and then checks to see if valid sequences of moves can be made. This is quite slow, as although there are only a few possible arrangements for the snakes, there are a lots of possible ladder arrangements to consider.

      It runs in 22.8s (using PyPy).

      from enigma import (irange, primes, subsets, partitions, update, printf)
      
      # play the game, constantly throwing <d>
      # return: <sequence-of-positions> or None
      def play(d, m, p=0):
        ps = list()
        while True:
          # move d spaces
          p += d
          # have we hit a special position?
          p = m.get(p, p)
          # have we hit a loop?
          if p in ps: return
          ps.append(p)
          # are we done?
          if p >= 100: return ps
      
      # check map <m> of special positions for a solution
      def check(m):
        n = dict()
        for d in [4, 5, 3, 6, 2, 1]:
          # each play eventually gets to 100
          ps = play(d, m)
          if ps is None or ps[-1] != 100: return
          n[d] = ps
          # check n4 = n5 + 1
          if d == 5 and not (len(n[4]) == len(n[5]) + 1): return
          # check n3 < n5
          if d == 3 and not (len(n[3]) < len(n[5])): return
        # return the moves for each die value
        return n
      
      # collect 2-digit squares and primes
      sqs = list(i * i for i in irange(4, 9))
      prs = list(primes.between(10, 99))
      
      # choose k special positions
      def special(ss, k, r, m):
        # choose k positions
        for xs in subsets(ss, size=k):
          # pair them up
          for ps in partitions(xs, 2, distinct=1):
            # add them to the map
            yield update(m, (sorted(pair, reverse=r) for pair in ps))
      
      # choose 6 ends for the snakes (squares) and ladders (primes)
      for m0 in special(sqs, 6, 1, dict()):
        for m in special(prs, 6, 0, m0):
          # check for solution
          n = check(m)
          if n:
            # output solution
            for (x, y) in m.items():
              printf("{x} -> {y} {z}", z=("ladder" if x < y else "snake"))
            printf()
            for d in sorted(n.keys()):
              printf("{d} -> {k} throws {v}", k=len(n[d]), v=n[d])
            printf()
      

      Solution: The ladders are: 19 → 97, 29 → 73, 31 → 43.

      And the snakes are: 36 → 25, 49 → 16, 81 → 64.

      The number of throws for each die value are as follows:

      1 = 22 throws
      2 = 42 throws
      3 = 18 throws
      4 = 21 throws
      5 = 20 throws
      6 = 22 throws

      With a standard board layout it looks like this:

      (Snakes in red; Ladders in green).

      Like

      • Jim Randell's avatar

        Jim Randell 2:41 pm on 8 April 2025 Permalink | Reply

        Here is a faster approach:

        This Python 3 program starts by repeatedly throwing a die value and travelling along the board. When it hits a potential “special” square it either allocates a snake/ladder or does nothing, until it completes a sequence that ends on 100. Once this is achieved it takes the partially completed board and then tries another die value, until all values have achieved valid sequences. The completed board (and sequences) are then returned.

        It runs in 115ms. (Internal runtime is 40ms).

        from enigma import (irange, primes, update, remove, printf)
        
        # collect 2-digit squares and primes
        sqs = list(i * i for i in irange(4, 9))
        prs = list(primes.between(10, 99))
        
        # play the game, repeatedly throwing <d>
        # m = behaviour of positions (src -> tgt)
        # p = current position
        # ps = previous positions
        # sps = positions for snakes
        # lps = positions for ladders
        # ns = remaining snakes
        # nl = remaining ladders
        # return updated (m, ps, sps, lps, ns, nl) values
        def play(d, m, p, ps, sps, lps, ns, nl):
          # are we done?
          if p >= 100:
            yield (m, ps, sps, lps, ns, nl)
          else:
            # move <d> spaces
            p += d
            x = m.get(p)
            if x is not None:
              # behaviour of position is already allocated
              if x not in ps:
                yield from play(d, m, x, ps + [x], sps, lps, ns, nl)
            else:
              # decide the behaviour of this position, either:
        
              # -> p is not a special square
              if p not in ps:
                yield from play(d, update(m, [(p, p)]), p, ps + [p], sps, lps, ns, nl)
        
              # -> p is the bottom of a ladder
              if nl > 0 and p in lps:
                # move to a higher ladder position
                for x in lps:
                  if not (x > p): continue
                  if x not in ps:
                    yield from play(d, update(m, [(p, x)]), x, ps + [x], sps, remove(lps, p, x), ns, nl - 1)
        
              # -> p is the top of a snake
              if ns > 0 and p in sps:
                # move to a lower snake position
                for x in sps:
                  if not (x < p): break
                  if x not in ps:
                    yield from play(d, update(m, [(p, x)]), x, ps + [x], remove(sps, p, x), lps, ns - 1, nl)
        
        # solve the puzzle for repeated throws in <ds>
        # m = behaviour of positions (src -> tgt)
        # sps = positions for snakes
        # lps = positions for ladders
        # ns = remaining snakes
        # nl = remaining ladders
        # ss = sequences recorded
        # return (m, ss)
        def solve(ds, m, sps, lps, ns, nl, ss=dict()):
          # are we done?
          if not ds:
            yield (m, ss)
          else:
            # try the next sequence
            d = ds[0]
            for (m_, ps_, sps_, lps_, ns_, nl_) in play(d, m, 0, [], sps, lps, ns, nl):
              # check the sequence ends on 100
              if not (ps_[-1] == 100): continue
              ss_ = update(ss, [(d, ps_)])
              # check n4 = n5 + 1
              if d == 4 or d == 5:
                (ss4, ss5) = (ss_.get(4), ss_.get(5))
                if ss4 and ss5 and not (len(ss4) == len(ss5) + 1): continue
              # check n3 < n5
              if d == 3 or d == 5:
                (ss3, ss5) = (ss_.get(3), ss_.get(5))
                if ss3 and ss5 and not (len(ss3) < len(ss5)): continue
              # solve for the remaining sequences
              yield from solve(ds[1:], m_, sps_, lps_, ns_, nl_, ss_)
        
        # solve for repeated throws 1 - 6, with 3 snakes on squares, 3 ladders on primes
        for (m, ss) in solve([6, 5, 4, 3, 2, 1], dict(), sqs, prs, 3, 3):
          # output layout
          for s in sorted(m.keys()):
            t = m[s]
            if s < t: printf("{s} -> {t} ladder")
            if s > t: printf("{s} -> {t} snake")
          printf()
          # output sequences
          for d in sorted(ss.keys()):
            ts = ss[d]
            printf("{d} -> {n} throws {ts}", n=len(ts))
          printf()
        

        Like

    • Frits's avatar

      Frits 6:25 pm on 7 April 2025 Permalink | Reply

      Similar to Jim’s program.

      It runs under 10 seconds (using PyPy).

      # -------------------------------- start Jim Randell ------------------------
      # play the game starting with list <lst>, constantly throwing <d>
      # return: <sequence-of-positions> or None
      def play(d, m, lst):
        ps = list(lst)
        p = lst[-1]
        while True:
          # move d spaces
          p += d
          # have we hit a special position?
          p = m.get(p, p)
          # have we hit a loop?
          if p in ps: return
          ps.append(p)
          # are we done?
          if p >= 100: return ps    
      
      # check map <m> of special positions for a solution
      def check_sol(m):
        n = dict()
        for d in [4, 5, 3, 6, 2, 1]:
          # each play eventually gets to 100
          ps = play(d, m, f_lst[d])
          if ps is None or ps[-1] != 100: return
          n[d] = ps
          # check n4 = n5 + 1
          if d == 5 and not (len(n[4]) == len(n[5]) + 1): return
          # check n3 < n5
          if d == 3 and not (len(n[3]) < len(n[5])): return
        # return the moves for each die value
        return n
      # -------------------------------- end Jim Randell --------------------------
        
      prms = [x for x in range(11, 100, 2) if all(x % p for p in [3, 5, 7])]
      sqs = [i * i for i in range(4, 10)]  # 2-digit squares
      spec = prms + sqs
      
      # mandatory first numbers 
      f_lst = {n: list(range(n, next(x for x in range(n, 100, n) if x in spec and 
                        x not in {sqs[0], prms[-1]}), n)) for n in range(1, 7)}
      
      # generate all possible combinations of 3 ladders
      ladders = []
      inds = {x: i for i, x in enumerate(prms)}
      for P in prms[:-3]:
        for Q in prms[inds[P] + 1:]:
         
          for R in prms[inds[P] + 1:-2]:
            if R == Q: continue
            for S in prms[inds[R] + 1:]:
              if S == Q: continue
              
              for T in prms[inds[R] + 1:-1]:
                if T in {Q, S}: continue
                for U in prms[inds[T] + 1:]:
                  if U in {Q, S}: continue
                  ladders.append((P, Q, R, S, T, U))
              
      # generate all possible combinations of 3 snakes
      for a in range(5, 9):
        A = a * a
        for b in range(4, a):
          B = b * b
          for c in range(a + 1, 9):
            C = c * c
            for d in range(4, c):
              if d in {a, b}: continue
              D = d * d
              e = 9
              E = e * e
              f = 39 - a - b - c - d - e
              if f in {a, b, c, d}: continue
              F = f * f
              # create snake dictionary
              d = {A: B, C: D, E: F}
              for lddr in ladders:
                d_ = d.copy()
                # add ladder data to dictionary
                d_.update({lddr[i]: lddr[i + 1] for i in range(0, 6, 2)})
                # check for solution 
                if (n := check_sol(d_)) is None: continue
                print("answer:", *[lddr[i:i+2] for i in range(0, 6, 2)])
      

      Like

    • Frits's avatar

      Frits 7:13 pm on 8 April 2025 Permalink | Reply

      New approach, again without using analysis.
      The main line can probably done with recursion but I didn’t feel like doing that.

      It run in approx. 18ms (using Cpython).

      prms = [x for x in range(11, 100, 2) if all(x % p for p in [3, 5, 7])]
      sqs = [i * i for i in range(4, 10)]  # 2-digit squares
      spec = prms + sqs
      
      # dictionary of mandatory last numbers 
      dlast = {n: tuple(range(next(x for x in range(100 - n, 0, -n) if x in spec and 
                  x not in {sqs[-1], prms[0]}), 100 + n, n)) for n in range(1, 7)}
      
      # dictionary of mandatory first numbers
      dfirst = {n: tuple(range(n, next(x for x in range(n, 100, n) if x in spec and 
                         x not in {sqs[0], prms[-1]}), n)) for n in range(1, 7)}
      
      # go from number <a> to <b> by adding a maximum of <k> board numbers
      # between <a> and <b> with standard throws <m> and <kn> = known snakes/ladders
      def solve(m, a, b, k, ns=0, nl=0, kn=set(), ss=[], new=set()):
        tst = (11, 83) in kn | new
        # <k> fields cause <k + 1> moves
        if k == -1 or a == b:
          if a == b:
            yield len(ss) - 1, kn | new, ns, nl
        else:
          # new number may not have been seen before
          if (n := a + m) in ss or n > 100: return
          # try standard throw if not on a kn snake of ladder 
          if n not in {x for x, y in kn}:
            yield from solve(m, n, b, k - 1, ns, nl, kn, ss + [n], new)
          
          # try to add a snake  
          if n in sqs:
            for s in sqs:
              if s == n: break 
              # do checks for new snake
              if (n, s) not in kn:
                if ns > 2 or not set(sum(kn | new, ())).isdisjoint({n, s}):
                  continue
                ns_ = ns + 1
              else:
                ns_ = ns
              yield from solve(m, s, b, k - 1, ns_, nl, kn, ss + [s], new | {(n, s)})
          # try to add a ladder
          if n in prms:
            for p in prms[::-1]:
              if p == n: break 
              # do checks for new ladder
              if (n, p) not in kn:
                if nl > 2 or not set(sum(kn | new, ())).isdisjoint({n, p}):
                  continue
                nl_ = nl + 1
              else:
                nl_ = nl  
              yield from solve(m, p, b, k - 1, ns, nl_, kn, ss + [p], new | {(n, p)})
      
      def six_throws(seq, ln5=0, ns=0, nl=0, kn=set(), ss=[]):
        if not seq:
          if len(kn) == 6:
            yield kn
        else:
          m = seq[0]
          mx = (107 if m not in {3, 4} else
                ln5 + (2 * m - 7) - len(dfirst[m]) - len(dlast[m]))
          
          for ln, kn_, ns_, nl_ in solve(m, dfirst[m][-1], dlast[m][0], 
                                         mx, ns, nl, kn):
            if m == 4: # check requirement
              if len(dfirst[m]) + len(dlast[m]) + ln != ln5 + 1: continue
              
            if m == 5: # save number of throws of 5 needed
              ln5 = len(dfirst[m]) + len(dlast[m]) + ln
            
            yield from six_throws(seq[1:], ln5 , ns_, nl_, kn_, ss)
              
      # perform 2 passes as some ordr's generate incorrect solutions
      ordr = (5, 4, 3, 6, 2, 1)  # one of the most efficient orders
      # 1st pass: start with no known snakes and ladders
      for p1 in six_throws(ordr):
        # 2nd pass: check again with a full set of snakes and ladders
        sols = []
        for p2 in six_throws(ordr, 0, 3, 3, p1):
          if p2 not in sols:
            if p1 != p2: 
              print("ERROR")
              exit(0)
            sols.append(p2) 
            
      for s in sols:
        print("answer:", *[(x, y) for x, y in sorted(s) if x not in sqs])
      

      Like

  • Unknown's avatar

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

    Teaser 2559: Inconsequential 

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

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

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

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

    What are my two numbers?

    [teaser2559]

     
    • Jim Randell's avatar

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

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

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

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

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

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

      Solution: The numbers are: 4, 536870912.

      Where:

      2^2 = 4
      2^29 = 536870912

      Like

  • Unknown's avatar

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

    Teaser 2564: 11/11/11 

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

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

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

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

    What is the eight-figure number?

    [teaser2564]

     
    • Jim Randell's avatar

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

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

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

      Solution: The number is: 76398245.

      Like

    • Frits's avatar

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

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

      Like

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