Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:06 am on 3 October 2021 Permalink | Reply
    Tags: by: Cdr. P. A. Maitland   

    Brain Teaser: What’s his number? 

    From The Sunday Times, 20th December 1959 [link]

    A professor of mathematics was driving in an unfamiliar transatlantic city to visit a friend who lived in a very long street. Stopping to check the street-name he found he had entered the street he was looking for at the end where the high numbers finished. The final number started a train of thought: after a short calculation he smiled in satisfaction and drove on to his friend’s house.

    His friend was astonished when he said: “I have just discovered a most curious thing: did you know that, in this street, the sum of the house-numbers larger than your number is equal to the sum of the house-numbers smaller than your number?”

    Given that the street contained between 500 and 3,500 houses, how many houses were there in it and what was the number of the friend’s house?

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of 3 guineas was offered.

    [teaser-1959-12-20] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:06 am on 3 October 2021 Permalink | Reply

      (See also: Enigma 1725).

      We assume the houses are numbered consecutively from 1 without duplication or omission.

      If the friend’s house is x of y, we have:

      sum(1 … x − 1) = sum(x + 1 … y)
      sum(1 … x − 1) = sum(1 … y) − sum(1 … x)
      T(x − 1) + T(x) = T(y)
      (1/2)(x − 1)x + (1/2)x(x + 1) = T(y)
      x² = T(y)

      And y is in the range 500 to 3500.

      We can easily solve this programatically. The following short Python program runs in 53ms.

      Run: [ @replit ]

      from enigma import (irange, tri, is_square, printf)
      
      for y in irange(500, 3500):
        x = is_square(tri(y))
        if x:
          printf("house {x} of {y}")
      

      Solution: There were 1681 houses in the street. The friend’s house was number 1189.


      Manually, we need to find a triangular number that is also a square number (and in a specific range).

      A recurrence relation for the square triangular numbers [@wikipedia] is:

      N[0] = 0
      N[1] = 1
      N[k + 2] = 34 N[k + 1] − N[k] + 2

      and if: N[k] = (s[k])² = T[t[k]], we have:

      s[0] = t[0] = 0
      s[1] = t[1] = 1
      s[k + 2] = 6 s[k + 1] − s[k]
      t[k + 2] = 6 t[k + 1] − t[k] + 2

      Calculating these sequences we see:

      s[2] = 6; t[2] = 8
      s[3] = 35; t[3] = 49
      s[4] = 204; t[4] = 288
      s[5] = 1189; t[5] = 1681
      s[6] = 6930; t[6] = 9800

      Only the values for k = 5 are in the required range, so:

      x = s[5] = 1189
      y = t[5] = 1681

      We can implement these steps programatically as follows:

      from enigma import (fib, unpack, printf)
      
      S = fib(0, 1, fn=unpack(lambda a, b: 6 * b - a))
      T = fib(0, 1, fn=unpack(lambda a, b: 6 * b - a + 2))
      
      for (k, (s, t)) in enumerate(zip(S, T)):
        f = (499 < t < 3501)
        printf("s[{k}] = {s}; t[{k}] = {t} {f}", f=['', '[*** SOLUTION ***]'][f])
        if t > 3500: break
      

      As these sequences increase t[k] / s[k] gives rational approximations to √2. In the case of the solution we have:

      t[5] / s[5] = 1681 / 1189 = 41 / 29 = 1.4137931…

      Like

    • John Crabtree's avatar

      John Crabtree 2:55 pm on 5 October 2021 Permalink | Reply

      Fortunately for manual solvers this brain teaser is accessible via the Pell equation, ie
      (2y + 1)^2 – 2(2x)^2 = 1
      where (2y + 1)/(2x) are rational approximations to √2.
      Letting a = 2x and b = 2y + 1 leads to
      a[0] = 0, b[0] = 1, x[0] = 0, y[0] = 0
      a[1] = 2, b[1] = 3, x[1] = 0, y[1] = 1
      a[2] = 12, b[2] = 17, x[2] = 6, y[2] = 8
      a[k + 2] = 6 a[k + 1] – a[k]
      b[k + 2] = 6 b[k + 1] – b[k]
      Then one needs to calculate three more (a, b) pairs so that y is in the permitted range.

      My guess is that this was the first appearance of the Pell equation in a ST Brain Teaser.

      Like

    • GeoffR's avatar

      GeoffR 6:36 pm on 7 October 2021 Permalink | Reply

      I programmed this teaser as the arithmetic sum of the house numbers left (smaller) and right (larger) than my friend’s house number.

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 500..3500: N;    % His_House
      var 500..3500: L;    % Last_House
      
      % There are (N - 1) houses left of house N, and last house is (N - 1)
      % There are (L - N) houses right of house N, and 1st house is (N + 1)
      % Using the arithmetic sum formula: S = n/2 * (2*a + (n-1)* d)
      % Left sum of numbers = Right sum of numbers
      
      constraint (N - 1) * N div 2 == (L - N) * (2 *(N + 1) + (L - N - 1)) div 2;
      
      solve satisfy;
      
       output [ "Number of my friend's house is " ++ show(N)
       ++ "\n" ++ "Number of houses in road is " ++ show(L) ];
       
      % Number of my friend's house is 1189
      % Number of houses in road is 1681
      % ----------
      % ========== 
      
      

      Like

    • GeoffR's avatar

      GeoffR 8:53 am on 8 October 2021 Permalink | Reply

      A short Python programme verified the answer.

      # check sums of lower and higher house numbers for his number (1189)
      from enigma import irange
      
      lower_sum, higher_sum = 0, 0
      
      # sum of numbers lower than his number
      for n in irange(1, 1188):
          lower_sum += n
      
      # sum of numbers higher than his number
      for m in irange(1190, 1681):
          higher_sum += m
      
      print(f"Lower sum = {lower_sum}, Higher sum = {higher_sum}")
      # Lower sum = 706266, Higher sum = 706266
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:15 pm on 1 October 2021 Permalink | Reply
    Tags:   

    Teaser 3080: One of a kind 

    From The Sunday Times, 3rd October 2021 [link] [link]

    The raffle tickets at the Mathematical Society Dinner were numbered from 1 to 1000. There were four winning tickets and together they used each of the digits from 0 to 9 once only. The winning numbers could be classified uniquely as one square, one cube, one prime and one triangular number. For example, 36 is a triangular number as 1+2+3+4+5+6+7+8 = 36, but it cannot be a winner as 36 is also a square. The tickets were all sold in strips of five, and two of the winning numbers were from consecutive strips. The first prize was won by the holder of the smallest-numbered winning ticket, which was not a cube.

    List the four winning numbers in ascending order.

    [teaser3080]

     
    • Jim Randell's avatar

      Jim Randell 4:34 pm on 1 October 2021 Permalink | Reply

      The following Python program runs in 64ms.

      Run: [ @replit ]

      from enigma import (irange, singleton, is_cube, is_square, is_triangular, is_prime, empty, tuples, printf)
      
      # collect numbers with no repeated digits as strings
      def collect(fns, a=1, b=1000):
        rs = list(list() for _ in fns)
        for n in irange(a, b):
          # remove numbers with repeated digits
          s = str(n)
          if len(s) != len(set(s)): continue
          # does the number satisfy a single condition
          j = singleton(i for (i, fn) in enumerate(fns) if fn(n))
          if j is not None: rs[j].append(s)
        return rs
      
      # collect each category
      cats = collect([is_cube, is_square, is_triangular, is_prime])
      
      # find members of each set, such that each digit is used exactly once
      def solve(ss, ns=[], ds=empty):
        # are we done?
        if not ss:
          if len(ds) == 10:
            yield ns
        else:
          # choose an element from the next set
          for n in ss[0]:
            if not ds.intersection(n):
              yield from solve(ss[1:], ns + [n], ds.union(n))
      
      # allocate tickets to strips
      strip = lambda n: (n + 4) // 5
      
      # find candidate winning tickets
      for ns in solve(cats):
        # smallest number is not a cube
        ns = sorted(int(n) for n in ns)
        if is_cube(ns[0]): continue
        # find numbers on consecutive strips
        if any(b == a + 1 for (a, b) in tuples(map(strip, ns), 2)):
          printf("{ns}")
      

      Solution: The winning numbers are: 49, 53, 216, 780.

      Like

    • GeoffR's avatar

      GeoffR 12:05 pm on 4 October 2021 Permalink | Reply

      For the 10 unique digits used in this teaser, I found only two arrangements to partition the 10 different digits to give four numbers, so that no number was greater than 1000.

      These two 10-digit arrangements were 1:3:3:3 and 2:2:3:3.

      The first 10-digit arrangement i.e. 1:3:3:3 did not produce a solution, using a similar MiniZinc programme for the second 10-digit arrangement i.e. 2:2:3:3, shown below.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 1..9:A; var 0..9:B; var 1..9:C; var 1..9:D; var 1..9:E; 
      var 0..9:F; var 0..9:G; var 1..9:H; var 0..9:I; var 0..9:J; 
      
      % four winning tickets used each of the digits from 0 to 9 once only
      constraint all_different([A, B, C, D, E, F, G, H, I, J]);
      
      predicate is_sq(var int: y) =
        exists(z in 1..ceil(sqrt(int2float(ub(y))))) (
              z*z = y );
              
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
      
      predicate is_tri( var int: n) =
        exists ( x in 1..n div 2)
             ( x * (x+1) div 2 = n);
      
      predicate is_cube(var int: c) =
         let {
            var 0..ceil(pow(int2float(ub(c)),(1/3))): z
         } in z*z*z = c;
         
      % Assume number digit splits are 2:2:3:3 to use the ten different digits
      var 10..99:AB = 10*A + B;
      var 10..99:CD = 10*C + D;
      var 100..999:EFG = 100*E + 10*F + G;
      var 100..999:HIJ = 100*H + 10*I + J;
      
      % Reducing 24 no.possible permutations, for 4 items, 
      % to say 9 permutations for two repeated groups of digit patterns,
      % omitting AB as a cube for the first 2-digit number, as specified,
      % proved sufficient to find the unique answer for the four numbers.
      constraint 
         (is_sq(AB) /\ is_prime(CD) /\ is_cube(EFG) /\ is_tri(HIJ)  )
      \/ (is_sq(AB) /\ is_cube(CD) /\ is_prime(EFG) /\ is_tri(HIJ)  )
      \/ (is_sq(AB) /\ is_tri(CD) /\ is_prime(EFG) /\ is_cube(HIJ)  )
      
      \/ (is_prime(AB) /\ is_sq(CD) /\ is_cube(EFG) /\ is_tri(HIJ)  )
      \/ (is_prime(AB) /\ is_cube(CD) /\ is_sq(EFG) /\ is_tri(HIJ)  )
      \/ (is_prime(AB) /\ is_tri(CD) /\ is_sq(EFG) /\ is_cube(HIJ)  )
      
      \/ (is_tri(AB) /\ is_sq(CD) /\ is_prime(EFG) /\ is_cube(HIJ)  )
      \/ (is_tri(AB)/\ is_prime(CD) /\ is_sq(EFG)  /\ is_cube(HIJ)  )
      \/ (is_tri(AB) /\ is_cube(CD) /\ is_sq(EFG) /\ is_prime(HIJ)  );
      
      % put four numbers in increasing order
      constraint increasing ( [ AB, CD, EFG, HIJ]);
      
      % two of the winning numbers were from consecutive strips of 5 tickets
      constraint ((CD + 4) div 5 - (AB + 4) div 5 == 1)
      \/ ((EFG + 4) div 5 - (CD + 4) div 5 == 1)
      \/ ((HIJ + 4) div 5 - (EFG + 4) div 5 == 1) ;
      
      output [ "Four numbers are " ++ show([AB, CD, EFG, HIJ]) ];
      

      Like

    • Frits's avatar

      Frits 3:29 pm on 4 October 2021 Permalink | Reply

      from enigma import SubstitutedExpression
      from itertools import permutations as perm
      
      checknum = lambda n: n in td
      gettype = lambda n: td[n]
      
      # two ticket numbers must be in adjacent strips
      def consecutive(li):
       strips = [(x - 1) // 5 for x in li]
       if any(strips[i + 1] == x + 1 for i, x in enumerate(strips[:-1])):
         return True
       return False
      
      # perfect squares and perfect cubes between 1000 and 9999
      squares = set(i ** 2 for i in range(1, 32))
      # as smallest ticket may not be a cube start with 27
      cubes = set(i ** 3 for i in range(3, 11))
      tris = set((i * (i + 1)) // 2 for i in range(1, 45))
      
      # set of prime numbers up to 997
      P = {2, 3, 5, 7}
      P |= {x for x in range(11, 33, 2) if all(x % p for p in P)}
      P |= {x for x in range(33, 1000, 2) if all(x % p for p in P)}
      
      td = dict()  # dictionary of tickets
      # build dictionary of "classified uniquely" ticket numbers
      for n in range(1, 1000):
       # skip numbers with duplicate digits
       if len(set(str(n))) != len(str(n)): continue
       cnt = 0
       for i, y in enumerate([squares, cubes, tris, P]):
         if n in y:
           cnt += 1
           i1 = i
       # classified uniquely?
       if cnt == 1:
         td[n] = i1
      
      # determine digits that occur in all cubes
      cubes = [k for k, v in td.items() if v == 1]
      mand_dgts = set(str(cubes[0]))
      for c in cubes[1:]:
       mand_dgts &= set(str(c))
      
      # remove non-cube entries which use digits that occur in all cubes
      if mand_dgts:
       td = dict((k, v) for k, v in td.items()
                 if v == 1 or all(c not in str(k) for c in mand_dgts))
      
      # the alphametic puzzle (numbers AB, CDE, FGH and IJK are increasing)
      p = SubstitutedExpression(
       [
        "A == 0 or C == 0",   # AB and CDE must have total length of 4
        "A + C = X",          # X is non-zero and represents A or C
      
        # check AB
        "checknum(AB)",
        "gettype(AB) != 1",   # not a cube
      
        # check CDE
        "AB < CDE and checknum(CDE)",
        "gettype(CDE) not in {gettype(AB)}",
        "consecutive([AB, CDE]) = Y",
      
        # check FGH
        "C < F",
        "checknum(FGH)",
        "gettype(FGH) not in {gettype(AB), gettype(CDE)}",
        "Y or (G == 9 and F < 8) or consecutive([CDE, FGH])",
        #"consecutive([CDE, FGH]) = Z",
        #"Y or Z or (G == 9 and F < 8)",
      
        # check IJK
        "F < I",
        "checknum(IJK)",
        "gettype(IJK) not in {gettype(AB), gettype(CDE), gettype(FGH)}",
      
        # two ticket numbers must be in adjacent strips
        #"consecutive([AB, CDE, FGH, IJK])",
        "Y or consecutive([CDE, FGH, IJK])",
        #"Y or Z or consecutive([FGH, IJK])",
       ],
      
       answer="AB, CDE, FGH, IJK",
       verbose=0,
       d2i=dict([(0, "FI")] +
                [(1, "I")] +
                [(8, "C")] +
                [(9, "CF")]),
       distinct="BDEFGHIJKX",
       env=dict(consecutive=consecutive, checknum=checknum, gettype=gettype),
       reorder=0,
      )
      
      # print answer
      for (_, ans) in p.solve():
       print(f"{ans}")
      

      Like

    • GeoffR's avatar

      GeoffR 4:47 pm on 4 October 2021 Permalink | Reply

      This Python programme lists all 3-digit primes, squares, cubes and triangular numbers, without repeating digits, with reference to a 1:3:3:3 digits split solution.

      from enigma import is_prime
      
      L1, L2, L3, L4 = [], [], [], []
      
      #1. 3-digit squares without repeating digits
      print('3-digit squares')
      L1 = [ x * x for x in range(10, 32) if len(set(str(x*x))) == len(str(x*x)) ]
      print(L1)
      
      #2. set of 3-digit tri numbers, without repeating digits
      print('3-digit triangular numbers')
      for n in range(14,45):
          tri = n * (n + 1)//2
          if len(set(str(tri))) == len(str(tri)):
              L2.append(tri)
      
      print(L2); print(len(L2))
      
      #3. 3-digit cubes. without repeating digits
      print('3-digit cubes')
      
      L3 = [ x*x*x for x in range(5,10) if len(set(str(x*x*x))) == len(str(x*x*x)) ]
      print(L3)
      
      #4. 3-digit primes, without repeating digits
      print('3-digit primes')
      for n in range(100,1000):
          if is_prime(n) and len(set(str(n))) == len(str(n)):
              L4.append(n)
      
      print(L4); print(len(L4))
      
      # 3-digit squares, non repeating digits
      # [169, 196, 256, 289, 324, 361, 529, 576, 625, 729, 784, 841, 961] 13 no.
      
      # 3-digit triangular numbers, non repeating digits
      # 105, 120, 136, 153, 190, 210, 231, 253, 276, 325, 351, 378,
      # 406, 435, 465, 496, 528, 561, 630, 703, 741, 780, 820, 861,
      # 903, 946] 26 no.
      
      # 3-digit cubes, non repeating digits
      # [125, 216, 512, 729] 4 no.
      
      # 3-digit primes, non repeating digits
      # 103, 107, 109, 127, 137, 139, 149, 157, 163, 167, 173, 179,
      # 193, 197, 239, 241, 251, 257, 263, 269, 271, 281, 283, 293,
      # 307, 317, 347, 349, 359, 367, 379, 389, 397, 401, 409, 419,
      # 421, 431, 439, 457, 461, 463, 467, 479, 487, 491, 503, 509,
      # 521, 523, 541, 547, 563, 569, 571, 587, 593, 601, 607, 613,
      # 617, 619, 631, 641, 643, 647, 653, 659, 673, 683, 691, 701,
      # 709, 719, 739, 743, 751, 761, 769, 809, 821, 823, 827, 829,
      # 839, 853, 857, 859, 863, 907, 937, 941, 947, 953, 967, 971,
      # 983] 97 no.
      
      
      
      

      Manual Analysis – why a 1:3:3:3 digits split is not viable
      ———————————————————-

      Considering a 1:3:3:3 digits split, the single digit can be a square (1,4,9), a prime (2,3,5), a triangular number (1,3,6) but not a cube(1,8) as per teaser conditions.

      For any combination of the three remaining 3-digit numbers, I found there was usually a duplicate hundreds digit (1-9), after choosing the first 3-digit number. The 400 series for 3-digit squares, as 400, 441 etc was excluded due to repeating digits.

      Obviously, this prevents finding four separate numbers with ten different digits.

      I also checked for any possibility of consecutive numbers in two strips of five tickets at the end of one hundred series e.g. end of 190’s and the start of a new hundred series e.g. start of 200’s.

      One instance of a 3-digit triangular number(496) and a consecutive 3-digit prime(503) was found, leaving the digits (1,2,7,9) to form a 3-digit cube and a single digit square, or a single digit cube and a 3-digit square. This was not found to be possible. The only 3-digit numbers found were either not in any of the four number categories, or duplicate primes to 503.

      The over-riding reasons why three 3-digit suitable numbers could not be found was the duplication of the hundreds digit and the teaser requirement to obtain two numbers in two consecutive strips of five tickets.

      Like

  • Unknown's avatar

    Jim Randell 10:04 am on 30 September 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 869: New type football 

    From The Sunday Times, 19th March 1978 [link]

    In this puzzle four football teams are going to play each other once during the course of the season. The incomplete table below shows the situation when some of the matches have been played (2 points for a win, 1 for a draw as usual).

    The digits have been replaced by letters and each letter stands for the same digit wherever it appears and different letters stand for different digits.

    The table looks like this:

    List the matches played and the score in each match.

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser869]

     
    • Jim Randell's avatar

      Jim Randell 10:04 am on 30 September 2021 Permalink | Reply

      We know the goals for/against values for A and B, so we can calculate scores for matches involving A or B, which only leaves the score in the C vs D match to be decided, and as the overall goals for C is t, we can calculate potential scores for C in the match. If the match is a draw, then D’s score is the same as C’s. If it is a win for C, then D scored fewer goals. And if it is a win for D, then D scored more goals. I put an upper limit on the number of goals in this last case, but it is not a situation that is reached.

      This Python program uses the [[ Football() ]] helper class from the enigma.py library. It runs in 54ms.

      Run: [ @replit ]

      from enigma import Football, irange
      
      # scoring system
      football = Football(points=dict(w=2, d=1))
      
      # the table
      table = dict(played='x?pk', w='?hx?', l='??h?', d='k?k?', points='??m?')
      (gf, ga) = ('hmt?', 'pm??')
      
      # label the teams
      (A, B, C, D) = (0, 1, 2, 3)
      
      # find match outcomes
      for (ms, d) in football.substituted_table(table):
      
        # calculate scores for games involving A and B
        for ss in football.substituted_table_goals(gf, ga, ms, d=d, teams=[A, B]):
      
          # only the C vs D match is undecided
          sCD = list()
          m = ms[(C, D)]
          if m == 'x':
            sCD = [None]
          else:
            # calculate known goals for/against C
            (f, a) = football.goals([ss[(A, C)], ss[(B, C)]], [1, 1])
            # so the score in the C vs D match is (x, y)
            for x in irange(0, 9 - f):
              t = f + x
              if t in d.values(): continue
              if m == 'd':
                sCD.append((x, x))
              elif m == 'w':
                sCD.extend((x, y) for y in irange(0, x - 1))
              elif m == 'l':
                sCD.extend((x, y) for y in irange(x + 1, 10))  # upper limit on goals scored
      
          for ss[(C, D)] in sCD:
            # output solution
            football.output_matches(ms, ss, teams="ABCD", d=d)
      

      Solution: The played matches are: A vs B = 0-0; A vs C = 0-3; B vs C = 5-5; C vs D = 1-0.

      The A vs D, B vs D matches are not yet played.

      Like

  • Unknown's avatar

    Jim Randell 9:49 am on 28 September 2021 Permalink | Reply
    Tags:   

    Teaser 2827: Password 

    From The Sunday Times, 27th November 2016 [link] [link]

    My computer password consists of different digits written in decreasing order.

    I can rearrange the digits to form a perfect cube.

    A further rearrangement gives a perfect square.

    Another rearrangement gives a prime number.

    A further rearrangement gives a number that is divisible by the number of digits in the password.

    Yet another rearrangement gives a number that is divisible by all but one of its digits.

    What is my password?

    [teaser2827]

     
    • Jim Randell's avatar

      Jim Randell 9:50 am on 28 September 2021 Permalink | Reply

      This Python program runs in 245ms.

      Run: [ @replit ]

      from enigma import group, unpack, powers, inf, is_duplicate, ordered, nsplit, intersect, nconcat, subsets, is_prime, find, update, printf
      
      # collect numbers by digit content
      def numbers(s):
        def generate():
          for n in s:
            if n > 9876543210: break
            # only collect numbers with distinct digits
            if is_duplicate(n): continue
            yield (ordered(*nsplit(n), reverse=1), n)
        return group(generate(), by=unpack(lambda ds, n: ds), f=unpack(lambda ds, n: n))
      
      cubes = numbers(powers(0, inf, 3))
      squares = numbers(powers(0, inf, 2))
      
      divides = lambda n, ds: sum(d != 0 and n % d == 0 for d in ds)
      
      # select distinct elements from each set
      def select(ss, xs):
        i = find(xs, None)
        if i == -1:
          yield xs
        else:
          # choose an elements from ss[i]
          for x in ss[i]:
            if x not in xs:
              yield from select(ss, update(xs, [(i, x)]))
      
      # look for keys common to squares and cubes
      for ds in intersect(s.keys() for s in (squares, cubes)):
        k = len(ds)
      
        # find rearrangements (we need at least 6)...
        rs = set(nconcat(*s) for s in subsets(ds, size=k, select="P") if s[0] > 0)
        if len(rs) < 6: continue
      
        # ... divisible by k
        r1s = set(n for n in rs if n % k == 0)
        if not r1s: continue
      
        # ... divisible by all but one of the digits in ds
        r2s = set(n for n in rs if divides(n, ds) == k - 1)
        if not r2s: continue
      
        # ... primes
        r3s = set(n for n in rs if is_prime(n))
        if not r3s: continue
      
        # select distinct members for each set
        n = nconcat(*ds)
        for xs in select([[n], cubes[ds], squares[ds], r3s, r1s, r2s], [None] * 6):
          printf("password = {n}")
          printf("-> cubes = {rs}", rs=cubes[ds])
          printf("-> squares = {rs}", rs=squares[ds])
          printf("-> primes = {r3s}")
          printf("-> divisible by {k} = {r1s}")
          printf("-> divisible by all but one of {ds} = {r2s}")
          printf("-> selected = {xs}")
          break  # only need one example
      

      Solution: The password is 9721.

      The program ensures five different rearrangements of the password satisfying the conditions can be made, for example:

      9721 = password
      2197 = cube
      7921 = square
      2179 = prime
      7912 = divisible by 4 (length of password)
      1792 = divisibly by 7, 2, 1 (but not 9)

      In fact there are 50 different sets of rearrangements that we could choose for this particular password.


      We can do some analysis to reduce the number of passwords considered by looking at digital roots (which are unchanged by rearrangement):

      the digital root of a square is 1, 4, 7, 9
      the digital root of a cube is 1, 8, 9
      the digital root of prime (except 3) is 1, 2, 4, 5, 7, 8

      So we need only consider passwords with a digital root of 1. (At this point considering cubes leads to 12 candidate passwords).

      Also, there must be at least 6 different rearrangements, so we need at least 3 digits, but it cannot have exactly 3 digits, as then the requirement for a rearrangement divisible by the number of digits would require a rearrangement that is divisible by 3, which would also have a digital root that was divisible by 3. (At this point considering cubes leads to 10 candidate passwords).

      Similarly, the number cannot have 9 digits, as it would have to have a rearrangement with a digital root of 9. Nor 10 digits, as again, the digital root would have to be 9.

      And if the password had length 6, it would have to have a rearrangement divisible by 6, which would require the rearrangement to be divisible by 3, and so its digital root would also be divisible by 3.

      So the password has 4, 5, 7, or 8 digits, and a digital root of 1. (At this point considering cubes leads to 6 candidate passwords).

      Additionally the password is not divisible only one of its digits, and it cannot be divisible by 0, 3, 6, 9, so no more than one of those four digits can occur in it.

      This is enough to narrow the password down to a single candidate, which gives the answer to the puzzle. (Although for completeness we should check that the appropriate rearrangements can be made).

      Like

    • Hugh Casement's avatar

      Hugh Casement 1:36 pm on 28 September 2021 Permalink | Reply

      All six rearrangements ending in 2 are integer multiples of 4 (and of course of 1 and 2).
      1279, 1297, 2179, 2719, 2791, 2917, 2971, 7129, 7219, 9127, 9721 are all prime.
      I have certainly known more satisfying Teasers.

      Like

    • Frits's avatar

      Frits 12:02 pm on 4 October 2021 Permalink | Reply

      The singles and lst2 logic is not really needed as Jim’s select function is sufficient.
      The program runs in 15ms.

       
      from enigma import is_cube, is_square, is_prime, find, update
      from itertools import permutations as perm
      
      # select distinct elements from each row
      def select(ss, xs):
        i = find(xs, None)
        if i == -1:
          yield xs
        else:
          # choose an elements from ss[i]
          for x in ss[i]:
            if x not in xs:
              yield from select(ss, update(xs, [(i, x)]))
      
      def check(n):
        # we need 6 arrangements
        
        # 0: I can rearrange the digits to form a perfect cube.
        # 1: A further rearrangement gives a perfect square.
        # 2: Another rearrangement gives a prime number.
        # 3: A rearrangement that is divisible by the number of digits.
        # 4: A rearrangement that is divisible by all but one of its digits.
        # 5: original number
      
        s = str(n)
        ndigits = len(s)
        lst = [[] for _ in range(5)]
        
        # check all permutations of original number
        for p in perm(s):
          m = int("".join(p)) 
          
          if m == n: continue
        
          if is_cube(m): 
            lst[0] += [m]
          if is_square(m): 
            lst[1] = lst[1] + [m]
          if is_prime(m): 
            lst[2] += [m]
          if m % ndigits == 0: 
            lst[3] += [m]
          if sum(x != '0' and m % int(x) == 0 for x in p) == ndigits - 1: 
            lst[4] += [m]
            
        # if any row is empty return False
        if any(not x for x in lst): return False
        
        # add 6th arrangement (original number)
        lst += [[n]]
        
        singles = [x[0] for x in lst if len(x) == 1]
        
        # build list of non-singles with numbers not occurring in singles
        lst2 = [[y for y in x if y not in singles] for x in lst if len(x) > 1]  
        lngth = len(lst2)
        
        # if each remaining row contains enough items return True
        if all(len(x) >= lngth for x in lst2):
          return True
        
        # select distinct members for each row
        for x in select(lst, [None] * 6):
          return True
        
        return False
      
      # add digit to each number in the list so that order remains decreasing
      def addLastDigit(li=range(1, 10)):
        lngth = len(str(li[0])) if li else 0
        return [10 * n + i for n in li for i in range(10 - lngth) if i < (n % 10)]
            
            
      # list of 3-digit numbers with different digits written in decreasing order
      nums = addLastDigit(addLastDigit())
      
      while True:
        for n in nums:
          if check(n):
            print("password:", n)
            break  # only need one example
        else:  # no break
          if len(str(nums[0])) > 9: break
          # add another digit
          nums = addLastDigit(nums)
          continue
          
        break    # break if nested break occurred 
      

      Like

  • Unknown's avatar

    Jim Randell 10:47 am on 26 September 2021 Permalink | Reply
    Tags:   

    Brain-Teaser: Silver collection 

    From The Sunday Times, 31st July 1960 [link]

    Seven South Sea Island brothers found on the beach a broken box and mixed silver coins scattered. These were old British coins (six-pence, shilling, florin, half-crown, and crown). 135 of them bore a man’s head and 54 a woman’s. The two younger brothers claimed the latter and their elders shared the former.

    Being ignorant of the value of the coins they agreed to take twenty-seven each. First they laid the coins in heaps of each size. The senior brother then took seven coins from each of two heaps and smaller numbers from the other three heaps to make up twenty-seven coins. Each other brother then took the same numbers but each from a different order of heaps.

    Unknown to themselves, the five elders had equal money value. The boys also had equal value, but greater than their elders.

    What were the values?

    [Note: Respective values: 6d, 12d, 24d, 30d, 60d]

    This is one of the occasional Holiday Brain-Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of 3 guineas was offered.

    This puzzle is included in the book Sunday Times Brain Teasers (1974). The above text is taken from the book.

    [teaser-1960-07-31] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 10:49 am on 26 September 2021 Permalink | Reply

      This Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (decompose, group, subsets, printf)
      
      # denominations of coins
      denominations = [6, 12, 24, 30, 60]
      
      # total for quantities of coins in qs (wrt denominations)
      total = lambda qs: sum(x * y for (x, y) in zip(qs, denominations))
      
      # decompose 27 - 14 = 13 into 3 numbers between 1 and 6
      for qs in decompose(13, 3, min_v=1, max_v=6, sep=0):
        qs += (7, 7)
      
        # collect permutations by total amount
        d = group(subsets(qs, size=len, select="mP"), by=total)
      
        # look for 5 permutations for the elders
        for (k1, vs1) in d.items():
          if not (len(vs1) > 4): continue
          # and 2 permutations, with a greater sum for the youngers
          for (k2, vs2) in d.items():
            if not (k2 > k1 and len(vs2) > 1): continue 
            printf("elders = {k1} [from {vs1}]")
            printf("youngers = {k2} [from {vs2}]")
            printf()
      

      Solution: The elders had selected coins with a value of 798d (= 66s 6d). The youngers had selected coins with a value of 834d (= 69s 6d).

      Like

    • Hugh Casement's avatar

      Hugh Casement 3:13 pm on 26 September 2021 Permalink | Reply

      I confess I cheated by working backward from Jim’s solution, but was able to deduce that there were 48 crowns, 44 half crowns, 38 florins, 32 shillings, and 27 sixpences.
      Total 189 coins with a value £23 11s 6d = 5658 d.

      If the five piles were arranged in order of decreasing value, the seniors (in some order) took
      7, 7, 4, 3, 6; 7, 7, 3, 6, 4; 7, 6, 4, 7, 3; 7, 4, 7, 6, 3; 6, 7, 7, 3, 4 coins from the respective piles.
      The youngsters took 7, 7, 6, 3, 4 and 7, 6, 7, 4, 3.

      There would be other ways of making up the values 798 and 834 with 27 coins, but not with as many as five of one and two of the other, using the same numbers in different orders.

      Like

  • Unknown's avatar

    Jim Randell 5:23 pm on 24 September 2021 Permalink | Reply
    Tags:   

    Teaser 3079: Hall of residence 

    From The Sunday Times, 26th September 2021 [link] [link]

    Oak Hall at Woodville University has groups of five study bedrooms per flat and they share a kitchen/diner. In one flat live language students Andy, Bill, Chris, Dave and Ed. Bill, whose home town is Dunstable is reading French. The person in room 5 comes from Colchester and Dave comes from Brighton. The chap reading German has the room with a number one greater than the man from Gloucester. Chris occupies room 3, and Ed is reading Italian. The man in room 2 is reading Spanish, and the man reading English has a room whose number is two different from the student from Reigate.

    What is Andy’s subject and where is his home?

    [teaser3079]

     
    • Jim Randell's avatar

      Jim Randell 9:42 pm on 24 September 2021 Permalink | Reply

      We can use the [[ SubstitutedExpression ]] solver from the enigma.py library to assign room numbers to the names, subjects and towns according to the given conditions.

      This run-file executes in 68ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      # assign room numbers 1-5 to each of the groups:
      #
      #   Andy(A) Bill(B) Chris(C) Dave(D) Ed(E)
      #   English(F) French(G) German(H) Italian(I) Spanish(J)
      #   Brighton(K) Colchester(L) Dunstable(M) Gloucester(N) Reigate(P)
      
      SubstitutedExpression
      
      --digits="1-5"
      --distinct="ABCDE,FGHIJ,KLMNP"
      
      # "Bill, whose home town is Dunstable is reading French"
      "B = M"
      "B = G"
      
      # "The person in room 5 comes from Colchester"
      "L = 5"
      
      # "Dave comes from Brighton"
      "D = K"
      
      # "The chap reading German has the room with a number one greater than the man from Gloucester"
      "N + 1 = H"
      
      # "Chris occupies room 3"
      "C = 3"
      
      # "Ed is reading Italian"
      "E = I"
      
      # "The man in room 2 is reading Spanish"
      "J = 2"
      
      # "The man reading English has a room whose number is two different from the student from Reigate"
      "abs(F - P) = 2"
      
      # mention A (so it gets assigned a value)
      "A > 0"
      
      --template="(A B C D E) (F G H I J) (K L M N P)"
      --solution=""
      
      

      Solution: Andy is reading Spanish, and is from Gloucester.

      The complete allocations are:

      Room 1: Dave, English, Brighton
      Room 2: Andy, Spanish, Gloucester
      Room 3: Chris, German, Reigate
      Room 4: Bill, French, Dunstable
      Room 5: Ed, Italian, Colchester


      Manually we find that there are only two possible room values for the (Bill/French/Dunstable) group. Trying both possible values quickly leads to a solution in one case and a contradiction in the other.

      Like

      • Jim Randell's avatar

        Jim Randell 4:20 pm on 25 September 2021 Permalink | Reply

        I’ve seen people talk about solving these kind of puzzles like a jigsaw, and as it only takes a few minutes to write a program or solve this one manually, I thought it might be interesting to look at transforming the constraints into an “exact cover” problem.

        It’s a bit more work, but it does run quickly:

        from enigma import union, irange, chunk, update, join, printf
        
        ###############################################################################
        
        # in-place algorithmX implementation (X, soln are modified)
        def algorithmX(X, Y, soln):
          if not X:
            yield soln
          else:
            c = min(X.keys(), key=lambda k: len(X[k]))
            # copy X[c], as X is modified (could use sorted(X[c]) for stability)
            for r in list(X[c]):
              soln.append(r)
        
              # cols = select(X, Y, r)
              cols = list()
              for j in Y[r]:
                for i in X[j]:
                  for k in Y[i]:
                    if k != j:
                      X[k].remove(i)
                cols.append(X.pop(j))
        
              yield from algorithmX(X, Y, soln)
        
              # deselect(X, Y, r, cols)
              for j in reversed(Y[r]):
                X[j] = cols.pop()
                for i in X[j]:
                  for k in Y[i]:
                    if k != j:
                      X[k].add(i)
        
              soln.pop()
        
        # input: ss = sequence of collections of sets [ [a0, a1, ...], [b1, b2, ...], [c1, c2, ...] ... ]
        # output: sequence of sets (a, b, c, ...) one from each collection
        def exact_cover(sss):
          # map elements to indices
          xs = sorted(union(union(ss) for ss in sss))
          n = len(xs)
          m = dict((x, i) for (i, x) in enumerate(xs))
        
          # set up Y, one row for each position
          Y = list()
          for (j, ss) in enumerate(sss, start=n):
            for s in ss:
              y = list(m[x] for x in s)
              y.append(j)
              Y.append(y)
        
          # set up X as a dict of sets
          X = dict((k, set()) for k in irange(0, j))
          for (i, y) in enumerate(Y):
            for k in y:
              X[k].add(i)
        
          # find exact covers using algorithmX
          k = len(sss)
          for rs in algorithmX(X, Y, list()):
            # turn the selected rows of Y, back into sets
            r = [None] * k
            for i in rs:
              y = Y[i]
              r[y[-1] - n] = list(xs[i] for i in y[:-1])
            yield r
        
        ###############################################################################
        
        # we fit the pieces into a grid:
        #
        #  1:  0   1   2
        #  2:  3   4   5
        #  3:  6   7   8
        #  4:  9  10  11
        #  5: 12  13  14
        
        # each piece represents a constraint
        pieces = [
        
          # "Bill, whose home town is Dunstable is reading French" [Bill, French, Dunstable]
          [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]],
        
          # "The person in room 5 comes from Colchester" [Colchester]
          [[14]],
        
          # "Dave comes from Brighton" [Dave, Brighton]
          [[0, 2], [3, 5], [6, 8], [9, 11], [12, 14]],
        
          # "The chap reading German has the room with a number one greater than the man from Gloucester" [German, Gloucester]
          [[4, 2], [7, 5], [10, 8], [13, 11]],
        
          # "Chris occupies room 3" [Chris]
          [[6]],
        
          # "Ed is reading Italian" [Ed, Italian]
          [[0, 1], [3, 4], [6, 7], [9, 10], [12, 13]],
        
          # "The man in room 2 is reading Spanish" [Spanish]
          [[4]],
        
          # "The man reading English has a room whose number is two different from the student from Reigate" [English, Reigate]
          [[1, 8], [4, 11], [7, 14], [7, 2], [10, 5], [13, 8]],
        
          # make sure A can be placed [Andy]
          [[0], [3], [6], [9], [12]],
        ]
        
        # the labels for each piece
        labels = [
          ['Bill', 'French', 'Dunstable'],
          ['Colchester'],
          ['Dave', 'Brighton'],
          ['German', 'Gloucester'],
          ['Chris'],
          ['Ed', 'Italian'],
          ['Spanish'],
          ['English', 'Reigate'],
          ['Andy'],
        ]
        
        # find exact covers
        for ss in exact_cover(pieces):
          # output solution
          g = ['???'] * 15
          for (ks, vs) in zip(ss, labels):
            g = update(g, ks, vs)
          for (i, x) in enumerate(chunk(g, 3), start=1):
            printf("{i}: {x}", x=join(x, sep=", "))
          printf()
        

        Like

    • Frits's avatar

      Frits 11:30 am on 30 September 2021 Permalink | Reply

      A lot slower.

        
      from enigma import matrix
      from itertools import product
      
      # assign room numbers 1-5 to each of the groups:
      #
      #   Andy(A) Bill(B) Chris(C) Dave(D) Ed(E)
      #   English(F) French(G) German(H) Italian(I) Spanish(J)
      #   Brighton(K) Colchester(L) Dunstable(M) Gloucester(N) Reigate(P)
      
      eqs = [
        # [     NAMES     ]   [    STUDY      ]   [    TOWNS      ]  
        # A   B   C   D   E   F   G   H   I   J   K   L   M   N   P
        ( 0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, -1,  0,  0 ),  # B = M
        ( 0,  1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  0,  0,  0,  0 ),  # B = G
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0 ),  # L = 5
        ( 0,  0,  0,  1,  0,  0,  0,  0,  0,  0, -1,  0,  0,  0,  0 ),  # D = K
        ( 0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0, -1,  0 ),  # N + 1 = H
        ( 0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # C = 3
        ( 0,  0,  0,  0,  1,  0,  0,  0, -1,  0,  0,  0,  0,  0,  0 ),  # E = I
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 ),  # J = 2
        ( 1,  1,  1,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # A + B + C + D + E = 15
        ( 0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0 ),  # F + G + H + I + J = 15
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1 ),  # K + L + M + N + P = 15
        ( 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # A = ?
        ( 0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # F = ?
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0 ),  # N = ?
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1 ),  # P = ?
       ]
      
      towns = ["Brighton", "Colchester", "Dunstable", "Gloucester", "Reigate"]
      langs = ["English", "French", "German", "Italian", "Spanish"]
      
      for (A, F, N, P) in product(range(1, 6), repeat = 4):
        # "The man reading English has a room whose number is two different
        # from the student from Reigate"
        if abs(F - P) != 2 or N == P: continue
      
        # solve equations
        s = matrix.linear(eqs, [0, 0, 5, 0, 1, 3, 0, 2, 15, 15, 15, A, F, N, P])
        
        # check that all numbers lie between 1 and 5
        if sorted(s[:5]) != [1, 2, 3, 4, 5]: continue
        if sorted(s[5:10]) != [1, 2, 3, 4, 5]: continue 
        if sorted(s[10:]) != [1, 2, 3, 4, 5]: continue
        
        lang = langs[s[5:10].index(A)]
        town = towns[s[10:].index(A)]
        print(f"Andy's home town is {town} and studies {lang}")
      

      Like

      • Frits's avatar

        Frits 12:13 pm on 30 September 2021 Permalink | Reply

        @Jim,

        Is there a more efficient way to enforce that 5 variables have distinct values 1, 2, 3, 4 and 5 (using matrix.linear)?

        Like

        • Jim Randell's avatar

          Jim Randell 1:20 pm on 30 September 2021 Permalink | Reply

          @Frits: The [[ matrix.linear() ]] solver finds solutions for a system of linear equations over a field (in this case the rationals), so if you want to further restrict the values of the solutions you have to check them after they are returned.

          In this case though there aren’t very many possible sets of permissible values, so we can just consider them directly:

          from enigma import (subsets, diff, singleton, printf)
          
          # start with towns (D, 5, B, H - 1, P)
          for (D, B, H1, P) in subsets((1, 2, 3, 4), size=4, select="P"):
            H = H1 + 1
            if H > 5: continue
          
            # then subjects (F = P +/- 2, B, H = H1 - 1, E, 2)
            ss = diff([1, 3, 4, 5], [B, H])
            if len(ss) != 2: continue
            for (F, E) in subsets(ss, size=2, select="P"):
              if abs(F - P) != 2: continue
          
              # calculate A
              A = singleton(diff([1, 2, 4, 5], [B, D, E]))
              if A is not None:
                printf("names = ({A}, {B}, 3, {D}, {E}); subjs = ({F}, {B}, {H}, {E}, 2); towns = ({D}, 5, {B}, {H1}, {P})")
          

          Like

  • Unknown's avatar

    Jim Randell 8:11 am on 23 September 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 855: Pity the poor postman 

    From The Sunday Times, 4th December 1977 [link]

    Extract (genuine!) from a national daily, 27th January 1977:

    Pity the poor postman trying to deliver letters along Stonebow Road in the village of Drake’s Broughton, in north-west England. Due to local government confusion the road boasts five houses with the number 1, four others are number 2, three have number 4, and two are numbered 6.

    To add to the postman’s woes there are four families called Davies in Stonebow Road, plus two named Bridges, three named Barker, and two named Webb.

    On the unwarranted supposition that:

    (i) the occupants of the said houses included all of the said families; but
    (ii) the postman’s problem was somewhat alleviated by the fact that no two families of the same name had the same house-number; and
    (iii) the house-numbers of the Webbs totalled to more than did those of the Bridges, while those of all the families with two of the four names totalled the  same as did those of all the families with the other two names.

    What were the house-numbers of the Barkers and Webbs respectively?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser855]

     
    • Jim Randell's avatar

      Jim Randell 8:11 am on 23 September 2021 Permalink | Reply

      This Python program runs in 49ms.

      Run: [ @replit ]

      from enigma import (multiset, subsets, partitions, printf)
      
      # the pool of house numbers
      ns = multiset.from_pairs([(1, 5), (2, 4), (4, 3), (6, 2)])
      
      # choose 2 house numbers for the Bridges
      for Brs in subsets(ns.keys(), size=2):
        sBrs = sum(Brs)
      
        # choose 2 house number for the Webbs
        ns1 = ns.difference(Brs)
        for Ws in subsets(ns1.keys(), size=2):
          sWs = sum(Ws)
          if not (sWs > sBrs): continue
      
          # choose 4 numbers for the Davies
          ns2 = ns1.difference(Ws)
          for Ds in subsets(ns2.keys(), size=4):
            sDs = sum(Ds)
      
            # choose 3 numbers for the Barkers
            ns3 = ns2.difference(Ds)
            for Bas in subsets(ns3.keys(), size=3):
              sBas = sum(Bas)
      
              # consider partitions of the sums ...
              for (p1, p2) in partitions([sBrs, sWs, sDs, sBas], 2):
                # that give the same total
                if sum(p1) == sum(p2):
                  printf("Bridges = {Brs}; Webbs = {Ws}; Davies = {Ds}; Barkers = {Bas} [{p1}, {p2}]")
      

      Solution: The Barkers are at 1, 4, 6. The Webbs are at 1, 4.

      And the total sum of their numbers is 16.

      The Davies are at 1, 2, 4, 6, and The Bridges are at 1, 2.

      The total sum of their numbers is also 16.

      There are house numbers 1, 2, 2 remaining.

      Like

  • Unknown's avatar

    Jim Randell 8:57 am on 21 September 2021 Permalink | Reply
    Tags: , ,   

    Teaser 2823: Queuing 

    From The Sunday Times, 30th October 2016 [link] [link]

    Tickets for the event went on sale at 09:30. The queue started at 09:00 when 2 people arrived, then 4 more at 09:01, 6 more at 09:02 and so on until 60 more arrived at 09:29. Just 16 people arrived at 09:30, 16 more at 09:31, 16 more at 09:32 and so on. Tickets were sold steadily at a rate of 25 per minute (one per person).

    Joe and I were the first to arrive at our respective minutes, but we had identical waiting times before being sold our tickets, and no-one waited for less time to get a ticket. Joe was lucky to get the last ticket to be sold.

    At what time did Joe join the queue?

    This version of the text is provided by the setter, and differs from the version published in The Sunday Times.

    [teaser2823]

     
    • Jim Randell's avatar

      Jim Randell 8:58 am on 21 September 2021 Permalink | Reply

      The problem with this puzzle (particularly in the form it was published in the newspaper) was working out the intended mechanics of the situation.

      We suppose that all people joining the queue, do so simultaneously at the start of each specified minute. And that when the tickets are sold, they sold sequentially (a single ticket booth), with each sale taking exactly 1/25 minute (= 0.04m = 2.4s). This is apparently what the setter intended.

      The following Python program simulates the sale of tickets as described, and looks for minimal wait times that occur for the first member of a joining clump, until the value is repeated (then the earlier one is the setter, and the later one Joe, and Joe gets the last ticket, so sales end).

      It produces a log of events as it goes, and runs in 72ms.

      Run: [ @replit ]

      from enigma import irange, inf, sprintf, printf
      
      # format a time, (m=0, f=0) = 09:00.00
      def fmt(m, f=0):
        (h, m) = divmod(m, 60)
        return sprintf("{h:02d}:{m:02d}.{f:02d}", h=h + 9, f=4 * f)
      
      # the queue, record groups as (number of people, start minute)
      q = [(0, -1)]
      
      # extend the queue, add n people with value m
      def extend(q, n, m):
        if n > 0:
          q.append((n, m))
      
      # pop the queue, identifying the first in a group, return (x, flag)
      def pop(q):
        ((n, m), flag) = (q[0], 0)
        if n == 0:
          # move on to the next group
          q.pop(0)
          ((n, m), flag) = (q[0], 1)
        q[0] = (n - 1, m)
        return (m, flag)
      
      # size of the queue
      def size(q):
        return sum(n for (n, m) in q)
      
      # t = tickets sold
      # W = shortest waiting time
      # data = information for minimal wait times
      (t, W, data) = (0, inf, None)
      
      # run a timer
      for i in irange(0, inf):
        # m = minutes, f = fractions (1/25) of a minute
        (m, f) = divmod(i, 25)
      
        # on the minute
        if f == 0:
          # up to 09:29, 2m + 2 people join the queue
          # afterwards, 16 people join the queue
          n = (2 * m + 2 if m < 30 else 16)
          extend(q, n, m)
          printf("time {i}: added {n} people to queue, queue length = {q}", i=fmt(m, f), q=size(q))
      
        # from 9:30, one ticket is sold per frame
        if m > 29:
          t += 1
          # calculate waiting time (in frames)
          (x, flag) = pop(q)
          w = 25 * (m - x) + f
          printf("time {i}: joined at {x}, waited {w} frames, ticket {t}", i=fmt(m, f), x=fmt(x))
          # is this a new minimum?
          if not (w > W):
            printf("-> min wait = {w} frames for ticket {t}")
            if w < W:
              W = w
              data = list()
            # is this the first in their group?
            if flag:
              # record data
              data.append((x, m, f, t))
              # are we done?
              if len(data) == 2:
                printf("-> sold out after ticket {t}, queue length = {q}\n", q=size(q))
                # output solution
                for (n, (x, m, f, t)) in enumerate(data, start=1):
                  printf("{n} joined at {x}, served at {m}, ticket {t}", x=fmt(x), m=fmt(m, f))
                break
      

      Solution: Joe joined the queue at 10:06.

      The minimal wait time is 24.24 minutes (= 606/25 minutes).

      When the 1507 tickets sell out there are 399 people remaining in the queue.

      The setter joined the queue at 09:12, and got the 157th ticket at 09:36.24.

      Joe joined the queue at 10:06, and got the 1507th (and final) ticket at 10:30.24.

      So, if the tickets are numbered consecutively from 1, the digit sum of the two tickets is the same (and if they are numbered with leading zeros where necessary, the ticket numbers are anagrams of each other).

      Like

      • Jim Randell's avatar

        Jim Randell 9:03 am on 21 September 2021 Permalink | Reply

        The puzzle text as originally published in the newspaper read as follows:

        Tickets for the event went on sale at 09:30. The queue started at 09:00 when 2 people arrived, then 4 more at 09:01, 6 more at 09:02 and so on until 60 more arrived at 09:29. Just 16 people arrived at 09:30, 16 more at 09:31, 16 more at 09:32 and so on. Tickets were sold at 25 per minute (with one to each person in the queue) until they were sold out.

        Joe and I both had identical waiting times before being sold our tickets, despite me having arrived earlier, and no-one who got a ticket waited for less time than us.

        At what time did Joe join the queue?

        My interpretation of this was that people joined the queue simultaneously at the start of a minute. And when the tickets were sold, the 25 people at the head of the queue were processed simultaneously, at the start of each minute. (You can suppose there are 25 ticket booths, and each transaction takes exactly one minute). The wait times will always be whole numbers of minutes.

        The main problem I came across with this method was that we don’t know how many tickets there are (it is implied that they sell out at some point), but if we suppose Joe is in the last batch of 25 tickets sold we can solve the problem.

        Here is my code adapted to process the tickets 25 at a time.

        from enigma import (irange, inf, sprintf, printf)
        
        # format a time, m=0 = 09:00
        def fmt(m):
          (h, m) = divmod(m, 60)
          return sprintf("{h:02d}:{m:02d}", h=h + 9)
        
        # the queue, record groups as (number of people, start minute)
        q = [(0, -1)]
        
        # extend the queue, add n people with value m
        def extend(q, n, m):
          if n > 0:
            q.append((n, m))
        
        # pop the queue, identifying the first in a group, return (x, flag)
        def pop(q):
          ((n, m), flag) = (q[0], 0)
          if n == 0:
            # move on to the next group
            q.pop(0)
            ((n, m), flag) = (q[0], 1)
          q[0] = (n - 1, m)
          return (m, flag)
        
        # size of the queue
        def size(q):
          return sum(n for (n, m) in q)
        
        # t = tickets sold
        # W = shortest waiting time
        # data = information for minimal wait times
        (t, W, data) = (0, inf, None)
        
        # run a timer (in minutes)
        for m in irange(0, inf):
        
          # up to 9:29, 2m + 2 people join the queue
          # afterwards, 16 people join the queue
          n = (2 * m + 2 if m < 30 else 16)
          extend(q, n, m)
          printf("time {m}: added {n} people to queue, queue length = {q}", m=fmt(m), q=size(q))
        
          # from 9:30, 25 tickets are processed per minute
          if m > 29:
            for _ in irange(1, min(25, size(q))):
              t += 1
              # calculate waiting time (in frames)
              (x, flag) = pop(q)
              w = m - x 
              printf("time {m}: joined at {x}, waited {w} minutes, ticket {t}", m=fmt(m), x=fmt(x))
              # is this a new minimum?
              if not (w > W):
                printf("-> min wait = {w} minutes for ticket {t}{s}", s=(' [first]' if flag else ''))
                if w < W:
                  W = w
                  data = list()
                # is this the first in their group?
                if flag:
                  # record data
                  data.append((x, m, t))
        
            # are we done?
            if len(data) > 1:
              printf("\nsold out after {t} tickets, min wait = {w} minutes, queue length = {q}", q=size(q))
              # output solution
              for (n, (x, m, t)) in enumerate(data, start=1):
                printf("{n} joined at {x}, served at {m}, ticket {t}", x=fmt(x), m=fmt(m))
              printf()
              break
        

        We get the solution:

        minimal wait time = 24 minutes
        setter joined queue @ 9:08, served @ 9:32, ticket 73
        joe joined queue @ 9:09, served @ 9:33, ticket 91
        100 tickets sold, 894 people remaining in queue

        Like

  • Unknown's avatar

    Jim Randell 9:03 am on 19 September 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 43: [Golf match] 

    From The Sunday Times, 14th January 1962 [link]

    Brown came into the clubhouse after his match with Jones. “We were all square after nine holes”, he said, “when Jones suggested that we should play for a small side-stake on the winning of the tenth hole, double the stake on the eleventh, double again on the twelfth, and so on. I agreed. We didn’t halve any of the next nine holes, the match was decided on the eighteenth green, and I found that I had won eleven shillings and ninepence”. We asked him who won the match. “Work it out”, said Brown.

    Who won the match, which of the last nine holes did Brown win, and what was the side-stake on the tenth hole?

    This puzzle was originally published with no title.

    [teaser43]

     
    • Jim Randell's avatar

      Jim Randell 9:03 am on 19 September 2021 Permalink | Reply

      B and J were equal after the first 9 holes (i.e. 4.5 holes each). And each won 4 of the next 8 holes, so that the match was decided on the 18th hole.

      If the stake on the 10th hole was k, then it increases in powers of 2, i.e. k, 2k, 4k, 8k, 16k, …, 256k.

      In order to come out on top B must have won the 256k hole (as the remaining holes sum to 255k), and so B won the match.

      In binary, B’s stake multiplier has 5 bits and is between 100001111 (= 271) and 111110000 (= 496), and J’s multiplier is (111111111 − B).

      In order for B to win 11s + 9d = 141d the stake on the 10th hole is 141 / (B − J), and I assume the stake is some sensible amount (i.e. a multiple of 1/4 d).

      This Python program runs in 53ms.

      from enigma import (bit_permutations, div, irange, printf)
      
      # choose B's stake multiplier
      for B in bit_permutations(0b100001111):
        J = 0b111111111 - B
        k = div(564, B - J)
        if k:
          hs = list(h + 10 for h in irange(0, 8) if B & (1 << h))
          printf("holes = {hs}; stake @ 10 = {k:g}d", k=0.25 * k)
        if B == 0b111110000: break
      

      Solution: Brown won the match. Brown won the 10th, 11th, 12th, 14th, 18th holes. The stake on the 10th hole was 3d.

      The holes Brown won give: (1 + 2 + 4 + 16 + 256)k = 279k

      And the holes Brown lost give: (8 + 32 + 64 + 128)k = 232k

      The difference is: 279k − 232k = 47k, so: k = 141d / 47 = 3d

      Like

    • Hugh Casement's avatar

      Hugh Casement 10:14 am on 19 September 2021 Permalink | Reply

      To bring it up to date a bit, he won £2.35
      — though when we compare (for example) the cost of sending a letter then and now I feel an equivalent initial stake would be quite a bit more than 5p.

      Like

    • Frits's avatar

      Frits 5:52 pm on 4 October 2021 Permalink | Reply

        
      from enigma import SubstitutedExpression, div
      
      # the alphametic puzzle 
      p = SubstitutedExpression(
        [
         # Brown won 4 holes on holes 10-17
         "sum([A, B, C, D, E, F, G, H]) == 4",
         # Brown won the last hole
         "I = 1",
         # Brown's profit (141 pence) is a multiple of the stake
         "div(141, sum([2 ** i if x else -1 * 2 ** i \
              for i, x in enumerate([A, B, C, D, E, F, G, H, I])]))",
        ],
        base=2,
        answer="(A, B, C, D, E, F, G, H, I), \
               sum([2 ** i if x else -1 * 2 ** i \
                   for i, x in enumerate([A, B, C, D, E, F, G, H, I])])",
        verbose=0,
        distinct="",
      )
      
      # print answer
      for (_, ans) in p.solve():
        print(f"Brown won the match and the holes "
              f"{[i + 10 for i, x in enumerate(ans[0]) if x]}"
              f" with side-stake of {141 // ans[1]} pence")
      

      Like

  • Unknown's avatar

    Jim Randell 4:35 pm on 17 September 2021 Permalink | Reply
    Tags:   

    Teaser 3078: Digital daisy-chains 

    From The Sunday Times, 19th September 2021 [link] [link]

    The number 798 is a “digital daisy-chain”; i.e., if you spell out each of its digits as a word, then the last letter of each digit is the first letter of the next. Furthermore, the number 182 is a “looped” digital daisy-chain because, in addition, the last letter of its last digit is the first letter of its first digit.

    I have written down a large looped digital daisy-chain (with fewer than a thousand digits!). The total of its digits is itself a digital daisy-chain.

    What is that total?

    [teaser3078]

     
    • Jim Randell's avatar

      Jim Randell 5:14 pm on 17 September 2021 Permalink | Reply

      When forming a loop the individual “links” must be “821” or “83”, and both of these have a digit sum of 11.

      So the digit sum of any loop is a multiple of 11, and we just need to find a multiple of 11 that forms a chain.

      The shortest link is length 2, so we only need to consider loops with up to 499 links, i.e. a total of 5489.

      This Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import irange, int2words, nsplit, tuples, printf
      
      # digits as words
      words = list(int2words(d) for d in irange(0, 9))
      
      # does a number form a chain?
      is_chain = lambda n: all(words[x][-1] == words[y][0] for (x, y) in tuples(nsplit(n), 2))
      
      # look for viable digit sums for loops
      for n in irange(1, 499):
        t = 11 * n
        if is_chain(t):
          printf("digit sum = {t}")
      

      Solution: The total of the digits in the loop is 583.

      The shortest loop would be “83” repeated 53 times (= 106 digits), and we can use “821” instead in any of the links giving us loops of up to 159 digits.

      The next highest possible sum would be 8382, which would require 762 links, so the loops would have between 1524 and 2286 digits.

      Like

      • Jim Randell's avatar

        Jim Randell 10:34 pm on 18 September 2021 Permalink | Reply

        As suggested by Frits below, it is faster to generate possible “chain” numbers, and look for those that are divisible by 11 (although I separated these conditions out, rather than interleaving them).

        Run: [ @replit ]

        from enigma import irange, int2words, group, subsets, unpack, div, printf
        
        # digits as words
        digits = irange(0, 9)
        words = list(int2words(d) for d in digits)
        
        # adjacency map for next digit
        adj = group(
          subsets(digits, size=2, select="M"),
          st=unpack(lambda x, y: words[x][-1] == words[y][0]),
          by=unpack(lambda x, y: x),
          f=unpack(lambda x, y: y),
        )
        
        # generate chain numbers less than M
        def chains(M):
          ss = list(digits)
          while ss:
            n = ss.pop(0)
            yield n
            if n:
              # expand the chain
              for d in adj.get(n % 10, ()):
                n_ = n * 10 + d
                if n_ < M:
                  ss.append(n_)
        
        # look for possible chain numbers ...
        for t in chains(500 * 11):
          # that are a multiple of 11
          n = div(t, 11)
          if n:
            printf("digit sum = {t}")
        

        Like

    • GeoffR's avatar

      GeoffR 9:39 pm on 17 September 2021 Permalink | Reply

      A programme with a similar basis to Jim’s solution.

      # Using Jim's number range/step 
      # Two digit numbers are 11, 22, 33, 44, 55, 66, 77, 88, 99 
      # They cannot be digital daisy chains as last letter
      # of first word is not the starting letter of second word
      
      from enigma import nsplit
      
      # Dictionary to look up words from digits
      DN = {0:'zero', 1:'one', 2:'two', 3:'three', 4:'four',
            5:'five', 6:'six', 7:'seven', 8:'eight', 9: 'nine'}
      
      for n in range(110, 5500, 11):
          # check three digit numbers
          if 100 < n < 1000:
              d1, d2, d3 = nsplit(n)
              # allocate words to digits
              w1 = DN[d1]
              w2 = DN[d2]
              w3 = DN[d3]
              # check last letter of one word is same
              # as first letter of next word
              if w1[-1] == w2[0] and w2[-1] == w3[0]:
                  print(f"Total is {n}.")
      
          # check four digit numbers
          if 1000 < n < 5500:
              d1, d2, d3, d4 = nsplit(n)
              # allocate words to digits
              w1 = DN[d1]
              w2 = DN[d2]
              w3 = DN[d3]
              w4 = DN[d4]
              # check last letter of one word is same
              # as first letter of next word
              if w1[-1] == w2[0] and w2[-1] == w3[0] \
                 and w3[-1] == w4[0]:
                  print(f"Total is {n}.")
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 12:32 pm on 18 September 2021 Permalink | Reply

      I did further work on digital daisy chains, finding 2, 3, 4 and 5-digit examples.

      Of the values found, five numbers were of the looped digital chain type i.e. 38, 83, 182, 218 and 821, with their digits summing to eleven.

      from itertools import permutations
      
      W = ['zero', 'one', 'two', 'three', 'four',
           'five', 'six', 'seven', 'eight', 'nine']
      
      for p in permutations(W, 2):
          w1, w2 = p
          if w1 == 'zero': continue
          if w1[-1] == w2[0]:
              print(w1, w2)
      
      # 18, 21, 38, 58, 79, 82, 83, 98
      print(); print()
      
      for p in permutations(W, 3):
          w1, w2, w3 = p
          if w1 == 'zero': continue
          if w1[-1] == w2[0] and w2[-1] == w3[0]:
              print(w1, w2, w3)
      
      # 182, 183, 218, 382, 582, 583, 798, 821, 982, 983
      
      # There were also 6 no. 4-digit digital daisy chains
      # i.e. 2183, 3821, 5821, 7982, 7983, 9821
      # A 5-digit digital daisy chain number was found i.e. 79821
      
      
      

      Like

    • Frits's avatar

      Frits 7:50 pm on 18 September 2021 Permalink | Reply

      Doing it a bit differently and not using modulo.

      dw = 'zero one two three four five six seven eight nine'.split()
      
      # credit: B. Gladman
      links = set((i, j) for j in range(10) for i in range(10) 
      			if dw[i][-1] == dw[j][0])
       
      # look for chains of 3 or 4 digits with the alternating sum  divisible by 11
      def chain(k, lst, rs=[], asum=0):
        if k == 5: return   # total is fewer than 5490
        else:  
          if k > 2 and asum in {-11, 0, 11}:
            t = int("".join(str(w) for w in (rs[0] + [y for x, y in rs[1:]])))
            if t <= 5489:	
              print("total =", t)
          for x, y in lst:
            if not rs or x == rs[-1][-1]:
      		# total must be a multiple of 11
      		# use alternating sum for testing divisibility by 11
              new = x - y if k == 1 else (-1)**k * y 
              chain(k + 1, lst, rs + [[x, y]], asum + new)
      
      # look for daisy-chain total
      chain(1, links)
      

      Like

  • Unknown's avatar

    Jim Randell 9:13 am on 16 September 2021 Permalink | Reply
    Tags: by: Adrian Winder   

    Brain-Teaser 186: [Adrian Winder’s problem] 

    From The Sunday Times, 15th November 1964 [link]

    A is 73 feet from a straight river, and B is on the same side of the river but not so far from it. M and N are the (distinct) points on the river nearest to A and B respectively. The lengths of AB, MN, and BN are whole numbers of feet.

    A man walks from A to B via the river, taking the shortest possible route, and this is also whole number of feet.

    How far does the man walk, and what is the direct distance from A to B?

    This puzzle was originally published with no title.

    This puzzle is included in the book Sunday Times Brain Teasers (1974).

    The puzzle is included as a postscript to the puzzles in the book, and the following text appears in the foreword:

    Perhaps the most outstanding example of teasing is Brain Teaser 186, of 15th November 1964, which was based on a deceptively simple diagram by an undergraduate, Adrian Winder, who died in a road accident just before his puzzle was published. Few correct answers were submitted; within the year there were 250 requests for the full solution; and still, from time to time, yet another reader concedes defeat and begs to be relieved of his misery.

    Mr Winder’s problem, with his solution, is published as a fitting postscript to this collection.

    — Anthony French

    This brings the total number of puzzles available on S2T2 to 550, but this is less than 20% of the Brain Teaser puzzles published in The Sunday Times.

    [teaser186]

     
    • Jim Randell's avatar

      Jim Randell 9:14 am on 16 September 2021 Permalink | Reply

      The direct route (h = AB) and the indirect route (z = AB′ = z1 + z2) are the hypotenuses of right-angled triangles (AXB and AXB′ respectively):

      h² = x² + d²
      z² = (146 − x)² + d²

      For a given value of x we can determine values for h, d and z by looking at the divisors of x²:

      h² − d² = x²
      (h − d)(h + d) = x² = a⋅b
      h = (a + b) / 2
      d = (b − a) / 2
      z = √((146 − x)² + d²)

      This Python program runs in 51ms. (Internal run time is 1.3ms).

      Run: [ @replit ]

      from enigma import (irange, divisors_pairs, div, is_square, printf)
      
      for x in irange(1, 72):
        for (a, b) in divisors_pairs(x * x):
          h = div(a + b, 2)
          d = div(b - a, 2)
          if not (h and d): continue
          t = 146 - x
          z = is_square(t * t + d * d)
          if not z: continue
          printf("x={x}; h={h} d={d} z={z}")
      

      Solution: The man walks 169 ft between A and B. The direct distance is 123 ft.

      B is 46 ft from the river (BN = 46). And M and N are 120 ft apart (MN = 120).

      Like

    • Frits's avatar

      Frits 11:32 am on 16 September 2021 Permalink | Reply

      This Python program runs in 75ms (with PyPy).

        
      from enigma import SubstitutedExpression, is_square
      
      # h^2 = x^2 + d^2                      d < 2592 ((72^2 - 1) / 2) 
      # z^2 = (146 – x)^2 + d^2  
      
      # the alphametic puzzle
      p = SubstitutedExpression([
          "DE < 26",
          "0 < XY < 73", 
          "is_square(XY**2 + DEFG**2) = HIJK",
          "is_square((146 - XY)**2 + DEFG**2) = ZABC",
          "DEFG > 0",
        ],
        answer="(ZABC, HIJK)",
        distinct="",        # allow symbols with same values
        d2i=dict([(k, "D") for k in range(3, 10)]),
        verbose=256,
      )
       
      # print answers
      for (_, ans) in p.solve(verbose=0):
        print(f"The man walks {ans[0]} ft between A and B. "
              f"The direct distance is {ans[1]} ft.")
      

      Like

    • Frits's avatar

      Frits 12:34 pm on 16 September 2021 Permalink | Reply

      This Python program runs in 4ms.

        
      from enigma import pythagorean_triples, is_square
      
      # h^2 = x^2 + d^2                      h < 2593 ((72^2 + 1) / 2) 
      # z^2 = (146 – x)^2 + d^2  
      # x < 73
      
      for (p, q, h) in pythagorean_triples(2592):
        if p > 72: continue
        if q > 72:
          # (x, d) must be (p, q)
          z = is_square((146 - p)**2 + q**2)
        else:
          # (x, d) can be (p, q) or (q, p)
          z = is_square((146 - p)**2 + q**2)
          if not z:
            z = is_square((146 - q)**2 + p**2)
          
        if z:
          print(f"The man walks {z} ft between A and B. "
                f"The direct distance is {h} ft.")
      

      Like

      • Jim Randell's avatar

        Jim Randell 1:58 pm on 16 September 2021 Permalink | Reply

        Here’s a version that only uses the list of Pythagorean triples (and doesn’t use [[ is_square() ]]).

        from enigma import (pythagorean_triples, ordered, printf)
        
        # we can show:
        #
        #   d <= (x^2 - 1) / 2
        #
        # and max x = 72, so:
        #
        #   d < 2592
        #
        # hence:
        #
        #   z^2 <= 145^2 + 2592^2
        #   z < 2597
        
        # find pythagorean triples, map: (a, b) -> c
        triples = dict(((a, b), c) for (a, b, c) in pythagorean_triples(2597))
        
        # consider triples
        for ((a, b), c) in triples.items():
          if a > 72: continue
          # consider x=a, d=b, h=c
          z = triples.get(ordered(b, 146 - a))
          if z:
            printf("x={a}; h={c} d={b} z={z}")
          if b < 73:
            # consider x=b, d=a, h=c
            z = triples.get(ordered(a, 146 - b))
            if z:
              printf("x={b}; h={c} d={a} z={z}")
        

        Although it is not quite as fast as my original program.

        Like

    • John Crabtree's avatar

      John Crabtree 2:52 pm on 22 September 2021 Permalink | Reply

      In theory there should be a way to solve brain teasers manually. Today I take this to mean without the use of computers or programable calculators. When this teaser was published in 1964, handheld calculators were some way off.

      Putting h = d + m and z = d + n, and then
      d = (x^2 – m^2)/(2m) = ((146 – x)^2 – n^2)/(2n) with n > m
      where if x is odd, m and n are odd, and if x ix even, m and n are even.
      Effectively, this is a way of finding Pythagorean triples with a common shorter side.

      Then for each value of x, one can look for values of m and n which give the same value of d. I did this with the aid of a calculator. I could have done it by hand with a table of squares, but it would have been tedious. The ratio of n/m is approximately (146 – x)^2/x^2. One can use this to reduce the number of calculations. The results took up two and half sides of paper. I found x = 27, m = 3, n = 49, which gave d = 120. The solution = d + n = 169.

      In summary, this is very challenging but accessible teaser. If Brian Gladman’s table of Pythagorean triples sorted by shorter sides went to 140, it would be very straightforward.

      Like

      • John Crabtree's avatar

        John Crabtree 5:02 pm on 22 September 2021 Permalink | Reply

        The man walks d + n = 169 feet. The direct distance is d + m = 123 feet.

        Like

  • Unknown's avatar

    Jim Randell 9:41 am on 14 September 2021 Permalink | Reply
    Tags:   

    Teaser 2822: Losing weight 

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

    The members of our local sports club are split into two groups. Originally the groups had the same number of members, and the first group contained member Waites who weighed 100kg (and indeed that was the average weight of the members of the first group). Then the club trainer decided that the groups were overweight and that, for each of the next ten weeks, for each group the average weight of the members should be reduced by one kilogram.

    This was achieved by simply transferring a member from the first group to the second group each week, and Waites was the tenth member to be transferred.

    How many members are there in the club?

    [teaser2822]

     
    • Jim Randell's avatar

      Jim Randell 9:42 am on 14 September 2021 Permalink | Reply

      This Python program considers the number of members in each group, and performs the transfers to see if the conditions of the puzzle are met.

      It runs in 54ms.

      Run: [ @replit ]

      from enigma import irange, inf, printf
      
      # S = starting average weight of group A
      # K = number of transfers from A -> B
      # W = weight of transfer number K
      (S, K, W) = (100, 10, 100)
      
      # suppose each group has n members
      for n in irange(K + 1, inf):
        m = 2 * n
        printf("n={n} ({m} members)")
        # initial total weight of the group is...
        t0 = S * n
        # K members of the group are transferred
        for k in irange(1, K):
          # the total weight becomes
          t1 = (S - k) * (n - k)
          w = t0 - t1
          # average weights for group A and group B
          (a, b) = (S - k, w + n + k - 1)
          printf("{k}: transfer = {w}, A avg {a0} -> {a}, B avg {b0} -> {b}", a0=a + 1, b0=b + 1)
          t0 = t1
      
        if w == W:
          printf("*** SOLUTION: n={n} ({m} members) ***")
          break
        printf()
      

      Solution: There are 38 members in the club.

      The average weight in Group 1 decreases by 1 kg per week from 100 to 90, and the average weight in Group 2 decreases by 1 kg per week from 138 to 128. After 10 weeks there are 9 members in Group 1 and 29 members in Group 2.


      Analytically:

      If we suppose each group has n members, the the total weight of Group 1 starts out at 100n.

      After the first transfer the average weight of Group 1 is 99 kg, so the total weight is 99(n − 1).

      And the difference between these two totals accounts for the weight of the first person transferred:

      w[1] = 100n − 99(n − 1)
      w[1] = n + 99

      After second transfer the average weight of Group 1 is 98kg, so the weight of the second person transferred is:

      w[2] = 99(n − 1) − 98(n − 2)
      w[2] = n + 97

      In general at the kth transfer, we have:

      w[k] = (100 − k + 1)(n − k + 1) − (100 − k)(n − k)
      w[k] = n + 101 − 2k

      And we are told the weight of the 10th person transferred is 100 kg:

      w[10] = 100
      n + 101 − 20 = 100
      n = 19

      So, there are 2n = 38 members in total.

      Like

  • Unknown's avatar

    Jim Randell 4:33 pm on 10 September 2021 Permalink | Reply
    Tags:   

    Teaser 3077: Shuffling series schedules 

    From The Sunday Times, 12th September 2021 [link] [link]

    A TV company planned a set of programmes to fill a weekly slot (one programme per week for many weeks) with six consecutive series of three different types (Arts, Biography and Comedy). None of the series was followed by another of the same type (e.g. there could be an Arts series for three weeks then a Comedy series for four weeks and so on). Then it decided to change the order of the series within the same overall slot, but to minimise disruption it would not alter the gaps between series of the same type. It did this by scheduling each of the three Arts series 6 weeks earlier than first planned, each of the two Biography series 20 weeks later than first planned, and the Comedy series 21 weeks earlier than first planned.

    How many programmes are in each of the six series after the change, in order?

    [teaser3077]

     
    • Jim Randell's avatar

      Jim Randell 5:31 pm on 10 September 2021 Permalink | Reply

      The following Python program finds orders that satisfy the given conditions, and then choose a “before” and “after” ordering, and uses them to construct a collection of 6 equations in 6 variables, which it then solves for positive integer solutions.

      It runs in 56ms.

      Run: [ @replit ]

      from enigma import subsets, tuples, multiset, matrix, as_int, map2str, join, printf
      
      # the programs
      progs = "AAABBC"
      
      # label a sequence, e.g. label(ABCABA) -> (A1 B1 C1 A2 B2 A3)
      def label(ps):
        m = multiset()
        s = list()
        for p in ps:
          m.add(p)
          s.append(p + str(m.count(p)))
        return s
      
      # find orderings of the programs with no consecutive letters
      orders = list(label(s) for s in subsets(progs, size=len, select="mP") if all(x != y for (x, y) in tuples(s, 2)))
      
      # choose a before and after ordering
      for (before, after) in subsets(orders, size=2, select="P"):
      
        # name the variables
        vs = sorted(before)
      
        # construct a collection of equations
        eqs = list()
        for v in vs:
          eq = [0] * len(vs)
          # add in the after amounts
          for q in after:
            if q == v: break
            eq[vs.index(q)] += 1
          # and subtract the before amounts
          for q in before:
            if q == v: break
            eq[vs.index(q)] -= 1
          eqs.append(eq)
          
        # solve the equations, for positive integers
        try:
          rs = list(as_int(x, '+') for x in matrix.linear(eqs, [-6, -6, -6, 20, 20, -21]))
        except ValueError:
          continue
      
        # output solution
        printf("runs: {rs} [before = {bf}; after = {af}]",
          rs=map2str(zip(vs, rs), sep=" ", enc=""),
          bf=join(before, sep=" ", enc="()"),
          af=join(after, sep=" ", enc="()"),
        )
      

      Solution: The number of programmes in each series after the change are: 5, 6, 9, 6, 5, 6.

      The two schedules are:

      Before: B1 (6); A1 (5); B2 (6); A2 (9); C1 (6); A3 (5)
      After: A1 (5); C1 (6); A2 (9); B1 (6); A3 (5); B2 (6)


      Manually:

      Neither A1 nor C1 can start the “before” sequence (as the As and Cs must be earlier in the “after” sequence), so the before sequence must start with B1, and the 5 remaining spaces must be (A1, ?, A2, ? A3), in order for the As not to be consecutive:

      before = (B1, A1, C1, A2, B2, A3)
      before = (B1, A1, B2, A2, C1, A3)

      Both these sequences end in A3, so A3 cannot be last in the “after” sequence, and in order for the As to be non-consecutive we have (A1, ?, A2, ?, A3, ?), giving:

      after = (A1, B1, A2, B2, A3, C1)
      after = (A1, B1, A2, C1, A3, B2)
      after = (A1, C1, A2, B1, A3, B2)

      The first of these is eliminated as C1 must occur 21 weeks earlier than in “before”, so cannot come at the end. So B2 comes at the end.

      We can go ahead and look at the equations produced by the 4 possibilities:

      before = (B1, A1, C1, A2, B2, A3)
      after = (A1, B1, A2, C1, A3, B2)

      [A1] (0) − (B1) = −6 ⇒ B1 = 6
      [B1] (A1) − (0) = +20 ⇒ A1 = 20
      [C1] (A1 + B1 + A2) − (B1 + A1) = −20 ⇒ A2 = −20 [***impossible***]

      before = (B1, A1, C1, A2, B2, A3)
      after = (A1, C1, A2, B1, A3, B2)

      [A1] (0) − (B1) = −6 ⇒ B1 = 6
      [C1] (A1) − (B1 + A1) = −21 ⇒ −6 = −21 [***impossible***]

      So “before” must be (B1, A1, B2, A2, C1, A3):

      before = (B1, A1, B2, A2, C1, A3)
      after = (A1, B1, A2, C1, A3, B2)

      [B1] (A1) − (0) = +20 ⇒ A1 = 20
      [A1] (0) − (B1) = −6 ⇒ B1 = 6
      [A2] (A1 + B1) − (B1 + A1 + B2) = −6 ⇒ B2 = 6
      [C1] (A1 + B1 + A2) − (B1 + A1 + B2 + A2) = −21 ⇒ B2 = 21
      [B2] (A1 + B1 + A2 + C1 + A3) − (B1 + A1) = +20 ⇒ A3 = −7 [***impossible***]

      So, there is only one possibility left:

      before = (B1, A1, B2, A2, C1, A3)
      after = (A1, C1, A2, B1, A3, B2)

      [A1] (0) − (B1) = −6 ⇒ B1 = 6
      [A3] (A1 + C1 + A2 + B1) − (B1 + A1 + B2 + A2 + C1) = −6 ⇒ B2 = 6
      [A2] (A1 + C1) − (B1 + A1 + B2) = −6 ⇒ C1 = 6
      [C1] (A1) − (B1 + A1 + B2 + A2) = −21 ⇒ A2 = 9
      [B1] (A1 + C1 + A2) − (0) = +20 ⇒ A1 = 5
      [B2] (A1 + C1 + A2 + B1 + A3) − (B1 + A1) = +20 ⇒ A3 = 5

      Hence: A1=5, A2=9, A3=5, B1=6, B2=6, C1=6.

      Like

      • Frits's avatar

        Frits 1:30 pm on 12 September 2021 Permalink | Reply

        @Jim

        Just wondering, how did you come up with this linear equations approach?

        Like

        • Jim Randell's avatar

          Jim Randell 1:46 pm on 12 September 2021 Permalink | Reply

          There are six unknowns we need to work out (the number of programs in each series), and we were given six known values (the difference between the start weeks of each series in the “before” and “after” schedules). So it seemed like a good candidate for a collection of 6 equations in 6 unknowns.

          The start week of each series is just the sum of the number of programs of the preceding series, so we can express the difference between the start weeks of a series in both schedules as the difference between these sums, and that gives us six linear equations in the six unknowns.

          The [[ matrix.linear() ]] solver from the enigma.py library will reject any system of equations that is inconsistent or incomplete, and we look for solutions where the results are all positive integers, and only a single answer popped out, and in a very reasonable time, so there was no need for any additional analysis.

          Manually, a little bit of analysis shows us that there are only 2 possible “before” sequences, and 2 possible “after” sequences, and only one of the 4 possible pairings give equations that solve to give positive integers.

          Like

  • Unknown's avatar

    Jim Randell 9:48 am on 9 September 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 859: Sick transit 

    From The Sunday Times, 8th January 1978 [link]

    The State of Inertia, in a last-ditch effort to revive an ailing economy, has decided to go ahead with the controversial innovation of adding two new digits

    POW! (Written ↑)
    WHAM! (Written ↓)

    thus symbolising the stark alternatives facing the nation.

    In a massive referendum on the relative merits, the country came down in favour of POW! carrying the greater weight and accordingly WHAM! is interposed in a lower position than POW! among the ten old digits, the usual order of which is retained.

    Teething troubles from the consequential change to duodecimal-based arithmetic and to the new values of some of the old digits, are minimised by the free provision to everyone of school-age or over of PEST, an appropriate Pocket Electronic Summary Tabulator.

    To enable a check to be made on the correct working of the instruments every PEST comes with the answers to 35 × 64 and 54 × 66, one consisting entirely of the new shapes and the other of neither of them.

    What are the two answers ?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser859]

     
    • Jim Randell's avatar

      Jim Randell 9:49 am on 9 September 2021 Permalink | Reply

      Using P for “POW!” and W for “WHAM!”:

      If we interpret “interposed” to mean that P and W are inserted between 2 existing symbols, then we find a unique solution.

      The following Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, base2int, int2base, join, printf)
      
      # categorise a string
      # 'new' -> entirely composed of new symbols
      # 'old' -> entirely composed of old symbols
      # 'mix' -> a mixture of new and old symbols
      def categorise(s):
        s = set(s)
        assert bool(s)
        if s.issubset('PW'): return 'new'
        if not s.intersection('PW'): return 'old'
        return 'mix'
      
      # make possible base 12 symbol sets
      for (w, p) in subsets(irange(1, 9), size=2):
        syms = list("0123456789")
        syms.insert(p, 'P')
        syms.insert(w, 'W')
      
        # convert between strings and numbers
        s2n = lambda s: base2int(s, base=12, digits=syms)
        n2s = lambda n: int2base(n, base=12, digits=syms)
      
        # calculate: A = '35' * '64', B = '54' * '66'
        A = n2s(s2n('35') * s2n('64'))
        B = n2s(s2n('54') * s2n('66'))
        # one is entirely composed of new symbols and one entirely composed of old
        if set(map(categorise, (A, B))) == {'old', 'new'}:    
          printf("digits 0-11 = {syms}", syms=join(syms))
          printf("  35 * 64 = {A}; 54 * 66 = {B}")
      

      Solution: The two sums are: 35 × 64 = 2445 and 54 × 66 = ↓↑↑↓.

      The digits from 0 to 11 are represented by:

      0 1 2 3 W 4 5 P 6 7 8 9

      So the sums are:

      35 × 64 = 2445 [Inertial base 12]
      36 × 85 = 2556 [standard base 12]
      42 × 101 = 4242 [standard decimal]

      54 × 66 = WPPW [Inertial base 12]
      65 × 88 = 4774 [standard base 12]
      77 × 104 = 8008 [standard decimal]

      However, if we allow W and P to be inserted anywhere (but still with W before P), we find an additional solution using the following digits:

      W 0 1 2 3 P 4 5 6 7 8 9

      And the sums are:

      35 × 64 = 2194 [Inertial base 12]
      47 × 86 = 32B6 [standard base 12]
      55 × 102 = 5610 [standard decimal]

      54 × 66 = PPWW [Inertial base 12]
      76 × 88 = 5500 [standard base 12]
      90 × 104 = 9360 [standard decimal]

      We can see this additional solution by using the following call to [[ subsets() ]] in line 15:

      subsets(irange(0, 10), size=2, select='R')
      

      Like

  • Unknown's avatar

    Jim Randell 10:37 am on 7 September 2021 Permalink | Reply
    Tags: ,   

    Teaser 2820: Three ages 

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

    Today is Alan’s, Brian’s and Colin’s birthday. If I write down their ages in a row in that order then I get a six-figure number. If I write down their ages in a row in the reverse order (i.e., Colin’s followed by Brian’s followed by Alan’s) then I get a lower six-figure number. When I divide the difference between these two six-figure numbers by the total of their three ages the answer is Alan’s age multiplied by Colin’s.

    What are Alan’s, Brian’s and Colin’s ages?

    As published there are 2 possible solutions to this puzzle.

    This puzzle was not included in the published collection of puzzles The Sunday Times Brainteasers Book 1 (2019).

    [teaser2820]

     
    • Jim Randell's avatar

      Jim Randell 10:38 am on 7 September 2021 Permalink | Reply

      If we assume the ages have 2 digits each (something not mentioned in the puzzle text), then we can quickly use the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this problem.

      The following run file executes in 187ms.

      #! python3 -m enigma -rr
      
      # A = UV
      # B = WX
      # C = YZ
      
      SubstitutedExpression
      
      --distinct=""
      
      "(UV * YZ) * (UV + WX + YZ) + YZWXUV = UVWXYZ"
      

      And this finds two viable solutions.

      However it is not a great deal of work to write a program that considers ages that include 1-digit and 3-digit ages (with a suitable upper limit).

      The following Python program runs in 179ms, and finds the same two solutions.

      Run: [ @replit ]

      from enigma import (irange, printf)
      
      # permissible ages
      ages = irange(1, 120)
      
      for A in ages:
        for B in ages:
          AB = str(A) + str(B)
          if len(AB) > 5: break
          for C in ages:
            ABC = AB + str(C)
            if len(ABC) > 6: break
            if len(ABC) < 6: continue
            d = int(ABC) - int(str(C) + str(B) + str(A))
            if A * C * (A + B + C) == d:
              printf("A={A} B={B} C={C} [A:B:C={A}{B}{C} C:B:A={C}{B}{A}; d={d}]")
      

      Solution: The are two possible answers: Alan = 22, Brian = 61, Colin = 18; Alan = 99, Brian = 70, Colin = 33.

      These could be reduced to a single solution by adding one of the following conditions:

      (a) “None of the ages include the digit 0” → (22, 61, 18)
      (b) “Alan is older than Brian, who is older than Colin” → (99, 70, 33)

      The published solution is: “22, 61, 18 (or 99, 70, 33)”.

      We can run the Python program above to consider ages up to 9999, and we find that without constraining the values of A, B, C there are 5 possible solutions:

      (22, 61, 18)
      (37, 326, 3)
      (51, 830, 3)
      (78, 252, 6)
      (99, 70, 33)

      Like

    • GeoffR's avatar

      GeoffR 12:13 pm on 7 September 2021 Permalink | Reply

      # Using 2-digit ages: Alan = ab, Brian = cd, Colin = ef
      for ab in range(10, 100):
        for cd in range (10, 100):
          for ef in range(10, 100):
            abcdef = 10000*ab + 100*cd + ef  # ages in order
            efcdab = 10000*ef + 100*cd + ab  # ages in reverse order
            if abcdef < efcdab:continue
            diff = abcdef - efcdab
            if diff == (ab * ef) * (ab + cd + ef):
              print(f"Alan = {ab}, Brian = {cd}, Colin = {ef}")
              
      # Alan = 22, Brian = 61, Colin = 18
      # Alan = 99, Brian = 70, Colin = 33
      
      
      

      Like

      • Frits's avatar

        Frits 2:46 pm on 7 September 2021 Permalink | Reply

          
        # Using 2-digit ages: Alan = A, Brian = B, Colin = C
        # difference <d> does not depend on B
        for A in range(10, 100):
          sA = str(A)
          for C in range(10, A):
            # use dummy "10" for the value of B
            d = int(sA + "10" + str(C)) - int(str(C) + "10" + sA)
            (sumABC, r) = divmod(d, A * C)
            if r != 0: continue
            B = sumABC - A - C
            if not (9 < B < 100): continue
            print(f"Alan = {A}, Brian = {B}, Colin = {C}")
        

        Like

        • Jim Randell's avatar

          Jim Randell 4:11 pm on 7 September 2021 Permalink | Reply

          Or (still assuming 2-digit ages):

          from enigma import (irange, subsets, div, printf)
          
          # assuming 2 digit ages
          for (C, A) in subsets(irange(10, 99), size=2):
            B = div(9999 * (A - C), A * C)
            if B is None: continue
            B -= A + C
            if B < 10 or B > 99: continue
            printf("A={A} B={B} C={C}")
          

          or:

          #! python3 -m enigma -rr
          SubstitutedExpression
          
          --distinct=""
          
          "div(9999 * (UV - YZ), UV * YZ) - UV - YZ = WX"
          

          Like

      • Frits's avatar

        Frits 6:18 pm on 7 September 2021 Permalink | Reply

         
        # Using 2-digit ages: Alan = A, Brian = B, Colin = C
        #
        #  d = 9999 * (A - C) = (A * C) * (A + B + C)
        #    9999 = 3 * 3 * 11 * 101
        #
        # as A * C cannot be a multiple of 101 the sum A + B + C must be 101 or 202
        # this means that A * C must be a multiple of 99
        #            and (A * C) / (A - C) is 49.5 or 99
        
        for A in [x for x in range(11, 100) if x % 3 == 0 or x % 11 == 0]:
         for i, y in enumerate([A + 99, 2 * A + 99]):
            (C, r) = divmod(99 * A, y)
            if r > 0 or not (9 < C < 99): continue
            
            B = 101 - A - C if i == 0 else 202 - A - C
            if not (9 < B < 100): continue     # probably not needed
            
            print(f"Alan = {A}, Brian = {B}, Colin = {C}") 
        

        Like

      • Frits's avatar

        Frits 9:22 pm on 7 September 2021 Permalink | Reply

        Allowing for ages up to 999, again we don’t need to loop over B.

          
        # allow for high 3-digit ages
        for A in range(10, 1000):
          sA = str(A)
          
          if A < 100:
            # see previous posting
            rangeC = list(range(1, 10)) + \
                     [int((99 * A) / (A + 99)), int((99 * A) / (2 * A + 99))] 
          else:
            rangeC = range(1 , A  % 100)
          
          for C in rangeC:
            sC = str(C)
            if sC[0] > sA[0]: continue
            AC = sA + sC
            if len(sA + sC) > 5: break
            
            prodAC = A * C
            
            # allow Brian to be born on 9th October 2016
            minB = 10**(5 - len(AC)) if len(AC) != 5 else 0
            
            # how much does d decrement due to one increment of B? 
            if len(sA) > len(sC):
              difB = int((len(sA) - len(sC)) * "9" + "0")
            else:
              difB = 0
            
            # calculate difference for first B entry
            d = int(sA + str(minB) + sC) - int(sC + str(minB) + sA)  
            
            # rule: (A * C) * (A + minB + C) + n * (A * C) = d - n * difB 
            (n, r) = divmod(d - prodAC * (A + minB + C), prodAC + difB)
            
            if r > 0 or n < 0: 
              continue
            
            B = minB + n
            sB = str(B)
            
            ABC = sA + sB + sC
            if len(ABC) != 6: continue
            d = int(ABC) - int(sC + sB + sA)
            if prodAC * (A + B + C) == d:
              print(f"A={A} B={B} C={C} [A:B:C={A}{B}{C} C:B:A={C}{B}{A}; d={d}]")
        

        Like

        • Jim Randell's avatar

          Jim Randell 10:20 pm on 7 September 2021 Permalink | Reply

          Here’s my take on isolating B from the equation, by adjusting the definition of [[ ages() ]] you can allow whatever range of ages you want (including up to 9999).

          from itertools import product
          from enigma import (irange, div, printf)
          
          # decompose t into n numbers, min m
          def decompose(t, n, m=1, s=[]):
            if n == 1:
              if not (t < m):
                yield s + [t]
            else:
              for x in irange(m, t - n + 1):
                yield from decompose(t - x, n - 1, m, s + [x])
          
          # acceptable k digit ages
          def ages(k):
            if k == 3:
              return irange(100, 120)
            if k < 3:
              return irange(10**(k - 1), (10**k) - 1)
          
          # consider number of digits a, b, c; a + b + c = 6
          for (a, b, c) in decompose(6, 3):
            # age ranges
            (rA, rB, rC) = (ages(k) for k in (a, b, c))
            if not (rA and rB and rC): continue
            # multipliers
            mA = 10**(b + c) - 1
            mB = (10**c) - (10**a)
            mC = 10**(a + b) - 1
            # consider values for A and C
            for (A, C) in product(rA, rC):
              AC = A * C
              B = div(A * mA - C * mC - AC * (A + C), AC - mB)
              if B is not None and B in rB:
                printf("A={A} B={B} C={C}")
          

          Like

          • Frits's avatar

            Frits 10:53 pm on 7 September 2021 Permalink | Reply

            @Jim, very concise.

            Almost twice as fast with PyPy (for ages up to 999) although having 4 times as many (innermost) loops (187920 and 44649).

            Like

          • Frits's avatar

            Frits 12:16 pm on 8 September 2021 Permalink | Reply

            @Jim,

            While trying decompose(11, 3) I got into memory problems (5,6 GB memory used).

            Although itertools product is supposed to be a generator the problem disappeared when using separate loops for A and C (25 MB memory used).

            Like

            • Jim Randell's avatar

              Jim Randell 12:41 pm on 8 September 2021 Permalink | Reply

              I suspect internally [[ product() ]] is remembering all the values of the inner loops, because it doesn’t know that range objects are restartable iterators. Which will end up using a lot of memory if the ranges are large.

              So in the general case it would be better to use two for loops. (Which was how it was originally before I decided to save a line and a level of nesting).

              Like

          • Jim Randell's avatar

            Jim Randell 4:00 pm on 9 September 2021 Permalink | Reply

            Since I end up writing [[ decompose() ]] functions a lot in puzzles, I’ve added a helper function to enigma.py to generate them for you (see [[ Decompose() ]] and [[ decompose() ]]).

            For example, this program becomes:

            from enigma import (decompose, irange, div, printf)
            
            # acceptable <k> digit ages
            def ages(k):
              if k == 3:
                return irange(100, 120)
              if k < 3:
                return irange(10**(k - 1), (10**k) - 1)
            
            # consider number of digits (a, b, c), a + b + c = 6
            for (a, b, c) in decompose(6, 3, increasing=0, sep=0, min_v=1):
              # age ranges
              (rA, rB, rC) = (ages(k) for k in (a, b, c))
              if not (rA and rB and rC): continue
              # multipliers
              mA = 10**(b + c) - 1
              mB = (10**c) - (10**a)
              mC = 10**(a + b) - 1
              # consider values for A and C
              for A in rA:
                for C in rC:
                  AC = A * C
                  B = div(A * mA - C * mC - AC * (A + C), AC - mB)
                  if B is not None and B in rB:
                    printf("A={A} B={B} C={C}")
            

            Like

    • Frits's avatar

      Frits 5:52 pm on 9 September 2021 Permalink | Reply

      @Jim, Thanks, I will look into it.

      Calculating rC after A is known is faster:

        
      rC = range(10 ** (c - 1), (int(str(A)[0]) + 1) * 10 ** (c - 1))
      

      Like

    • Frits's avatar

      Frits 2:07 pm on 10 September 2021 Permalink | Reply

      Finding a galactic object for “Brian” 9.5 billion years old.
      This program runs in 8 seconds with PyPy.

       
      from enigma import decompose
      from math import ceil
      
      # acceptable k digit ages
      def ages(k):
        if k == 11:
          # universe is approx. 13.8 billion years old
          return range(10 ** (k - 1), 138 * 10 ** (k - 3))
        else:  
          return range(10 ** (k - 1), (10 ** k))
        
      L = 13  # number of digits concatenated A, B and C
      
      print("number of digits =", L)
      cnt = 0 
      cntsols = 0
      
      # consider number of digits a, b, c; a + b + c = L
      for (a, b, c) in decompose(L, 3, increasing=0, sep=0, min_v=1):
        # skip if A * C * (A + B + C) will have too many digits
        if 2 * a + c - 2 > L or 2 * c + a - 2 > L: 
          continue
        
        # group A range by first digit
        rA = [range(i * 10**(a - 1),  i * 10**(a - 1) + 10**(a - 1)) 
              for i in range(1, 10)]
        rB = ages(b)
        
        # multipliers
        mA = 10 ** (b + c) - 1
        mB = (10 ** c) - (10 ** a)
        mC = 10 ** (a + b) - 1
        
        # consider values for A and C
        for i, grp in enumerate(rA, 1):
          rC = range(10 ** (c - 1), (i + 1) * 10 ** (c - 1))
          
          # check first entry in C loop (see below)
          A = i * 10**(a - 1)  
          C = rC[0]
          if A * C == mB: 
            C = rC[1]     # next entry, we don't want to divide by zero
          
          numeratorB = A * mA - C * mC - A * C * (A + C)
          denominatorB = A * C - mB
          
          # numeratorB decreases and denominatorB increases as C becomes higher
          (B, r) = divmod(numeratorB, denominatorB)
          
          d1 = A * mA + B * mB - C * mC
          if d1 <= 0:    
            # we need a positive distance
            if numeratorB > 0: 
              # check when numeratorB becomes negative
              # (mC + A**2 + A * C) * C - A * mA = 0
              # quadratic equation: A * C**2 + (mC + A**2) * C - A * mA = 0
              newC1 = (-1 * (mC + A**2) + ((mC + A**2)**2 + 4 * A**2 * mA)**.5) 
              newC1 /= (2 * A)
              newC2 = mB / A      # when will denominatorB become positive again?
              newCs = sorted([newC1, newC2])
              low = ceil(newCs[0])
              hgh = int(newCs[1])
              rC = range(low, hgh + 1)
         
          for A in grp:
            for C in rC:
              cnt += 1
              AC = A * C
              
              # difference rule: A * mA + B * mB - C * mC = A * C * (A + B + C)
              if AC == mB: continue  # don't divide by zero
              
              (B, r) = divmod(A * mA - C * mC - AC * (A + C), AC - mB)
              
              if B < 0: break  
              
              if r == 0 and B in rB:
                d1 = int(str(A) + str(B) + str(C)) - int(str(C) + str(B) + str(A))
                d2 = AC * (A + B + C)
                
                print(f"A={A} B={B} C={C}, AC={AC} diff1={d1} diff2={d2}")
                cntsols += 1
            
      print("number of solutions", cntsols, "loop count =", cnt) 
      

      Like

    • GeoffR's avatar

      GeoffR 1:49 pm on 12 September 2021 Permalink | Reply

      
      ' A Solution in Visual Basic 2019
      Module Module1
        Public Sub Main()
          Dim A, B, C As Integer ' for Alan, Brian and Colin
          Dim AGES, Rev_AGES, DIFF_AGES As Integer
          For A = 10 To 99
            For B = 10 To 99
              If B = A Then
                Continue For
              End If
              For C = 10 To 99
                If C = B Or C = A Then
                  Continue For
                End If
                AGES = 10000 * A + 100 * B + C
                Rev_AGES = 10000 * C + 100 * B + A
                If AGES > Rev_AGES Then
                  DIFF_AGES = AGES - Rev_AGES
                  If DIFF_AGES = (A + B + C) * (A * C) Then
                    Console.WriteLine("Alan = {0}, Brian = {1}, Colin = {2}", A, B, C)
                  End If
                End If
              Next   'C
            Next   'B
          Next   'A
          Console.ReadKey()   'Freeze console screen
        End Sub
      End Module
      
      'Alan = 22, Brian = 61, Colin = 18
      'Alan = 99, Brian = 70, Colin = 33
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:33 pm on 3 September 2021 Permalink | Reply
    Tags:   

    Teaser 3076: Bee lines 

    From The Sunday Times, 5th September 2021 [link] [link]

    Three bees are each trapped inside an empty cuboidal box, each box of a different size, none of whose faces are squares. The lengths of the internal edges of each box in centimetres are whole numbers, and the volume of each box is no more than a litre. Starting at a corner, each bee moves only in straight lines, from corner to corner, until it has moved along every edge of its box. The only points a bee visits more than once are corners of its box, and the total distance moved by each bee is a whole number of centimetres. Given the above, the sum of these three distances is as small as it could be.

    What is the sum of the distances that the bees moved?

    [teaser3076]

     
    • Jim Randell's avatar

      Jim Randell 5:40 pm on 3 September 2021 Permalink | Reply

      After a couple of false starts I think I have found the answer.

      We assume the bees are points of negligible size travelling along the inside edges and faces of the boxes, which have internal integer dimensions. [Note: This may not be what the setter intends, see my additional comment below].

      If the cuboid has (increasing, internal) dimensions x, y, z, then in order to traverse all 12 edges (distance = 4x + 4y + 4z)) we need to also traverse the diagonals of 3 of the faces (as we do not need to form a closed path). So we need the diagonals of (at least) two of the three different face shapes to have integer values.

      This Python program looks for viable cuboid shapes, and then finds three with the shortest distances traversed. It runs in 62ms.

      Run: [ @replit ]

      from enigma import (irange, is_square, subsets, group, unpack, printf)
      
      # integer diagonal?
      diag = lambda *vs: is_square(sum(v * v for v in vs))
      
      # find possible cuboid dimensions (x, y, z) and distance traversed d
      # return (x, y, z, d)
      def generate():
        for z in irange(3, 499):
          for y in irange(2, z - 1):
            if not (y * z < 1000): break
            dyz = diag(y, z)
            for x in irange(1, y - 1):
              if not (x * y * z < 1000): break
              dxy = diag(x, y)
              dxz = diag(x, z)
              # calculate paths
              d = 4 * (x + y + z)
              for (a, b) in subsets(filter(None, (dxy, dxz, dyz)), size=2):
                dx = 2 * a + b
                yield (x, y, z, d + dx)
      
      # group cuboids by distance
      g = group(generate(), by=unpack(lambda x, y, z, d: d))
       
      # find 3 different cuboids with the smallest distances
      ss = set()
      t = 0
      for k in sorted(g.keys()):
        for (x, y, z, d) in g[k]:
          if (x, y, z) not in ss:
            ss.add((x, y, z))
            t += d
            printf("[{n}: ({x}, {y}, {z}) -> {d}]", n=len(ss))
            if len(ss) == 3: printf("total distance = {t}")
        if len(ss) >= 3: break
      

      The program above finds minimal paths where the bee is constrained to the interior surface of the box, but if the bee is permitted to fly between diametrically opposite corners of the box we get a different answer (see comment below for modified program).

      Solution: The sum of the distances is 397 cm.


      The net of a cuboid has 8 vertices, each with three edges to other vertices. In order to make a continuous path we need there to be only two vertices of odd order (for the start and finish of the path), or all vertices to be even (for a path that starts and finishes at the same vertex).

      Adding a diagonal link between two vertices, can reduce the number of even vertices by 2. Hence by adding in three diagonals between different vertices we can make a net with two vertices of odd order, which can be traversed (with these vertices as the start and finish of the path).

      We need to traverse all 12 edges, and an additional 3 diagonals, giving a minimum path of 15 lines. We can’t use diagonals that have points in common (e.g. we can only cross a face once), as the only points the bee is allowed to revisit are the vertices.

      Here is a diagram showing the traversal of the edges of a cuboid in 15 lines, using three “face” diagonals:

      (The path could be closed by traversing the diagonal cross the front of the box, but this is not required by the puzzle, and wouldn’t provided a minimal length path).

      To minimise the length of the path we want the paths that cross opposite faces (i.e. 6 and 14) to be the shortest available diagonal, and the remaining diagonal (i.e. 8) to be the next shortest.

      We find there are three viable boxes for this method:

      5 × 9 × 12: volume = 540; distance = 145
      6 × 8 × 15: volume = 720; distance = 153
      5 × 12 × 16: volume = 960; distance = 178

      Together these constitute the answer if the bee is restricted to crossing the interior surface of the box, and give a total of 476 cm.

      However, if we suppose the bee is permitted to fly (in a straight line) between diametrically opposite corners of the box (the puzzle text isn’t explicit on whether this is allowed or not), then there is another potential family of paths:

      Here the “space” diagonal through the interior space of the box is shown as a curved line, but the bee travels in a straight line between opposite vertices. And all four of the “space” diagonals meet at the central point of the cuboid, so we can only use one of them.

      (In this case closing the path would require traversing another “space” diagonal, so is not possible).

      This method provides another two viable boxes:

      3 × 4 × 12: volume = 144; distance = 99
      8 × 9 × 12: volume = 864; distance = 165

      Using the smallest of these, along with the smallest 2 previously found boxes gives a total distance of 397 cm.

      This latter distance is the published solution, so the bee must be allowed to fly (in a straight line) between diametrically opposite corners.

      Like

      • Frits's avatar

        Frits 10:53 pm on 3 September 2021 Permalink | Reply

        @Jim, I don’t know what you mean by “three edges”. I have the feeling your first solution was better (as I can visualize the route of the bee and it has a smaller distance).

        “no more than a litre” can be coded more accurately.

        Like

        • Jim Randell's avatar

          Jim Randell 9:05 am on 4 September 2021 Permalink | Reply

          @Frits: I realised that the path my previous solution was based on didn’t meet all the necessary requirements of the puzzle, so I had to revise my approach. (Which gave a longer, but more correct answer).

          Once a viable path for traversing the edges of the cuboid is worked out, you can proceed to look for paths which have integer lengths. (I’m going to put the path I worked out up with the solution, but if you want to see it now it is [here]).

          The only slightly worrying thing is that the part about minimising the sum. With my current approach there are only three possible boxes, so only one possible sum.

          Like

          • Jim Randell's avatar

            Jim Randell 11:15 am on 4 September 2021 Permalink | Reply

            I think the minimal sum comes about because there are longer possible paths on the same cube. (We can generate these by adding a [[ select="P" ]] parameter to the [[ subsets() ]] call on line 15. Without this it only generates the shorter variants).

            Like

      • Jim Randell's avatar

        Jim Randell 10:13 pm on 4 September 2021 Permalink | Reply

        If the bee is allowed to travel (fly) along a path through the centre of the cube to the opposite corner (something my original approach did not allow), we can adapt the program to include this diagonal as follows:

        Run: [ @replit ]

        from enigma import (irange, is_square, group, unpack, printf)
        
        # integer diagonal?
        diag = lambda *vs: is_square(sum(v * v for v in vs))
        
        # find possible cuboid dimensions (x, y, z) and minimal distance traversed d
        # return (x, y, z, d)
        def generate():
          for z in irange(3, 499):
            for y in irange(2, z - 1):
              if not (y * z < 1000): break
              dyz = diag(y, z)
              for x in irange(1, y - 1):
                if not (x * y * z < 1000): break
                dxy = diag(x, y)
                dxz = diag(x, z)
                dxyz = diag(x, y, z)
                # calculate minimal path
                ds = list(x for x in (dxy, dxz, dyz, dxyz) if x is not None)
                if len(ds) > 1:
                  (a, b) = ds[:2]
                  yield (x, y, z, 4 * (x + y + z) + 2 * a + b)
        
        # group cuboids by distance
        g = group(generate(), by=unpack(lambda x, y, z, d: d))
         
        # find 3 different cuboids with the smallest distances
        n = t = 0
        for k in sorted(g.keys()):
          for (x, y, z, d) in g[k]:
            n += 1
            t += d
            printf("[{n}: ({x}, {y}, {z}) -> {d}]")
            if n == 3: printf("total distance = {t}")
          if n >= 3: break
        

        This brings 2 more cuboids in to play, so there are now 5 to choose from.

        This is probably the scenario intended by the setter.

        (Although maybe the bee could cross a face diagonally in one direction by walking, and cross it in the other direction by flying, and thereby avoid visiting the centre of the face twice. This would bring me full circle back to my original (abandoned) approach where the bee crosses one face by 2 different diagonals).

        Like

        • Jim Randell's avatar

          Jim Randell 1:02 pm on 5 September 2021 Permalink | Reply

          I also wrote a program that performs an exhaustive search for the minimal path on each cuboid [@replit], which verifies that the programs above do find the minimal length paths in the cases where the space diagonal isn’t or is allowed.

          from enigma import (irange, inf, Accumulator, is_square, group, unpack, printf)
          
          # vertices are labelled: 0 - 8
          # moves are labelled by midpoints 0-19 (0 = n/a, +12 edges, +6 faces, +1 centre)
          
          # matrix of vertex to vertex moves, values = midpoints
          move = [
            #  0   1   2   3   4   5   6   7
            [  0,  1, 13,  4, 15, 19, 16,  9 ], # 0
            [  1,  0,  2, 13, 19, 17, 10, 16 ], # 1
            [ 13,  2,  0,  3, 18, 11, 17, 19 ], # 2
            [  4, 13,  3,  0, 12, 18, 19, 15 ], # 3
            [ 15, 19, 18, 12,  0,  5, 14,  8 ], # 4
            [ 19, 17, 11, 18,  5,  0,  6, 14 ], # 5
            [ 16, 10, 17, 19, 14,  6,  0,  7 ], # 6
            [  9, 16, 19, 15,  8,  14, 7,  0 ], # 7
          ]
          
          # find minimal path, visiting tgt midpoints
          # ws = weights for each midpoint 0-19, (0 for invalid midpoints)
          # tgt = target midpoints to collect
          # vs = vertices visited so far
          # r = accumulator for minimal path
          # ms = midpoints visited (so far)
          # t = total weight of path (so far)
          def path(ws, tgt, vs, r, ms=set(), t=0):
            # are we done?
            if tgt.issubset(ms):
              r.accumulate_data(t, vs)
            else:
              # extend the path
              for (v, m) in enumerate(move[vs[-1]]):
                if (not m) or (not ws[m]) or m in ms: continue
                t_ = t + ws[m]
                if not (t_ > r.value):
                  path(ws, tgt, vs + [v], r, ms.union([m]), t_)
          
          # integer diagonal?
          diag = lambda *vs: is_square(sum(v * v for v in vs))
          
          # find possible cuboid dimensions (x, y, z) and minimal distance traversed d
          # return (x, y, z, d)
          def generate():
            for z in irange(3, 499):
              for y in irange(2, z - 1):
                if not (y * z < 1000): break
                dyz = diag(y, z)
                for x in irange(1, y - 1):
                  if not (x * y * z < 1000): break
                  dxy = diag(x, y)
                  dxz = diag(x, z)
                  dxyz = diag(x, y, z)
                  # calculate minimal weight path, visiting all edges of the cuboid
                  ws = [0, x, y, x, y, x, y, x, y, z, z, z, z, dxy, dxy, dyz, dxz, dyz, dxz, dxyz]
                  tgt = set(irange(1, 12))
                  r = Accumulator(fn=min, value=inf)
                  path(ws, tgt, [0], r)
                  if r.value < inf:
                    printf("({x}, {y}, {z}) -> {r.value}")
                    yield (x, y, z, r.value)
          
          # group cuboids by distance
          g = group(generate(), by=unpack(lambda x, y, z, d: d))
           
          # find 3 different cuboids with the smallest distances
          n = t = 0
          for k in sorted(g.keys()):
            for (x, y, z, d) in g[k]:
              n += 1
              t += d
              printf("[{n}: ({x}, {y}, {z}) -> {d}]")
              if n == 3: printf("total distance = {t}")
            if n >= 3: break
          

          (To disallow the space diagonal replace [[ dxyz ]] with [[ 0 ]] on line 54).

          Like

    • Brian Gladman's avatar

      Brian Gladman 8:37 am on 4 September 2021 Permalink | Reply

      @Frits I don’t know what Jim’s earlier answer was but my current answer agrees with Jim’s current one. However, I must admit that I got there after a lot of path sketching to find the form of the shortest path and it is quite possible that I have missed a shorter arrangement.

      Like

    • Frits's avatar

      Frits 1:42 pm on 4 September 2021 Permalink | Reply

      If we need to traverse the diagonals of (at least) two of the three different face shapes we can look for Pythagorean triples that share a non-hypotenuse side.

        
      from enigma import pythagorean_triples
      
      pt = list(pythagorean_triples(200))
      
      s = set()
      # check if pythogorean triples share a non-hypotenuse side
      for p1 in pt:
        for p2 in pt:
          if p2 == p1: continue
          
          if p1[0] in p2[:2]:
            match = p1[0]
          elif p1[1] in p2[:2]:
            match = p1[1]
          else:
            continue   
         
          n3 = p2[0] if p2[1] == match else p2[1]  
          
          if p1[0] * p1[1] * n3 <= 1000:
            d = 4 * (p1[0] + p1[1] + n3) + 2 * (min(p1[2], p2[2])) +  max(p1[2], p2[2]) 
            s.add((d, ) + tuple(sorted([p1[0], p1[1], n3])))
            
      # more than 3 boxes?      
      if len(s) > 2:
        sol = sorted(s)[:3]
        print("answer:", sum(x[0] for x in sol))
        for x in sol: 
          print(x[1:], "with distance", x[0])
      

      Like

    • GeoffR's avatar

      GeoffR 7:25 am on 7 September 2021 Permalink | Reply

      I realise that my code can probably be shortened, but I wanted to show the full workings of this solution and therefore code optimisation was of secondary importance to me. My solution finds the five cuboids mentioned and the three minimum cuboids for the triple flight path solution.

      from itertools import combinations
      
      C = []  # list of cuboid flight path totals
      
      from math import isqrt
      
      def is_sq(x):
         return isqrt(x) ** 2 == x 
       
      # consider possible cuboid dimensions 
      for a, b, c in combinations(range(3, 50), 3):
        # each cuboid is less than one litre in volume
        if a * b * c <= 1000:
           
          # identify types of squares from dimensions a, b and c
          s1 = a*a + b*b
          s2 = a*a + c*c
          s3 = b*b + c*c
          s4 = a*a + b*b + c*c
          
          # separate research showed there were just two squares for each path
          if sum([is_sq(s1), is_sq(s2), is_sq(s3), is_sq(s4)]) == 2:
            # Type s1 and s2 squares contribute
            if is_sq(s1) and is_sq(s2):
              path = 4 *(a + b + c) + 2 * isqrt(a*a + b*b) + isqrt(a*a + c*c)
              C.append(path)
            # Type s1 and s3 squares contribute
            if is_sq(s1) and is_sq(s3):
              path = 4 *(a + b + c) + 2 * isqrt(a*a + b*b) + isqrt(b*b + c*c)
              C.append(path)
            # Type s1 and s4 squares contribute
            if is_sq(s1) and is_sq(s4):
              path = 4 *(a + b + c) + 2 * isqrt(a*a + b*b) + isqrt(a*a + b*b + c*c)
              C.append(path)
            # Type s2 and s3 squares contribute
            if is_sq(s2) and is_sq(s3):
              path = 4 *(a + b + c) + 2 * isqrt(a*a + c*c) + isqrt(b*b + c*c)
              C.append(path)
            # Type s2 and s4 squares contribute 
            if is_sq(s2) and is_sq(s4):
              path = 4 *(a + b + c) + 2 * isqrt(a*a + c*c) + isqrt(a*a + b*b + c*c)
              C.append(path)
            # Type s3 and s4 squares contribute
            if is_sq(s3) and is_sq(s4):
              path = 4 *(a + b + c) + 2 * isqrt(b*b + c*c) + isqrt(a*a + b*b + c*c)
              C.append(path)
      
      # A sorted list of flight path totals
      C = sorted(C)
      # find minimum sum of three flight paths
      Min_FP = C[0] + C[1] + C[2]
      
      print(f"All possible flight path lengths = {C} cm.")
      print(f"Length of three shortest flight paths = {Min_FP} cm.")
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:03 am on 2 September 2021 Permalink | Reply
    Tags: by: Gerald Fitzgibbon   

    Brain-Teaser 848: The magic class 

    From The Sunday Times, 16th October 1977 [link]

    The class only had nine pupils.

    Three girls sat in the front row, three boys in the back one, while in the middle rows the sexes sat alternately.

    Altogether, including the teacher, the sexes were equally divided.

    When their home-work was returned marks were compared and the children were surprised to discover that the total marks gained by those in each row were the same, as were also those for each column from front to back and for each diagonal of the square in which they sat. Excitedly they pointed out that fact to the teacher who replied that, when checking their work, which had been marked out of ten, he noticed that every digit had been used once and once only.

    Three was the lowest mark awarded to a girl.

    What was the highest mark given to a boy?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser848]

     
    • Jim Randell's avatar

      Jim Randell 10:05 am on 2 September 2021 Permalink | Reply

      The possible marks are: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. For a set of nine of them to use all ten digits requires 10 to be used, which means 0 and 1 cannot be used, so the marks are 2-10.

      These marks sum to 54, so the magic constant is 18, and the central square must be 6.

      The teacher is revealed to be male so the layout must be (front to back): (f f f) (f m f) (m m m).

      The following run file executes in 70ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      #  A B C     m m m <- back
      #  D E F  =  f m f
      #  G H I     f f f <- front
      
      SubstitutedExpression
      
      --base=11
      --digits="2-10"
      
      # all rows, columns, diagonals sum to 18
      "A + B + C == 18"
      "D + E + F == 18"
      "G + H + I == 18"
      "A + D + G == 18"
      "B + E + H == 18"
      "C + F + I == 18"
      "A + E + I == 18"
      "C + E + G == 18"
      
      # lowest female mark is 3
      "min(D, F, G, H, I) == 3"
      
      # remove duplicate (left/right) solutions
      "A < C"
      
      # answer is the highest male mark
      --answer="max(A, B, C, E)"
      
      # [optional]
      --template="(A B C) (D E F) (G H I)"
      --solution=""
      

      Solution: 9 was the highest mark awarded to a boy.

      The layout looks like this (or reflected about a vertical axis):

      Like

    • GeoffR's avatar

      GeoffR 4:08 pm on 2 September 2021 Permalink | Reply

      # Using the same integer (A - I) and m/f references
      
      from itertools import permutations
      for p1 in permutations(range(2, 11)):
        A, B, C, D, E, F, G, H, I = p1
        if (A + B + C == D + E + F == G + H + I
          == A + D + G == B + E + H == C + F + I
          == A + E + I == C + E + G):
          if min(D, F, G, H, I) == 3: #lowest girl mark
            HBM = max(A, B, C, E)  # highest boy mark
            print('All Marks ( A to I): ', A, B, C, D, E, F, G, H, I)
            print('Highest Boy Mark: ', HBM)
      
      # All Marks ( A to I):  7 2 9 8 6 4 3 10 5
      # Highest Boy Mark:  9
      # All Marks ( A to  I):  9 2 7 4 6 8 5 10 3
      # Highest Boy Mark:  9
      
      
      

      Like

    • Frits's avatar

      Frits 4:06 pm on 3 September 2021 Permalink | Reply

      The magic constant is 18, and the central square must be 6.

      All corners can’t be even as using odd marks for all the sides would make odd total marks.
      Only two opposite corners can’t be even as then the other 2 corners would have to be odd.
      To make total even marks we would need odd marks for all the sides as well.
      So all corners must be odd and all sides even.
      As 3 is in a bottom corner 9 must be in a top corner (male).
      The 10 mark must be adjacent to the 3 mark and thus female.

      So we can conclude that the highest mark given to a boy is a 9.

      Like

  • Unknown's avatar

    Jim Randell 10:12 am on 31 August 2021 Permalink | Reply
    Tags:   

    Teaser 2815: PIN-in-the-middle 

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

    For Max Stout’s various accounts and cards he has nine different 4-digit PINs to remember. For security reasons none of them is the multiple of another, and none is an anagram of another. He has written these PINs in a 3-by-3 array with one PIN in each place in the array. It turns out that the product of the three PINs in any row, column or main diagonal is the same. In fact there are just two different prime numbers that divide into this product.

    What is the PIN in the middle position of the array?

    [teaser2815]

     
    • Jim Randell's avatar

      Jim Randell 10:13 am on 31 August 2021 Permalink | Reply

      There are two primes p and q and each square of the grid contains some product of the two primes, and the whole forms a multiplicative magic square.

      Each square has a value: (p^m)(q^n) so the squares formed by the exponents (the “m-square” and the “n-square”) are both additive
      magic squares.

      If the PIN in the central square is (p^j)(q^k) then the magic constant of the multiplicative square is (p^3j)(q^3k) and the magic constants for the two additive magic squares are 3j and 3k.

      This Python program runs in 59ms.

      Run: [ @replit ]

      from enigma import (Primes, irange, inf, subsets, div, seq_all_different, nsplit, ordered, printf)
      
      primes = Primes(expandable=1)
      
      # generate multiplicative magic squares formed from two primes
      def solve():
      
        # consider values for p and q
        for q in primes.range(3, inf):
          for p in primes.range(2, q):
      
            # find 4 digit combinations of p and q
            ns = set()
            for k in irange(0, inf):
              qk = q ** k
              if qk > 9999: break
              for j in irange(0, inf):
                n = (p ** j) * qk
                if n > 9999: break
                if n > 3: ns.add(n)
      
            # choose E (the central number)
            for E in ns:
              # the magic constant is ...
              K = E ** 3
      
              # choose A (the smallest corner)
              for A in ns:
                if not (A < E): continue
      
                # compute I
                I = div(K, A * E)
                if I is None or I not in ns: continue
      
                # choose B (less than D)
                for B in ns:
                  # compute the remaining values
                  C = div(K, A * B)
                  if C is None or C < A or C not in ns: continue
                  F = div(K, C * I)
                  if F is None or F not in ns: continue
                  D = div(K, E * F)
                  if D is None or D < B or D not in ns: continue
                  G = div(K, A * D)
                  if G is None or G < A or C * E * G != K or G not in ns: continue
                  H = div(K, B * E)
                  if H is None or G * H * I != K or H not in ns: continue
      
                  # return the magic square
                  sq = (A, B, C, D, E, F, G, H, I)
                  if len(set(sq)) == 9:
                    yield sq
      
      
      for sq in solve():
        # check no numbers have the same digits
        if not seq_all_different(ordered(*nsplit(x, 4)) for x in sq): continue
        # check none of the numbers is a factor of another
        if any(b % a == 0 for (a, b) in subsets(sorted(sq), size=2)): continue
      
        # output the square
        (A, B, C, D, E, F, G, H, I) = sq
        printf("[ {A} {B} {C} | {D} {E} {F} | {G} {H} {I} ]")
        break # we only need the first solution
      

      Solution: The PIN in the middle is 2592.

      And here is a complete grid:

      The magic constant is: 2592^3 = 17414258688 = (2^15)(3^12).

      The numbers in brackets show the powers of 2 and 3, which can be seen to form additive magic squares with magic constants of 15 and 12.

      Like

    • Frits's avatar

      Frits 6:15 pm on 16 September 2021 Permalink | Reply

      See also [https://brg.me.uk/?page_id=4071] for 2 different approaches.

      Like

  • Unknown's avatar

    Jim Randell 8:57 am on 29 August 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 42: [Pounds and pence] 

    From The Sunday Times, 7th January 1962 [link]

    Our greengrocer is an odd sort of man. When I asked for 2 lb. of grapes, he counted out 64 grapes and asked for 4s. I raised my eyebrows, so he weighed them and was exactly right.

    “Oh”, I said, “and suppose I ask for beans?”

    “Try working it out for yourself”, he replied. “An onion is half the price of a carrot, 24 times the weight of a pea, and a quarter the price of a pound of beans. Seven times the number of peas that weigh the same as the number of onions that cost as much as 2 lb. of grapes, is less by the number of beans that weigh 12 lb. and the number of pence in the price of 16 lb. of carrots, than the number of beans that weigh as many pounds as the difference between the number of grapes that weigh the same as half the number of onions as there are carrots in a pound and a half, and the number of beans that cost as many pennies as there are grapes in the weight of a gross of peas. You get 6 times as many beans for the price of 6 carrots as you do grapes for the price of 2 lb. of onions. The cost of 3 carrots is to that of a grape as a bean is to a pea in weight, and two dozen carrots make three.”

    “Three what?” I asked.

    “Pounds, of course”, he replied.

    How many beans make five?

    This puzzle was originally published with no title.

    [teaser42]

     
    • Jim Randell's avatar

      Jim Randell 8:58 am on 29 August 2021 Permalink | Reply

      Working in prices of pennies and weights of 1lb, we can try to determine the following values for each item:

      (number of items per lb)
      (price of 1lb of items)
      (weight per item) = 1 / (items per lb)
      (price per item) = (price of 1lb items) / (items per lb)

      Untangling the facts we are given:

      “2 lb. of grapes = 64 grapes; cost = 4s. = 48d”

      (grapes per lb) = 32
      (weight per grape) = 1/32
      (price of 1lb grapes) = 24
      (price per grape) = 3/4

      “An onion is half the price of a carrot, 24 times the weight of a pea, and a quarter the price of a pound of beans.”

      (price per carrot) = k
      (price per onion) = k/2

      (peas per lb) = p
      (weight per pea) = 1/p
      (onions per lb) = p/24
      (weight per onion) = 24/p
      (price of 1lb onions) = (p/24) × (k/2) = kp/48

      (price per onion) = (price of 1lb beans) / 4
      (price of 1lb beans) = 4 × (k/2) = 2k

      “The cost of 3 carrots is to that of a grape as a bean is to a pea in weight”

      (price of 3 carrots) / (price of 1 grape) = (weight of 1 bean) / (weight of 1 pea)
      (price of 3 carrots) × (weight of 1 pea) = (weight of 1 bean) × (price of 1 grape)
      (3k) × (1/p) = (weight of 1 bean) × (3/4)
      (weight of 1 bean) = 4k/p
      (beans per lb) = p/4k

      (price per bean) = (price of 1lb beans) / (beans per lb)
      (price per bean) = (2k) / (p/4k)
      (price per bean) = 8k²/p

      So the answer to the question: “how many beans in 5 lb?”, is: 5 × (p/4k).

      All we need to do now is determine k and p.

      “You get 6 times as many beans for the price of 6 carrots as you do grapes for the price of 2 lb. of onions.”

      (number of beans for (price of 6 carrots)) = 6× (number of grapes for (price of 2lb onions))
      (number of beans for 6k pence) = 6× (number of grapes for kp/24 pence)
      (6k) / (8k²/p) = 6× (kp/24) / (3/4)
      (kp/24) (8k/p) = (3/4)
      8k² / 24 = 3 / 4
      k² = 9/4
      k = 3/2

      “two dozen carrots make three [lbs]”

      (carrots per lb) = 24/3 = 8
      (weight per carrot) = 1/8
      (price of 1lb carrots) = 8 × (3/2) = 12

      This leaves us with:

      “Seven times the number of peas that weigh the same as the number of onions that cost as much as 2 lb. of grapes, is less by the number of beans that weigh 12 lb. and the number of pence in the price of 16 lb. of carrots, than the number of beans that weigh as many pounds as the difference between the number of grapes that weigh the same as half the number of onions as there are carrots in a pound and a half, and the number of beans that cost as many pennies as there are grapes in the weight of a gross of peas.”

      We can break this down into:

      X = 7A + B + C

      where:

      A = “the number of peas that weigh the same as the number of onions that cost as much as 2 lb. of grapes”
      B = “the number of beans that weigh 12 lb.”
      C = “the number of pence in the price of 16 lb. of carrots”
      X = “the number of beans that weigh as many pounds as the difference between the number of grapes that weigh the same as half the number of onions as there are carrots in a pound and a half, and the number of beans that cost as many pennies as there are grapes in the weight of a gross of peas.”

      A = (the number of peas that weigh (the weight of (the number of onions that cost (the price of 2lb of grapes))))
      A = (the number of peas that weigh (the weight of (the number of onions that cost 48 pence)))
      A = (the number of peas that weigh (the weight of 64 onions))
      A = (the number of peas that weigh 1536/p lbs)
      A = 1536

      B = (the number of beans that weigh 12lb)
      B = 12 × (p/6)
      B = 2p

      C = (the number of pence in the price of 16lb of carrots)
      C = 16 × 12
      C = 192

      X = (the number of beans that weigh as many pounds as (the difference between (the number of grapes that weigh the same as (half the number of onions as there are (carrots in a pound and a half))) and (the number of beans that cost as many pennies as there are (grapes in the weight of (a gross of peas))))

      X = (the number of beans that weigh as many pounds as (the difference between Y and Z))
      Y = (the number of grapes that weigh the same as (half the number of onions as there are (carrots in a pound and a half)))
      Z = (the number of beans that cost as many pennies as there are (grapes in the weight of (a gross of peas)))

      Y = (the number of grapes that weigh the same as (half the number of onions as (the number of carrots in a pound and a half)))
      Y = (the number of grapes that weigh the same as (half the number of onions as (12 carrots)))
      Y = (the number of grapes that weigh the same as (6 onions))
      Y = (the number of grapes that weigh 144/p lbs)
      Y = 4608/p

      Z = (the number of beans that cost as many pennies as there are (grapes in the weight of (a gross of peas)))
      Z = (the number of beans that cost as many pennies as there are (grapes in (the weight of 144 peas)))
      Z = (the number of beans that cost as many pennies as there are (grapes in 144/p lbs))
      Z = (the number of beans that cost 4608/p pennies)
      Z = 256

      There are probably more than 18 peas per lb, so:

      X = (the number of beans that weigh as many pounds as (256 − 4608/p))
      X = (256 − 4608/p) × (p/6)
      X = 128p/3 − 768

      Hence:

      X = 7A + B + C
      128p/3 − 768 = 7×1536 + 2p + 192
      128p/3 = 11712 + 2p
      p = 35136 / 122
      p = 288

      Solution: There are 240 beans in 5 lb.

      We can summarise the calculated information as:

      grapes: 32 grapes per lb; price per lb = 24d
      carrots: 8 carrots per lb; price per lb = 12d
      onions: 12 onions per lb; price per lb = 9d
      peas: 288 peas per lb; price per lb = ?
      beans: 48 beans per lb; price per lb = 3d

      Which means grapes and onions have the same unit price (3/4 d each).

      Like

  • Unknown's avatar

    Jim Randell 6:37 pm on 27 August 2021 Permalink | Reply
    Tags:   

    Teaser 3075: Prime cuts for dinner 

    From The Sunday Times, 29th August 2021 [link] [link]

    Tickets to the club dinner were sequentially numbered 1, 2, …, etc. and every ticket was sold. The number of guests for dinner was the highest common factor of three different two-figure numbers and the lowest common multiple of three different two-figure numbers. There were several dinner tables, each with the same number of seats, couples being seated with friends. The guests on each table added their ticket numbers together and obtained one of two prime numbers, both less than 150, but if I told you the larger prime number you would not be able to determine the other.

    What was the larger of the two prime numbers?

    [teaser3075]

     
    • Jim Randell's avatar

      Jim Randell 7:03 pm on 27 August 2021 Permalink | Reply

      This Python program looks for candidate solutions, but doesn’t show that the tickets can be assigned to tables in the required fashion. However there is only one possible answer, so this is not necessary. (However, it is possible to assign tickets in both cases – see [link]).

      It runs in 234ms.

      from enigma import (mgcd, mlcm, subsets, irange, intersect, divisors_pairs, tri, Primes, express, filter_unique, unpack, printf)
      
      # generate candidate solutions
      def generate():
      
        # primes less than 150
        primes = Primes(150)
      
        # look for gcds and lcms of 3 2-digit numbers
        (gcds, lcms) = (set(), set())
        for ns in subsets(irange(10, 99), size=3):
          gcds.add(mgcd(*ns))
          lcms.add(mlcm(*ns))
      
        # consider number of tickets, n
        for n in intersect([gcds, lcms]):
          # total sum of tickets
          t = tri(n)
          # consider k tables, with q people per table
          for (k, q) in divisors_pairs(n, every=1):
            if k < 3 or q < 3: continue
      
            # consider 2 primes, for the ticket sums
            for (p1, p2) in subsets(primes, size=2):
              # express the total using 2 of the primes
              for (q1, q2) in express(t, (p1, p2), min_q=1):
                if q1 + q2 == k:
                  yield (n, t, k, q, p1, p2, q1, q2)
      
      # look for candidate solutions, where p1 is ambiguous by p2
      ss = filter_unique(
        generate(),
        unpack(lambda n, t, k, q, p1, p2, q1, q2: p2),
        unpack(lambda n, t, k, q, p1, p2, q1, q2: p1),
      )
      
      for (n, t, k, q, p1, p2, q1, q2) in ss.non_unique:
        printf("n={n}: t={t}; {k} tables of {q} people; ({p1}, {p2}) -> ({q1}, {q2})")
      

      Solution: The larger of the two primes was 109.


      There are 30 tickets:

      gcd(30, 60, 90) = 30
      lcm(10, 15, 30) = 30

      And 30 is the only number that satisfies the conditions.

      So the sum of the ticket numbers is T(30) = 465.

      And the tickets can be arranged into 5 tables of 6 people, to give one of two prime sums for each table as follows:

      Table 1: 2, 3, 4, 5, 7, 8; sum = 29
      Table 2: 1, 9, 13, 27, 29, 30; sum 109
      Table 3: 6, 10, 14, 25, 26, 28; sum = 109
      Table 4: 11, 12, 17, 22, 23, 24; sum = 109
      Table 5: 15, 16, 18, 19, 20, 21; sum = 109

      Table 1: 1, 3, 7, 25, 26, 27; sum = 89
      Table 2: 5, 6, 9, 22, 23, 24; sum 89
      Table 3: 8, 10, 11, 19, 20, 21; sum = 89
      Table 4: 12, 13, 14, 15, 17, 18; sum = 89
      Table 5: 2, 4, 16, 28, 29, 30; sum = 109

      In each case the larger prime is 109.

      And there are many ways to achieve the same sums.

      Like

    • GeoffR's avatar

      GeoffR 9:30 am on 31 August 2021 Permalink | Reply

      I programmed this teaser in two stages, first re-using the few lines of Jim’s code to find the intersection of GCD/LCM values to give the number of guests.

      The next stage was to examine possible table arrangements and arrangements of two prime numbers to make up the sum of tickets.

      I also tested different prime number multipliers e.g 4/1 and 3/2 for the 5 table arrangement and 5/1 and 4/2 for the 6 table arrangement.

      
      from itertools import permutations
      from enigma import mgcd, mlcm, subsets, irange, Primes
       
      # primes less than 150
      primes = Primes(150)
       
      # look for 3 No. 2-digit numbers for gcd and lcm values
      gcds, lcms = set(), set()
      for ns in subsets(irange(10, 99), size=3):
        gcds.add(mgcd(*ns))
        lcms.add(mlcm(*ns))
      
      # Numbers of Guests (N)
      N = list(gcds.intersection(lcms))[0]
      print('No. of guests = ', N)   # N = 30
      
      # Ticket sum of numbers for N guests
      T = N * (N + 1)//2
      print('Sum of all ticket numbers = ', T) 
      
      # 30 people - possible table arrangements:
      # - 5 tables of 6 people or 6 tables of 5 people - two possibilities
      # - 2 tables of 15 people or 15 tables of 2 people - not viable
      # - 3 tables of 10 people or 10 tables of 3 people - not viable
      
      # try 5 tables of 6 people, using 2 primes to make sum of tickets
      # results were found for prime multipliers of 4 and 1 
      # no results from 2 and 3 prime multipliers were found
      print('Two possible prime values:')
      for p1, p2 in permutations(primes, 2):
          if 4 * p1 + p2 == T:
              print( (max(p1, p2), min(p1, p2)),  end = "  ")
      
      # (149, 79)  (109, 89)  (101, 61)  (103, 53)  (107, 37)  (109, 29)  (113, 13)
      # There is only maximum prime i.e. 109, where the other prime could not be known.
      # For maximum primes of 149, 101, 103, 107 and 113, the second prime is known.
      
      # try 6 tables of 5 people, using 2 primes make sum of tickets
      for p3, p4 in permutations(primes,2):
          if 5 * p3 + p4 == T:
              print(max(p3, p4), min(p3, p4))        
      # No solution
      
      # No. of guests =  30
      # Sum of all ticket numbers =  465
      # Two possible prime values:
      # (149, 79)  (109, 89)  (101, 61)  (103, 53)  (107, 37)  (109, 29)  (113, 13)
      
      

      This section of code find the two sets of three two digit numbers used by the setter for the HCF/LCM values in this teaser.

      There were 33 numbers in the GCD set of numbers and 25,608 numbers in the LCM set of numbers, with a minimum value of 30 and a maximum value of 941,094. The setter used the minimum LCM value.

      
      # The number of guests for dinner was the highest common
      # factor of three different two-figure numbers and the 
      # lowest common multiple of three different two-figure numbers.
      
      # Q. What were these two sets of three digit numbers?
      
      from itertools import combinations
      
      from math import gcd, lcm
      for x in combinations(range(10, 100), 3):
        a, b, c = x
        if gcd(a, b, c) == 30:
          print(f"Three 2-digit values for HCF = {a}, {b}, {c}")
      
      for x in combinations(range(10, 100), 3):
        d, e, f = x
        if lcm(d, e, f) == 30:
          print(f"Three 2-digit values for LCM = {d}, {e}, {f}")
      
      # Three 2-digit values for HCF = 30, 60, 90
      # Three 2-digit values for LCM = 10, 15, 30
      # Therefore, a value of 30 was used for number of guests.
      
      

      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