Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • 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

  • Unknown's avatar

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

    Teaser 2541: Lotto luck 

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

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

    How much did Chris keep?

    [teaser2541]

     
    • Jim Randell's avatar

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

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

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

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

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

      Solution: Chris kept £ 1019.

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

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

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


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

      We find the remainder after step k is:

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

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

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

      1024 W − 3125 R = 10505

      where: 0 < R < W < 5000.

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

      Here is a general solver for this kind of equation:

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

      Which we can then use to solve the puzzle:

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

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

      Like

      • Hugo's avatar

        Hugo 10:54 am on 9 January 2026 Permalink | Reply

        A slightly different way of looking at it:

        He borrows £5 from his wife, so the amount to be distributed is £3125 (= 5^5).
        The eldest child gets a fifth of that, and each successive child four fifths as much as the previous child, being a fifth of what is left. He ends up with £1024 (= 4^5), from which he can repay his wife.

        There are further solutions with an integer times as much, always including the borrowed £5.

        If there were six children, the smallest solution would be £15625 (= 5^6),
        if only four then £625 (= 5^4), in each case including the borrowed £5.

        It’s not hard to come up with variants in which each child gets a quarter, or a sixth, or some other fraction of what is left.

        Like

    • John Crabtree's avatar

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

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

      Like

  • Unknown's avatar

    Jim Randell 6:21 am on 1 June 2025 Permalink | Reply
    Tags:   

    Teaser 3271: Uncut diamonds 

    From The Sunday Times, 1st June 2025 [link] [link]

    Andrew was building a rhombus-shaped patio. He could have paved it with equilateral triangular slabs with 1ft edges, but he wanted a layout with a hexagonal slab in place of six triangular ones at various places. He considered two layouts that were symmetrical in two perpendicular directions:

    Layout 1: every triangular slab touches the perimeter of the patio in at least one point.

    Layout 2: each group of three adjacent hexagonal slabs encloses exactly one triangular one.

    The triangular and hexagonal slabs come in boxes of 12, and Andrew chose layout 1 because he would only need a third as many boxes of triangular slabs as layout 2.

    What length were the sides of the patio and how many slabs in total would be left over?

    [teaser3271]

     
    • Jim Randell's avatar

      Jim Randell 7:13 am on 1 June 2025 Permalink | Reply

      Once you have determined how the two layouts work (isometric graph paper helps), this is a relatively straightforward puzzle.

      Here are the two layouts on a rhombus with sides of size 8:

      (Layout 1 uses 17 hexes and 26 tris. Layout 2 uses 16 hexes and 32 tris. Note that there are tris in Layout 2 that are not surrounded by 3 hexes).

      The following Python program generates increasing sizes for each layout, and determines the (side, hexes, tris) for each case, and then looks for two layouts with the same size where layout 1 requires 1/3 the number of boxes of triangular tiles as layout 2.

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

      from enigma import (irange, inf, sq, intersect, divc, printf)
      
      # generate (<side>, <hexes>, <tris>) for layout 1
      def layout1(M):
        # consider number of hexagons on the long diagonal
        for n in irange(1, inf):
          s = n + 1
          if s > M: break
          h = 2 * sum(irange(n, 1, step=-3)) - n
          t = 2 * sq(s) - 6 * h
          yield (s, h, t)
      
      # generate (<side>, <hexes>, <tris>) for layout 2
      def layout2(M):
        # there is a sq(n) array of hexagons
        for n in irange(1, inf):
          s = 2 * n
          if s > M: break
          h = sq(n)
          t = 2 * sq(s) - 6 * h
          yield (s, h, t)
      
      # collect (h, t) by s for each layout
      M = 100
      (d1, d2) = (dict(), dict())
      for (s, h, t) in layout1(M): d1[s] = (h, t)
      for (s, h, t) in layout2(M): d2[s] = (h, t)
      
      # calculate boxes and left overs
      def boxes(n, k=12):
        b = divc(n, k)
        return (b, k * b - n)
      
      # examine common side lengths
      for s in sorted(intersect([d1.keys(), d2.keys()])):
        ((h1, t1), (h2, t2)) = (d1[s], d2[s])
        # check for layout 1 needing 1/3 as many boxes of tris as layout 2
        ((b1, r1), (b2, r2)) = (boxes(t1), boxes(t2))
        if 3 * b1 == b2:
          # output solution
          printf("s={s}: layout 1 = {h1} hex + {t1} tri; layout 2 = {h2} hex + {t2} tri")
          printf("-> tri: layout 1 = {b1} boxes (rem = {r1}); layout 2 = {b2} boxes (rem = {r2})")
          ((b1, r1), (b2, r2)) = (boxes(h1), boxes(h2))
          printf("-> hex: layout 1 = {b1} boxes (rem = {r1}); layout 2 = {b2} boxes (rem = {r2})")
          printf()
      

      Solution: The sides of the patio were 24 ft. There are 9 slabs left over.

      Layout 1 uses 177 hex slabs (= 15 boxes, with 3 unused), and 90 tri slabs (= 8 boxes, with 6 unused).

      Layout 2 uses 144 hex slabs (= 12 boxes, with 0 unused), and 288 tri slabs (= 24 boxes, with 0 unused).

      Like

      • Jim Randell's avatar

        Jim Randell 9:45 am on 1 June 2025 Permalink | Reply

        Some analysis gives a shorter program:

        For a rhombus with side s, the number of hexagons in each of the layouts is:

        layout 1: h1 = ceil(sq(s − 1)/3), where s ≥ 2
        layout 2: h2 = sq(s/2), where s is even

        The following program considers increasing even length sides of the rhombus, until a solution is found.

        from enigma import (irange, inf, sq, divc, ediv, printf)
        
        # calculate boxes and left overs
        def boxes(n, k=12):
          b = divc(n, k)
          return (b, k * b - n)
        
        # consider possible sides of the rhombus
        for s in irange(2, inf, step=2):
          n = 2 * sq(s)  # number of triangular cells
          # calculate the number of hex tiles
          (h1, h2) = (divc(sq(s - 1), 3), sq(ediv(s, 2)))
          # calculate the number of tri tiles
          (t1, t2) = (n - 6 * h1, n - 6 * h2)
          # calculate number of boxes of tri tiles
          ((b1, r1), (b2, r2)) = (boxes(t1), boxes(t2))
          if 3 * b1 == b2:
            # output solution
            printf("s={s}: layout 1 = {h1} hex + {t1} tri; layout 2 = {h2} hex + {t2} tri")
            printf("-> tri: layout 1 = {b1} boxes (rem = {r1}); layout 2 = {b2} boxes (rem = {r2})")
            ((b1, r1), (b2, r2)) = (boxes(h1), boxes(h2))
            printf("-> hex: layout 1 = {b1} boxes (rem = {r1}); layout 2 = {b2} boxes (rem = {r2})")
            printf()
            break
        

        Like

  • Unknown's avatar

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

    Teaser 2537: Odds and evens 

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

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

    What are the two three-digit numbers?

    [teaser2537]

     
    • Jim Randell's avatar

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

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

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

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

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

      The completed multiplication sum looks like this:

      Like

    • ELBAZ HAVIV's avatar

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

      Hello Jim

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

      from the original puzzle

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

      Thank you.

      Like

      • Jim Randell's avatar

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

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

        So instead of doing:

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

        We just do:

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

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

        Like

    • ELBAZ HAVIV's avatar

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

      Hello again

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

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

      Thank you.

      Like

  • Unknown's avatar

    Jim Randell 9:39 am on 27 May 2025 Permalink | Reply
    Tags:   

    Teaser 2520: [Phone number] 

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

    I have a brand-new 11-digit phone number. The first and last digits are zero and the middle section uses each of the digits 1 to 9 once. If you called the number by pressing the buttons on the standard keypad of a modern phone, you would find no two consecutive digits of the number in the same row or column. Looking at the fifth and sixth digits, I see one is double the other. The second and third digits differ by two, the seventh is lower than the sixth and the 10th is odd, and one more than the eighth.

    What is my number?

    This puzzle was originally published with no title.

    [teaser2520]

     
    • Jim Randell's avatar

      Jim Randell 9:40 am on 27 May 2025 Permalink | Reply

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

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # the number is: 0ABCDEFGHI0
      --digits="1-9"  # for A-I
      
      # a keypad is laid out as:
      #
      #   1 2 3
      #   4 5 6
      #   7 8 9
      #     0
      #
      # map digits to (<row>, <col>):
      #              0  1  2  3  4  5  6  7  8  9
      --code="row = [4, 1, 1, 1, 2, 2, 2, 3, 3, 3]"
      --code="col = [2, 1, 2, 3, 1, 2, 3, 1, 2, 3]"
      
      # no digits share row or col with an adjacent digit
      --code="check = lambda x, y: col[x] != col[y] and row[x] != row[y]"
      "check(0, A)"
      "check(A, B)"
      "check(B, C)"
      "check(C, D)"
      "check(D, E)"
      "check(E, F)"
      "check(F, G)"
      "check(G, H)"
      "check(H, I)"
      "check(I, 0)"
      
      # one of 5th or 6th digits is double the other
      "(D == 2 * E) or (E == 2 * D)"
      
      # 2nd and 3rd digits differ by 2
      "abs(A - B) = 2"
      
      # 7th digit is lower than 6th
      "F < E"
      
      # 10th digit is odd, and one more than 8th
      "I % 2 = 1"
      "G + 1 = I"
      
      --template="0 A B C D E F G H I 0"
      --solution=""
      

      Solution: The number is: 03594816270.

      Like

    • Ruud's avatar

      Ruud 3:02 pm on 27 May 2025 Permalink | Reply

      Very brute force, but still vey fast:

      row = {0: 0, 1: 1, 2: 1, 3: 1, 4: 4, 5: 4, 6: 4, 7: 7, 8: 7, 9: 7}
      column = {0: 2, 1: 1, 2: 2, 3: 3, 4: 1, 5: 2, 6: 3, 7: 1, 8: 2, 9: 3}
      
      for n in itertools.permutations(range(1, 10)):
          if (
              ((number:=(None,0)+ n + (0,))[5] == 2 * number[6] or number[6] == 2 * number[5])
              and abs(number[2] - number[3]) == 2
              and number[7] < number[6]
              and number[10] % 2 == 1
              and number[10] == number[8] + 1
              and all(row[number[i]] != row[number[i + 1]] and column[number[i]] != column[number[i + 1]] for i in range(1, 11))
          ):
              print("".join(str(number[i]) for i in range(1, 12)))
      

      Like

    • Frits's avatar

      Frits 6:29 pm on 27 May 2025 Permalink | Reply

      #! python3 -m enigma -rr
       
      SubstitutedExpression
      
      # the number is: 0ABCDEFGHI0
      --digits="1-9"  # for A-I
       
      # a keypad is laid out as:
      #
      #   1 2 3      
      #   4 5 6      
      #   7 8 9      
      #     0       
      #
      # map digits to (<row>, <col>):
      #              0  1  2  3  4  5  6  7  8  9
      --code="row = [4, 1, 1, 1, 2, 2, 2, 3, 3, 3]"
      --code="col = [2, 1, 2, 3, 1, 2, 3, 1, 2, 3]"
      
      --invalid="1|3|5|7|9,G"     # G is even
      --invalid="1|3|5|6|7|9,DE"  # {D, E} is either {2, 4} or {4, 8}
      --invalid="8|9,F"           # F < E and E is 2, 4 or 8
      --invalid="4,ABCFGH"        # either D = 4 or E = 4
      --invalid="2|4|6|8,AB"      # D, E and G are even, so A and B can't be even
      --invalid="1|9,AB"          # A and B not both on same row
      --invalid="5,CDEFGH"        # {3, 5, 7} remains for A and B
      --invalid="2|5|8,A"         # not adjacent to "0" button
      
      --assign="B,5"              # either A = 5 or B = 5 but also A != 5
      --invalid="2|4|6|8,AC"      # not on same row/col as B(5)
       
      # no digits share row or col with an adjacent digit
      --code="check = lambda x, y: col[x] != col[y] and row[x] != row[y]"
      "check(A, B)"
      "check(B, C)"
      "check(C, D)"
      "check(D, E)"
      "check(E, F)"
      "check(F, G)"
      "check(G, H)"
      "check(H, I)"
      
      # parity of F and H is unknown
      "45 - (A + B + C + D + E + F + G + I) = H"
       
      # one of 5th or 6th digits is double the other
      "(D == 2 * E) or (E == 2 * D)"
       
      # 2nd and 3rd digits differ by 2
      "abs(A - B) = 2"
       
      # 7th digit is lower than 6th
      "F < E"
       
      # 10th digit is odd, and one more than 8th
      "G + 1 = I"
       
      --template="0 A B C D E F G H I 0"
      --distinct="ABCDEFGI"
      --solution=""
      

      or

      N = 3
      r = lambda i: (i - 1) // N
      c = lambda i: (i - 1) % N if i else 1
      
      forbidden = {i: set(j for j in range(10) if j != i and
                      (r(i) == r(j) or c(i) == c(j))) for i in range(10)}
      
      def solve(k=9, ns=set(range(1, 10)), ss=[0]):
        if k == 0:
          # check 10th number
          if 0 not in forbidden[ss[-1]] and ss[-1] == ss[-3] + 1:
            yield ss + [0]
        else:
          match(10 - k):
           case(3): 
             if abs(ss[-1] - ss[-2]) != 2: return
           case(6): 
             if not (ss[-1] == 2 * ss[-2] or ss[-2] == 2 * ss[-1]): return
           case(7):   
             if ss[-1] > ss[-2]: return
           case(8):    
             if ss[-1] % 2: return
      
          for n in ns:
            if n not in forbidden[ss[-1]]:
              yield from solve(k - 1, ns - {n}, ss + [n])
      
      for s in solve():
        print("answer:", ''.join(str(x) for x in s))
      

      Like

  • Unknown's avatar

    Jim Randell 7:12 am on 25 May 2025 Permalink | Reply
    Tags:   

    Teaser 3270: Tunnel vision 

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

    Four ramblers came to a tunnel that they all had to travel through. The tunnel was too dangerous to travel through without a torch, and unfortunately they only had one torch. It was also too narrow to walk through more than two at a time. The maximum walking speed of each of the walkers was such that they could walk through the tunnel in an exact number of minutes, less than ten, which was different for each walker. When two walkers walked together, they would walk at the speed of the slower one.

    They all managed to get through the tunnel and in the quickest possible time, this time being five sixths of the total of their individual crossing times.

    In ascending order, what are their four four individual crossing times?

    [teaser3270]

     
    • Jim Randell's avatar

      Jim Randell 7:34 am on 25 May 2025 Permalink | Reply

      See also: Enigma 981, [@wikipedia].

      This Python program runs in 63ms. (Internal runtime is 1.8ms).

      from enigma import (Accumulator, irange, subsets, div, cache, printf)
      
      # find minimum total crossing times for individual times <ts> (all different)
      # where <xs> is the times of the participants on the far side
      @cache
      def _time(ts, xs):
        # 1 or 2 people take the max of the times
        if len(ts) < 3: return max(ts)
        # otherwise, find minimal time for 2 to cross (x, y) and 1 to return (z)
        r = Accumulator(fn=min)
        for xy in subsets(ts, size=2):
          for z in xs.union(xy):
            # minimise the remaining scenario
            t = _time(ts.difference(xy).union({z}), xs.union(xy).difference({z}))
            r.accumulate(max(xy) + z + t)
        return r.value
      time = lambda ts, xs=(): _time(frozenset(ts), frozenset(xs))
      
      # choose 4 individual crossing times; all different and <10 minutes
      for ts in subsets(irange(1, 9), size=4):
        # target time is 5/6 the sum of the individual times
        t = div(5 * sum(ts), 6)
        if t is None or time(ts) != t: continue
        # output solution
        printf("{ts} -> {t}")
      

      Solution: The individual crossing times are: 1, 2, 7, 8 minutes.

      The total of the individual crossing times is 18 minutes.

      And the minimum crossing time is 15 minutes (15 = 18 × 5/6), for example:

      t=0: 1+2 cross (2 min) {1, 2}
      t=2: 1 returns (1 min) {2}
      t=3: 7+8 cross (8 min) {2, 7, 8}
      t=11: 2 returns (2 min) {7, 8}
      t=13: 1+2 cross (2 min) {1, 2, 7, 8}
      t=15: crossing complete

      Like

      • Jim Randell's avatar

        Jim Randell 1:48 pm on 27 May 2025 Permalink | Reply

        I experimented further with this puzzle.

        Here is a modified version of my program which will produce a minimal sequence of crossings, and is also modified to use [[ multiset ]] instead of [[ set ]], which allows individual crossing times to be repeated:

        from enigma import (Accumulator, multiset, irange, subsets, div, printf)
        
        # find minimum total crossing times for individual times <ts>
        # where <xs> is the times of the participants on the far side
        def _time(ts, xs, ss):
          # 1 or 2 people take the max of the times
          if ts.size() < 3: return (ts.max(), ss + [list(ts.sorted())])
          # otherwise, find minimal time for 2 to cross (x, y) and 1 to return (z)
          r = Accumulator(fn=min)
          for xy in ts.subsets(size=2):
            for z in xs.combine(xy):
              # minimise the remaining scenario
              (t, ss_) = _time(ts.difference(xy).add(z), xs.combine(xy).remove(z), ss + [list(xy.sorted()), [z]])
              r.accumulate_data(xy.max() + z + t, ss_)
          return (r.value, r.data)
        time = lambda ts, xs=(): _time(multiset(ts), multiset(xs), list())
        
        # choose 4 individual crossing times
        for ts in subsets(irange(1, 9), size=4, select='C'):
          # target time is 5/6 the sum of the individual times
          t = div(5 * sum(ts), 6)
          if t is None: continue
          (t_, ss) = time(ts)
          if t_ != t: continue
          # output solution
          printf("{ts} -> {t} {ss}")
        

        We find there are 2 further solutions if repeated individual crossing times are allowed.

        And the program can also be used to find solutions for other variations of the puzzle, for example, where there are 5 participants, all with different crossing times less than 12.

        [Solutions below.]


        If repeated crossing times are allowed in the original puzzle (use [[ select='R' in line 19), the following scenarios are also solutions to the puzzle:

        (1, 1, 4, 6) → 10 [[1, 1], [1], [4, 6], [1], [1, 1]]
        (2, 2, 7, 7) → 15 [[2, 2], [2], [7, 7], [2], [2, 2]]

        And if we extend the puzzle to 5 participants (with different individual crossing times less than 12 minutes):

        (1, 2, 6, 10, 11) → 25 [[1, 6], [1], [1, 2], [1], [10, 11], [2], [1, 2]]

        Like

    • Frits's avatar

      Frits 2:49 pm on 26 May 2025 Permalink | Reply

      Only using analysis for the multiple of 6.

      from itertools import combinations
      
      # decompose the integer <t> into <k> increasing
      # numbers between mn and mx (inclusive)
      def decompose(t, k=4, mn=1, mx=9, vs=[]):
        if k == 1:
          if vs[-1] < t <= mx:
            yield set(vs + [t])
        else:
          for n in range(mn, mx + 1):
            if 2 * k * n + k * (k - 1) > 2 * t:
              break
            yield from decompose(t - n, k - 1, n + 1, mx, vs + [n])
      
      # get all four ramblers through the tunnel from a to b
      def solve(a, b=set(), e=0, ss=[], t=0): 
        global quickest
        if len(a) == 2 and not e: # penultimate stage
          quickest = min(quickest, t + max(a)) 
        else:
          if e: # we are at the end of the tunnel
            for c in combinations(b, 1):
              if (t_ := t + c[0]) < quickest:
                solve(a | set(c), b.difference(c), 0, ss + [c], t_)
          else: # we are at the start of the tunnel   
            for c in combinations(a, 2):
              if (t_ := t + max(c)) < quickest:
                solve(a.difference(c), b  | set(c), 1, ss + [c], t_)
      
      # total of their individual crossing times <ti> must be a multiple of 6
      # to get to five sixths
      for ti in range(12, 31, 6):
        # choose times for the four ramblers (with sum <ti>)
        for t4 in decompose(ti):
          quickest = 999
          # get the quickest possible time for these four times
          solve(t4)
          # five sixths?
          if 6 * quickest == 5 * ti:
            print(f"answer: {sorted(t4)}")
      

      or faster

      from itertools import combinations, permutations
      from collections import defaultdict
      
      # assume a and e are walking back times (may have same value) 
      # b, c, d and f must have different values
      
      # 1: (a, b)  2: a  3: (c, d)  4: e  5: (e, f)
      dct = defaultdict(lambda: 45) # default value 45
      dgts = set(range(1, 10))
      for c, d in combinations(range(1, 10), 2):
        for b, f in permutations(dgts - {c, d}, 2):
          # total of their individual crossing times must be 
          # a multiple of 6 to get to five sixths
          if (b + c + d + f) % 6 == 0: 
            # go for quickest possible time
            a, e = min(c, f), min(b, c) 
            # store the quickest possible time
            t = max(a, b) + a + d + e + max(e, f)
            t4 = tuple(sorted([b, c, d, f]))
            dct[t4] = min(dct[t4], t)
      
      for k, v in dct.items():
        # five sixths of the total of their individual crossing times
        if 5 * sum(k) == 6 * v:
          print(f"answer: {k}") 
      

      Like

  • Unknown's avatar

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

    Teaser 2546: Secret agents 

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

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

    THREE + THREE + TWO = EIGHT
    time: DATA

    When does this secret operation begin?

    [teaser2546]

     
    • Jim Randell's avatar

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

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

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

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

      Solution: The operation begins at 15:35.

      Like

    • Ruud's avatar

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

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

      Like

  • Unknown's avatar

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

    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 6:50 am on 18 May 2025 Permalink | Reply
    Tags:   

    Teaser 3269: Combination lock 

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

    Phil buys a four-digit combination lock where each dial can be set from 0 to 9, and needs to decide what number to set to unlock it. He opts for a number with all different digits and a leading zero so he only has to remember a three-digit number. When the chosen number is set for the lock to open, nine other four-digit numbers are visible. For instance, if he chooses 0123, then 1234, 2345, 3456, 4567, 5678, 6789, 7890, 8901 and 9012 are all visible. As a retired Maths teacher with a natural interest in numbers he examines the other nine numbers that are displayed when the lock is set to open. None of these numbers are prime but two of them are perfect squares.

    What three-digit number does Phil need to remember?

    [teaser3269]

     
    • Jim Randell's avatar

      Jim Randell 7:06 am on 18 May 2025 Permalink | Reply

      A brute force approach gives a short and acceptably fast program.

      The following Python program runs in 77ms. (Internal runtime is 8.4ms).

      from enigma import (irange, nconcat, subsets, is_prime, is_square_p, printf)
      
      # shift digits <ds> by increment <i> (modulo <n>)
      shift = lambda ds, i, n=10: ((d + i) % n for d in ds)
      
      # consider possible 3 digit numbers
      for (X, Y, Z) in subsets(irange(1, 9), size=3, select='P'):
        # calculate each of the "other" numbers
        ns = list(nconcat(shift((0, X, Y, Z), i)) for i in irange(1, 9))
        # (exactly) 2 are squares
        if sum(is_square_p(n) for n in ns) != 2: continue
        # none are prime
        if any(is_prime(n) for n in ns): continue
        # output solution
        printf("0{X}{Y}{Z} -> {ns}")
      

      Solution: The number Phil has to remember is 912.

      So the combination is: 0912.

      Of the other numbers visible (1023, 2134, 3245, 4356, 5467, 6578, 7689, 8790, 9801):

      4356 = 66^2
      9801 = 99^2

      There is one other combination that gives 2 squares: 0327.

      Of the other numbers visible (1438, 2549, 3650, 4761, 5872, 6983, 7094, 8105, 9216):

      2549 is prime
      4761 = 69^2
      6983 is prime
      9216 = 96^2

      So this fails the requirement that none of the other numbers is prime.

      And these are the only 2 combinations that give more than one square among the “other” numbers.

      Like

      • Jim Randell's avatar

        Jim Randell 10:39 am on 18 May 2025 Permalink | Reply

        Here is a slightly longer, but faster, alternative approach.

        It considers the possible 4-digit squares, and then looks for 2 that give the same combination number (0xyz).

        It has an internal runtime of 253µs.

        from enigma import (defaultdict, irange, nsplit, nconcat, is_prime, join, printf)
        
        # shift digits <ds> by increment <i> (modulo <n>)
        shift = lambda ds, i, n=10: ((d + i) % n for d in ds)
        
        # count 4-digit squares by the combination (smallest number on the lock)
        m = defaultdict(int)
        for i in irange(32, 99):
          ds = nsplit(i * i)
          if len(set(ds)) != len(ds): continue
          # reduce the first digit to zero
          k = tuple(shift(ds, -ds[0]))
          m[k] += 1
        
        # consider combinations that give exactly 2 squares
        for (k, n) in m.items():
          if n != 2: continue
        
          # construct the 9 "other" numbers
          ns = list(nconcat(shift(k, i)) for i in irange(1, 9))
          # none of them are prime
          if any(is_prime(n) for n in ns): continue
        
          # output solution
          printf("{k} -> {ns}", k=join(k))
        

        Like

        • Frits's avatar

          Frits 11:04 am on 18 May 2025 Permalink | Reply

          Nice, I was also considering possible 4-digit squares but was using the less efficient “itertools.combinations”.

          Like

  • Unknown's avatar

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

    Teaser 2552: Ever-increasing circles 

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

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

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

    [teaser2552]

     
    • Jim Randell's avatar

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

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

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

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

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

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

      (See proof below).

      And in this case we have:

      a = 1
      b = √2 − 1

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

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

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

      Solution: The answer is 2.


      Proof:

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

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

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

      So:

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

      Like

  • Unknown's avatar

    Jim Randell 7:32 am on 13 May 2025 Permalink | Reply
    Tags: ,   

    Teaser 2350: [Circles and tangents] 

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

    A puzzle in a magazine was flawed: it had two different answers. The puzzle showed two circles that did not meet. It gave the radii (prime numbers of centimetres) and the distance between the centres (a whole number of centimetres less than 50).

    It then said:

    “Imagine a straight line that is a tangent to both the circles. The distance between the two points where that line touches the circles is. a whole number of centimetres.

    How many?”

    What was the radius of the smaller circle?

    This puzzle was originally published with no title.

    [teaser2350]

     
    • Jim Randell's avatar

      Jim Randell 7:32 am on 13 May 2025 Permalink | Reply

      We are to construct the tangents to two non-touching circles. See: [ @wikipedia ]

      I am assuming that, as we are asked to find the radius of the smaller circle, then the two circles must be of different sizes. (And without this condition the Teaser puzzle itself is flawed).

      If the circles have radii r and R (where r < R), and the separation between their centres is d, then the tangents have length:

      outer tangent, length T: d^2 = T^2 + (R − r)^2
      inner tangent, length t: d^2 = t^2 + (R + r)^2

      This Python program runs in 59ms. (Internal runtime is 2.3ms).

      from enigma import (primes, irange, ircs, printf)
      
      # r = radius of smaller circle (a prime)
      for r in primes.between(2, 24):
        # R = radius of the larger circle (a prime)
        for R in primes.between(r + 1, 48 - r):
          # d = separation of centres (< 50)
          for d in irange(r + R + 1, 49):
            # calculate: T = length of outer tangent, t = length of inner tangent
            (T, t) = (ircs(d, -(R - r)), ircs(d, -(R + r)))
            if T is None or t is None or t == T: continue
            # output viable configurations
            printf("r={r} R={R} d={d} -> T={T} t={t}")
      

      Solution: The smaller circle has a radius of 7 cm.

      There are two possible scenarios that provide tangents with two different integer lengths:

      d=26, r=7, R=17 → T=24, t=10
      d=34, r=7, R=23 → T=30, t=16

      And so one of these must be the scenario presented in the magazine puzzle.

      The first of these solutions looks like this:


      If the circles were allowed to be the same size, then we can find the following additional viable configurations, that give two different integer tangent lengths:

      d=5, r=2, R=2 → T=5, t=3
      d=10, r=3, R=3 → T=10, t=8
      d=26, r=5, R=5 → T=26, t=24

      And the Teaser puzzle would not have a unique solution.

      These additional solutions can be seen by changing line 6 in the program to start the search for R from the value of r.

      Like

      • Frits's avatar

        Frits 7:13 pm on 14 May 2025 Permalink | Reply

        In both scenarios we have (not a general rule):

        d^2 = T^2 + t^2 = 2.(r^2 + R^2)

        Like

    • Frits's avatar

      Frits 11:02 am on 16 May 2025 Permalink | Reply

      from enigma import primes, pythagorean_triples, subsets
      from collections import defaultdict
      
      prms = set(primes.between(2, 48))
      
      # collect Pythagorean triples for each distance between the centres
      pt = defaultdict(list)
      for x, y, h in pythagorean_triples(49):
        pt[h] += [x]
        pt[h] += [y]
      
      for d, vs in pt.items():  
        for t, T in subsets(sorted(vs), 2):
          # make sure r and R are integers
          if t % 2 != T % 2: continue
          r, R = (T - t) // 2, (T + t) // 2
          if any(x not in prms for x in (r, R)): continue 
          print(f"{r=} {R=} {d=} -> {T=} {t=}")
      

      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