Updates from December, 2020 Toggle Comment Threads | Keyboard Shortcuts

  • Jim Randell 8:37 am on 31 December 2020 Permalink | Reply
    Tags: ,   

    Teaser 1995: Pyramid selling 

    From The Sunday Times, 10th December 2000 [link]

    At the fruit stall in our local market the trader built a stack of oranges using the contents of some complete boxes, each containing the same number of oranges.

    He first laid out one box of oranges in a rectangle to form the base of a stack. He then added more oranges layer by layer from the contents of the other boxes. Each layer was a rectangle one orange shorter and narrower than the layer beneath it.

    The top layer should have consisted of a single row of oranges but the trader was one orange short of being able to complete the stack.

    How many oranges were there in each box?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    This completes the 72 puzzles from the 2002 Brainteasers book. In the New Year I will start posting puzzles from the 1982 book “The Sunday Times book of Brain Teasers (50 hard (very hard) master problems)”, compiled by Victor Bryant and Ronald Postill. It is a selection of Teaser puzzles originally published in The Sunday Times between January 1974 and December 1979.

    Happy New Year from S2T2!

    [teaser1995]

     
    • Jim Randell 8:38 am on 31 December 2020 Permalink | Reply

      See: Enigma 1086.

      This Python program runs in 43ms.

      Run: [ @repl.it ]

      from enigma import irange, inf, div, printf
      
      # calculate stacking numbers n <= m
      stack = lambda n, m: sum((n - i) * (m - i) for i in irange(n))
      # or: n * (n + 1) * (3 * m - n + 1) // 6
      
      # generate (n, m) pairs where 1 < n < m
      def pairs():
        for t in irange(5, inf):
          for n in irange(2, (t - 1) // 2):
            yield (n, t - n)
      
      # consider n, m values
      for (n, m) in pairs():
        # number of oranges in a box
        b = n * m
        # number of stacked oranges
        s = stack(n, m) - 1
        # number of boxes required
        k = div(s, b)
        if k is not None:
          printf("n={n} m={m} b={b} s={s} k={k}")
          break
      

      Solution: There were 72 oranges in each box.

      3 boxes were used, making a total of 216 oranges.

      The base layer was 6 × 12 layer, using 72 oranges (= 1 box).

      The remaining layers:

      5 × 11 = 55
      4 × 10 = 40
      3 × 9 = 27
      2 × 8 = 16
      1 × 6 = 6 (the final layer is 1 orange short)

      use 55+40+27+16+6 = 144 oranges (= 2 boxes)


      Manually:

      To complete a structure starting with a base that is n × m where n ≤ m, the number of oranges required is:

      S(n, m) = n(n + 1)(3m – n + 1)/6

      And if we use k boxes we have:

      n(n + 1)(3m – n + 1) / 6 = kmn + 1
      n(n + 1)(3m – n + 1) = 6kmn + 6

      n divides the LHS, so n must divide the RHS, hence n divides 6.

      So: n = 1, 2, 3, 6.

      If n = 6:

      m = 12 / (7 – 2k)

      so: m = 12, k = 3.

      If n = 3:

      m = 5 / (6 – 3k)

      doesn’t work.

      If n = 2:

      m = 2 / (3 – 2k)

      doesn’t work.

      If n = 1:

      m = 1 / (1 – k)

      doesn’t work.

      So: n = 6, m = 12, k = 3 is the only viable solution.

      Like

  • Jim Randell 8:27 am on 27 December 2020 Permalink | Reply
    Tags:   

    Teaser 1991: Numismathematics 

    From The Sunday Times, 12th November 2000 [link]

    I have three circular medallions that I keep in a rectangular box, as shown. The smallest (of radius 4cm) touches one side of the box, the middle-sized one (of radius 9cm) touches two sides of the blox, the largest touches three sides of the box, and each medallion touches both the others.

    What is the radius of the largest medallion?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1991]

     
    • Jim Randell 8:27 am on 27 December 2020 Permalink | Reply

      Here’s a manual solution:

      Labelling the centres of the medallions (smallest to largest) are A, B, C, and the corresponding radii are 4, 9, R.

      Then the horizontal displacement between B and C, h(B, C), is given by:

      h(B, C)² = (R + 9)² – (R – 9)²
      h(B, C)² = 36R
      h(B, C) = 6r, where: r = √R

      And the horizontal displacement between A and C, h(A, C), is given by:

      h(A, C)² = (R + 4)² – (R – 4)²
      h(A, C)² = 16R
      h(A, C) = 4r

      And the difference in these displacements is the horizontal displacement between A and B, h(A, B):

      h(A, B) = h(B, C) – h(A, C)
      h(A, B) = 2r
      h(A, B)² = 4r² = 4R

      The vertical displacement between A and B, v(A, B), is given by:

      v(A, B) = 2R – 13

      Then we have:

      h(A, B)² + v(A, B)² = 13²
      4R + (2R – 13)² = 13²
      4R + 4R² – 52R = 0
      4R(R – 12) = 0

      So R = 12.

      Solution: The radius of the largest medallion is 12cm.

      Like

  • Jim Randell 9:20 am on 15 December 2020 Permalink | Reply
    Tags:   

    Teaser 1967: Domino effect 

    From The Sunday Times, 28th May 2000 [link]

    I have placed a full set of 28 dominoes on an eight-by-seven grid, with some of the dominoes horizontal and some vertical. The array is shown above with numbers from 0 to 6 replacing the spots at each end of the dominoes.

    Fill in the outlines of the dominoes.

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1967]

     
    • Jim Randell 9:20 am on 15 December 2020 Permalink | Reply

      We can use the [[ DominoGrid() ]] solver from the enigma.py library to solve this puzzle.

      Run: [ @repl.it ]

      from enigma import DominoGrid
      
      DominoGrid(8, 7, [
        1, 6, 3, 3, 5, 6, 6, 0,
        2, 6, 2, 5, 4, 4, 3, 3,
        3, 6, 6, 5, 0, 4, 1, 3,
        2, 4, 5, 0, 0, 1, 1, 4,
        4, 4, 1, 1, 0, 2, 0, 6,
        0, 4, 3, 2, 0, 2, 2, 6,
        2, 1, 5, 5, 5, 5, 1, 3,
      ]).run()
      

      Solution: The completed grid is shown below:

      Like

    • Frits 2:00 pm on 19 December 2020 Permalink | Reply

      You have to press a key each time for finding new dominoes/stones.

      import os
      from collections import defaultdict
        
      # grid dimensions (rows, columns)
      (R, C) = (7 ,8)
       
      g = [
          1, 6, 3, 3, 5, 6, 6, 0,
          2, 6, 2, 5, 4, 4, 3, 3,
          3, 6, 6, 5, 0, 4, 1, 3,
          2, 4, 5, 0, 0, 1, 1, 4,
          4, 4, 1, 1, 0, 2, 0, 6,
          0, 4, 3, 2, 0, 2, 2, 6,
          2, 1, 5, 5, 5, 5, 1, 3,
          ]
      
      # return coordinates of index number <n> in list <g>
      coord = lambda n: (n // C, n % C)
      
      # return index number in list <g>
      gnum = lambda x, y: x * C + y
      
      # return set of stone numbers in list <li>, like {'13', '15', '12'}
      domnums = lambda li: set("".join(sorted(str(li[i][1]) + str(li[i+1][1]))) 
                           for i in range(0, len(li), 2))
      
      # build dictionary of coordinates per stone
      def collect_coords_per_stone()  : 
        # empty the dictionary
        for k in coords_per_stone: coords_per_stone[k] = []
        
        for (i, d) in enumerate(g):
          if d < 0: continue           # already placed?
          # (x, y) are the coordinates in the 2 dimensional matrix
          (x, y) = divmod(i, C) 
          js = list()
          # horizontally
          if y < C - 1 and not(g[i + 1] < 0): js.append(i + 1)
          # vertically
          if x < R - 1 and not(g[i + C] < 0): js.append(i + C)
          
          # try possible placements
          for j in js:
            stone = tuple(sorted((g[i], g[j])))
            if ((coord(i), coord(j))) in ex: continue
           
            if not stone in done:
              coords_per_stone[stone] += [[coord(i), coord(j)]]
           
      # coordinate <mand> has to use stone {mandstone> so remove other possibilities
      def remove_others(mand, mandstone, reason):  
        global stop
        mandval = g[gnum(mand[0], mand[1])]
        stones = stones_per_coord[mand]
        for i in range(0, len(stones), 2):
          otherval = stones[i+1][1] if stones[i][0] == mand else stones[i][1]
          stone = sorted([mandval, otherval])
          # stone at pos <mand> unequal to <mandstone> ?
          if stone != mandstone:
            otherco = stones[i+1][0] if stones[i][0] == mand else stones[i][0]
            tup = tuple(sorted([mand, otherco]))
            if tup in ex: continue
            print(f"stone at {yellow}{str(tup)[1:-1]}{endc} "
                  f"cannot be {stone} {reason}")
            ex[tup] = stone
            stop = 0
      
      # check for unique stones and for a coordinate occurring in all entries 
      def analyze_coords_per_stone():
        global step, stop
        for k, vs in sorted(coords_per_stone.items()): 
          k_stone = tuple(sorted(k))
          if len(vs) == 1:
            pair = vs[0]
            x = gnum(pair[0][0], pair[0][1])
            y = gnum(pair[1][0], pair[1][1])
            if list(k_stone) in done: continue
      
            print(f"----  place stone {green}{k_stone}{endc} at coordinates "
                  f"{yellow}{pair[0]}, {pair[1]}{endc} (stone occurs only once)")
            done.append(k_stone)
            g[x] = g[y] = step
            step -= 1
            stop = 0
          elif len(vs) != 0:
            # look for a coordinate occurring in all entries
            common = [x for x in vs[0] if all(x in y for y in vs[1:])]
            if len(common) != 1: continue
            reason = " (coordinate " + yellow + str(common)[1:-1] + \
                     endc + " has to use stone " + str(k) + ")"
            # so remove <common> from other combinations
            remove_others(common[0], sorted(k), reason)
      
      # build dictionary of stones per coordinate
      def collect_stones_per_coord():
        stones = defaultdict(list)
        # collect stones_per_coord per cell
        for (i, d) in enumerate(g):
          if d < 0: continue      # already placed?
          # (x, y) are the coordinates in the 2 dimensional matrix
          (x, y) = divmod(i, C) 
          
          js = list()
          # horizontally
          if y < C - 1 and not(g[i + 1] < 0): js.append(i + 1)
          if y > 0     and not(g[i - 1] < 0): js.append(i - 1)
          # vertically
          if x < R - 1 and not(g[i + C] < 0): js.append(i + C)
          if x > 0     and not(g[i - C] < 0): js.append(i - C)
          # try possible placements
          for j in js:
            t = [[coord(i), g[i]], [coord(j), g[j]]]
            t2 = tuple(sorted((coord(i), coord(j))))
            if t2 not in ex:
              stones[(x, y)] += t
              
        return stones
        
      # check if only one stone is possible for a coordinate  
      def one_stone_left():
        global step, stop
        for k, vs in sorted(stones_per_coord.items()): 
          if len(vs) != 2: continue  
      
          pair = vs
          x = gnum(pair[0][0][0], pair[0][0][1])
          y = gnum(pair[1][0][0], pair[1][0][1])
          if g[x] < 0 or g[y] < 0: return
          
          stone = sorted([g[x], g[y]])  
          if stone in done: continue
      
          print(f"----  place stone {green}{tuple(stone)}{endc} at coordinates "
                f"{yellow}{str(sorted((pair[0][0], pair[1][0])))[1:-1]}{endc}, "
                f"(only one possible stone left)")
          done.append(stone)
          g[x] = g[y] = step
          step -= 1
          stop = 0
      
      
      # if n fields only allow for n stones we have a group
      def look_for_groups():
        global stop
        for n in range(2, 5):
          for group in findGroups(n, n, list(stones_per_coord.items())):
            # skip for adjacent fields 
            a1 = any(x[0] == y[0] and abs(x[1] - y[1]) == 1 
                     for x in group[1] for y in group[1])
            if a1: continue
            a2 = any(x[1] == y[1] and abs(x[0] - y[0]) == 1 
                     for x in group[1] for y in group[1])
            if a2: continue
      
            for d in group[0]:
              # get stone number
              tup = tuple(int(x) for x in d)
              for c in coords_per_stone[tup]:
                # skip coordinates in our group 
                if len(set(c) & set(group[1])) > 0: continue
                
                tup2 = tuple(c)
                if g[gnum(tup2[0][0], tup2[0][1])] < 0: continue
                if g[gnum(tup2[1][0], tup2[1][1])] < 0: continue
                if tup2 in ex: continue
                print(f"stone at {yellow}{str(tup2)[1:-1]}{endc} cannot be "
                      f"{list(tup)}, group ({', '.join(group[0])}) "
                      f"exists at coordinates {yellow}{str(group[1])[1:-1]}{endc}")
                ex[tup2] = list(tup)
                stop = 0
          if stop == 0: return    # skip the bigger groups
      
      # pick <k> elements from list <li> so that all picked elements use <n> values
      def findGroups(n, k, li, s=set(), f=[]):
        if k == 0:
          yield (list(s), f)
        else:
          for i in range(len(li)):
            # get set of stone numbers
            vals = domnums(li[i][1])
            if len(s | vals) <= n:
              yield from findGroups(n, k - 1, li[i+1:], s | vals, f + [li[i][0]])  
      
      def print_matrix(mat, orig):
        # https://en.wikipedia.org/wiki/Box-drawing_character
        global g_save
        nw = '\u250c'    #   N
        ne = '\u2510'    # W   E
        ho = '\u2500'    #   S
        ve = '\u2502' 
        sw = '\u2514' 
        se = '\u2518'
        
        numlist = "".join(["    " + str(i) for i in range(C)]) + "   "
        print("\n\x1b[6;30;42m" + numlist + "\x1b[0m")
        for i in range(R):
          top = ""
          txt = ""
          bot = ""
          prt = 0
          for j in range(C):
            v = mat[i*C + j]
            # original value
            ov = orig[i*C + j] if orig[i*C + j] > -1 else " "
            
            cb = green if v != g_save[i*C + j] else ""
            ce = endc if v != g_save[i*C + j] else ""
            
            o = orientation(mat, i*C + j, v)
            
            if o == 'N': 
              top += nw+ho+ho+ho+ne
              txt += ve+cb+ " " + str(ov) + " " + ce+ve
              bot += ve+ "   " + ve
            if o == 'S': 
              top += ve+ "   " + ve
              txt += ve+cb+ " " + str(ov) + " " + ce+ve
              bot += sw+ho+ho+ho+se  
            if o == 'W':   # already handle East as well
              top += nw+ho+ho+ho+ho+ho+ho+ho+ho+ne
              txt += ve+cb+ " " + str(ov) + "    " + str(orig[i*C + j + 1]) + " " + ce+ve
              bot += sw+ho+ho+ho+ho+ho+ho+ho+ho+se
            if o == '':  
              top += "     "
              txt += "  " + str(ov) + "  " 
              bot += "     "
          print("\x1b[6;30;42m \x1b[0m " + top + "\x1b[6;30;42m \x1b[0m")  
          print("\x1b[6;30;42m" + str(i) + "\x1b[0m " + txt + "\x1b[6;30;42m" + str(i) + "\x1b[0m")
          print("\x1b[6;30;42m \x1b[0m " + bot + "\x1b[6;30;42m \x1b[0m")  
        print("\x1b[6;30;42m" + numlist + "\x1b[0m")  
          
        g_save = list(g)
          
      def orientation(mat, n, v):
        if v > -1: return ""
        
        # (x, y) are the coordinates in the 2 dimensional matrix
        (x, y) = divmod(n, C) 
        # horizontally
        if y < C - 1 and mat[n + 1] == v: return "W"
        if y > 0     and mat[n - 1] == v: return "E"
        # vertically
        if x < R - 1 and mat[n + C] == v: return "N"
        if x > 0     and mat[n - C] == v: return "S"
        
        return ""
                     
       
      coords_per_stone = defaultdict(list)
      
      stop = 0
      step = -1
      green = "\033[92m"    
      yellow = "\033[93m"   
      endc = "\033[0m"    # end color
       
       
      done = []
      ex = defaultdict(list)
      
      os.system('cls||clear')
      
      g_orig = list(g)
      g_save = list(g)
      
      for loop in range(222):
        stop = 1
        prevstep = step
        
        stones_per_coord = collect_stones_per_coord()
        
        one_stone_left()
        
        if step == prevstep:
          collect_coords_per_stone()    
          analyze_coords_per_stone()
      
        if step < prevstep:
          print_matrix(g, g_orig) 
          if all(y < 0 for y in g):
            exit(0)
          letter = input('Press any key: ')
          os.system('cls||clear')
          print()
          continue
      
        # collect again as some possible placements may have been ruled out
        stones_per_coord = collect_stones_per_coord()
        look_for_groups()
      
        if stop: break
      
      
      print("----------------------- NOT SOLVED -----------------------------")
      
      print_matrix(g, g_orig) 
      

      Like

  • Jim Randell 9:45 am on 13 December 2020 Permalink | Reply
    Tags:   

    Teaser 1984: Which whatsit is which? 

    From The Sunday Times, 24th September 2000 [link]

    I have received three boxes of whatsits. They all look alike but those in one of the boxes weigh 6 grams each, those in another box all weigh 7 grams each, and all those in the remaining box weigh 8 grams each.

    I do not know which box is which but I have some scales which enable me to weigh accurately anything up to 30 grams. I with to use the scales to determine which whatsit is which.

    How can I do this with just one weighing?

    The text of this puzzle is taken from the book Brainteasers (2002, edited by Victor Bryant), the wording differs only slightly from the puzzle originally published in the newspaper.

    The following note was added to the puzzle in the book:

    When this Teaser appeared in The Sunday Times, instead of saying “some scales” it said “a balance”. This implied to some readers that you could place whatsits on either side of the balance — which opens up all sorts of alternative approaches which you might like to think about.

    There are now 400 Teaser puzzles available on the site.

    [teaser1984]

     
    • Jim Randell 9:45 am on 13 December 2020 Permalink | Reply

      Evidently we need to find some selection from the three types of whatsit, such that from the value of their combined weight we can determine the weights of the individual types.

      If the scale did not have such a low maximum weight we could weigh 100 type A whatsits, 10 type B whatsits, and 1 type C whatsit, to get a reading of ABC, which would tell us immediately the weights of each type. (For example, if the total weight was 768g, we would know A = 7g, B = 6g, C = 8g).

      We first note that if we can determine the weights for two of the types, then the third types weight must be the remaining value. (Equivalently if the selection (a, b, c) determines the weights, so does selection (0, b – a, c – a) where a is the smallest count in the selection).

      So we need to find a pair of small numbers (as 6 or more of the lightest whatsits will give an error on the scales), that give a different outcome for each possible choice of whatsits.

      This Python program runs in 45ms.

      Run: [ @repl.it ]

      from enigma import group, subsets, printf
      
      # display for a weight of x
      def weigh(x):
        return ("??" if x > 30 else str(x))
      
      # check the qunatities <ns> of weights <ws> are viable
      def check(ns, ws):
        # collect total weight
        d = group(subsets(ws, size=len, select="P"), by=(lambda ws: weigh(sum(n * w for (n, w) in zip(ns, ws)))))
        # is this a viable set?
        return (d if all(len(v) < 2 for v in d.values()) else None)
      
      # consider (a, b) pairs, where a + b < 6
      for (a, b) in subsets((1, 2, 3, 4), size=2):
        if a + b > 5: continue
        ns = (0, a, b)
        d = check(ns, (6, 7, 8))
        if d:
          # output solution
          printf("ns = {ns}")
          for k in sorted(d.keys()):
            printf("  {k:2s} -> {v}", v=d[k])
          printf()
      

      Solution: You should choose one whatsit from one of the boxes, and three whatsits from another box.

      The combined weight indicates what the weight of each type of whatsit is:

      25g → (0 = 8g, 1 = 7g, 3 = 6g)
      26g → (0 = 7g, 1 = 8g, 3 = 6g)
      27g → (0 = 8g, 1 = 6g, 3 = 7g)
      29g → (0 = 6g, 1 = 8g, 3 = 7g)
      30g → (0 = 7g, 1 = 6g, 3 = 8g)
      <error> → (0 = 6g, 1 = 7g, 3 = 8g) (actual weight = 31g)

      Like

    • Frits 12:01 pm on 13 December 2020 Permalink | Reply

      from enigma import SubstitutedExpression, group
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [# pick not more than 5 whatsits from one or two boxes
         "min(X, Y, Z) == 0 and sum([X, Y, Z]) < 6",
         
         # don't allow all whatits to be picked from one box only
         "max(X, Y, Z) != sum([X, Y, Z])"
        ],
        answer="(X, Y, Z), X*6 + Y*7 + Z*8",
        d2i=dict([(k, "XYZ") for k in range(5,10)]),
        distinct="",
        verbose=0)   
      
      # collect weighing results
      res = [y for _, y in p.solve()]
      
      # group items based on X,Y,Z combinations    
      grp = group(res, by=(lambda a: "".join(str(x) for x in sorted(a[0]))))
      
      for g, vs in grp.items(): 
        # there should be 6 different weighing results in order to determine 
        # which whatsit is which 
        if (len(vs)) != 6: continue
        
        # group data by sum of weights
        grp2 = group(vs, by=(lambda a: a[1]))
        
        morethan30 = [1 for x in grp2.items() if x[0] > 30]
        if (len(morethan30) > 1): continue 
        
        ambiguous = [1 for x in grp2.items() if len(x[1]) > 1]
        if len(ambiguous) > 0: continue
        
        print("Solution", ", ".join(g), "\nweighings: ", vs)
      

      Like

      • Frits 12:17 pm on 13 December 2020 Permalink | Reply

        I could have used “[X, Y, Z].count(0) == 1” but somehow count() is below my radar (probably because I have read that count() is/was quite expensive).

        Like

  • Jim Randell 9:23 am on 10 December 2020 Permalink | Reply
    Tags:   

    Teaser 1966: Square of primes 

    From The Sunday Times, 21st May 2000 [link]

    If you place a digit in each of the eight unshaded boxes, with no zeros in the corners, then you can read off various three-figure numbers along the sides of the square, four in a clockwise direction and four anticlockwise.

    Place eight different digits in those boxes with the largest of the eight in the top right-hand corner so that, of the eight resulting three-figure numbers, seven are prime and the other (an anticlockwise one) is a square.

    Fill in the grid.

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1966]

     
    • Jim Randell 9:24 am on 10 December 2020 Permalink | Reply

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

      The following run file executes in 129ms.

      Run: [ @repl.it ]

      #!/usr/bin/env python -m enigma -r
      
      #  A B C
      #  D   E
      #  F G H
      
      SubstitutedExpression
      
      # no zeros in the corners
      --invalid="0,ACFH"
      
      # C is the largest number
      "C > max(A, B, D, E, F, G, H)"
      
      # all the clockwise numbers are prime
      "is_prime(ABC)"
      "is_prime(CEH)"
      "is_prime(HGF)"
      "is_prime(FDA)"
      
      # three of the anticlockwise numbers are prime
      "icount((ADF, FGH, HEC, CBA), is_prime) = 3"
      
      # and the other one is square
      "icount((ADF, FGH, HEC, CBA), is_square) = 1"
      
      # answer grid
      --answer="((A, B, C), (D, E), (F, G, H))"
      

      Solution: The completed grid looks like this:

      Like

      • Frits 2:42 pm on 10 December 2020 Permalink | Reply

        @Jim,

        A further optimization would be to limit A, F, and H to {1, 3, 5, 7} and C to {7, 9}.

        Like

    • Frits 10:54 am on 10 December 2020 Permalink | Reply

      A solution with miniZinc.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      %  A B C
      %  D   E
      %  F G H
      
      % no zeros in the corners 
      var 1..9:A; var 0..9:B; var 1..9:C; 
      var 0..9:D; var 0..9:E; 
      var 1..9:F; var 0..9:G; var 1..9:H;
      
      % 3-digit numbers clockwise
      var 100..999: ABC = 100*A + 10*B + C;
      var 100..999: CEH = 100*C + 10*E + H;
      var 100..999: HGF = 100*H + 10*G + F;
      var 100..999: FDA = 100*F + 10*D + A;
      
      % 3-digit numbers anticlockwise
      var 100..999: CBA = 100*C + 10*B + A;
      var 100..999: HEC = 100*H + 10*E + C;
      var 100..999: FGH = 100*F + 10*G + H;
      var 100..999: ADF = 100*A + 10*D + F;
      
      constraint all_different([A, B, C, D, E, F, G, H]);
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
       
      predicate is_square(var int: y) = exists(z in 1..ceil(sqrt(int2float(ub(y)))))
       (z*z = y );
       
      % C is the largest number
      constraint C > max([A, B, D, E, F, G, H]);
      
      % all the clockwise numbers are prime
      constraint is_prime(ABC) /\ is_prime(CEH)
      /\ is_prime(HGF) /\ is_prime(FDA);
      
      % three of the anticlockwise numbers are prime
      constraint (is_prime(CBA) /\ is_prime(HEC)/\ is_prime(FGH))
      \/ (is_prime(CBA) /\ is_prime(HEC)/\ is_prime(ADF)) 
      \/ (is_prime(CBA) /\ is_prime(FGH) /\ is_prime(ADF))
      \/ (is_prime(HEC) /\ is_prime(FGH) /\ is_prime(ADF));
      
      % and the other one is square
      constraint is_square(CBA) \/ is_square(HEC)
      \/ is_square(FGH) \/ is_square(ADF);
       
      solve satisfy;
       
      output [ show(ABC) ++ "\n" ++ show(D) ++ " " ++ show(E) ++ "\n" ++ show(FGH) ];
      

      Like

    • GeoffR 9:44 am on 11 December 2020 Permalink | Reply

      A solution in Python. formatted to PEP8:

      
      import time
      start = time.time()
      
      from itertools import permutations
      from enigma import is_prime
      
      digits = set(range(10))
      
      def is_sq(n):
        a = 2
        while a * a < n:
          a += 1
        return a * a == n
      
      for P1 in permutations(digits, 4):
        A, F, H, C = P1
        if C not in (7, 9):
          continue
        if 0 in (A, F, H, C):
          continue
        Q1 = digits.difference(P1)
        for P2 in permutations(Q1, 4):
          B, D, E, G = P2
          if C > max(A, B, D, E, F, G, H):
      
            # Four clockwise primes
            ABC, CEH = 100 * A + 10 * B + C, 100 * C + 10 * E + H
            HGF, FDA = 100 * H + 10 * G + F, 100 * F + 10 * D + A
            if sum([is_prime(ABC), is_prime(CEH),
                    is_prime(HGF), is_prime(FDA)]) == 4:
              ADF, FGH = 100 * A + 10 * D + F, 100 * F + 10 * G + H
              HEC, CBA = 100 * H + 10 * E + C, 100 * C + 10 * B + A
      
              # Three anti-clockwise primes
              if sum([is_prime(ADF), is_prime(FGH),
                      is_prime(HEC), is_prime(CBA)]) == 3:
      
                # One anti-clockwise square
                if sum([is_sq(ADF), is_sq(FGH),
                        is_sq(HEC), is_sq(CBA)]) == 1:
                  print(f"{A}{B}{C}")
                  print(f"{D} {E}")
                  print(f"{F}{G}{H}")
                  print(time.time() - start) # 0.515 sec
      
      # 389
      # 6 0
      # 157
      
      
      

      A solution in MiniZinc, first part similar to previous MiniZinc solution, the last part using a different approach to find the seven primes and the one square number:

      
      % A Solution in MiniZinc
      include "globals.mzn";
      %  A B C
      %  D   E
      %  F G H
       
      var 0..9:A; var 0..9:B; var 0..9:C; 
      var 0..9:D; var 0..9:E; var 0..9:F; 
      var 0..9:G; var 0..9:H;
      
      constraint A > 0 /\ C > 0 /\ H > 0 /\ F > 0;
      constraint all_different([A, B, C, D, E, F, G, H]);
       
      % Four 3-digit numbers clockwise
      var 100..999: ABC = 100*A + 10*B + C;
      var 100..999: CEH = 100*C + 10*E + H;
      var 100..999: HGF = 100*H + 10*G + F;
      var 100..999: FDA = 100*F + 10*D + A;
       
      % Four 3-digit numbers anti-clockwise
      var 100..999: CBA = 100*C + 10*B + A;
      var 100..999: HEC = 100*H + 10*E + C;
      var 100..999: FGH = 100*F + 10*G + H;
      var 100..999: ADF = 100*A + 10*D + F;
       
      set of int: sq3 = {n*n | n in 10..31}; 
       
      % Hakank's prime predicate
      predicate is_prime(var int: x) =
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
       
      % C is the largest number
      constraint C > max([A, B, D, E, F, G, H]);
      
      % four clockwise prime numbers
      constraint sum([is_prime(ABC), is_prime(CEH), is_prime(HGF),
       is_prime(FDA)]) == 4;
       
      % three anti-clockwise prime numbers
      constraint sum([is_prime(CBA), is_prime(HEC), is_prime(FGH),
      is_prime(ADF)]) == 3;
      
      % one anti-clockwise square
      constraint sum([CBA in sq3, HEC in sq3, FGH in sq3,
      ADF in sq3]) == 1;
      
      solve satisfy;
        
      output ["Completed Grid: " ++ "\n"  ++ 
      show(ABC) ++ "\n" ++ 
      show(D) ++ " " ++ show(E) ++ "\n" ++ show(FGH) ];
      
      % Completed Grid: 
      % 389
      % 6 0
      % 157
      % ----------
      % ==========
      
      
      
      

      Like

    • Frits 2:05 pm on 12 December 2020 Permalink | Reply

      Further analysis leads to C = 9 and square 361.

      from enigma import SubstitutedExpression
      
      #  A B C
      #  D   E
      #  F G H
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # C = 9
        # All corner numbers have to be odd and can't be number 5"
        # (otherwise somewhere a number xx5 is not prime and has to be square.
        #  xx5 is either 225 or 625 but their 1st digit is not odd)
         
        # all the clockwise numbers are prime
        "is_prime(10*AB + 9)",  # ABC
        "is_prime(900 + EH)",   # CEH
        "is_prime(HGF)",
        "is_prime(FDA)",
         
        # three of the anticlockwise numbers are prime
        "icount((ADF, FGH, 10*HE + 9, 900 + BA), is_prime) = 3",
         
        # and the other one is square
        "icount((ADF, FGH, 10*HE + 9, 900 + BA), is_square) = 1",
       
        ],
        answer="((A, B, 9), (D, E), (F, G, H))",
        digits=range(0, 9),
        d2i=dict([(k, "AFH") for k in {0, 2, 4, 5, 6, 8}] +
                 [(k, "BEDG") for k in {1, 3, 7}]),
        distinct="ABDEFGH",
        verbose=0)   
      
      # Print answers 
      for (_, ans) in p.solve():
          print(f"{ans}")
      
      # ((3, 8, 9), (6, 0), (1, 5, 7))
      

      Only one square is possible:

      from enigma import is_prime
      
      alldiff = lambda seq: len(seq) == len(set(seq))
      
      sqs = []
      # check 3-digit squares
      for i in range(11, 32):
        i2 = i * i
        # digits in square must all be different 
        # and number must start/end with an odd digit
        if not(alldiff(str(i2)) and i2 % 2 == 1 and (i2 // 100) % 2 == 1):
          continue
        # reverse square has to be prime
        if not(is_prime(int(str(i2)[::-1]))): continue
        sqs.append(i2)
      
      print("possible squares:")  
      for sq in sqs: 
        print(sq)
      
      # possible squares:
      # 361
      

      Like

  • Jim Randell 11:53 am on 3 December 2020 Permalink | Reply
    Tags: ,   

    Teaser 1956: Moving the goalpost 

    From The Sunday Times, 12th March 2000 [link]

    We have a large rectangular field with a wall around its perimeter and we wanted one corner of the field fenced off. We placed a post in the field and asked the workment to make a straight fence that touched the post and formed a triangle with parts of two sides of the perimeter wall. They were to do this in such a way that the area of the triangle was as small as possible. They worked out the length of fence required (less than 60 metres) and went off to make it.

    Meanwhile, some lads played football in the field and moved the post four metres further from one side of the field and two metres closer to another.

    Luckily when the men returned with the fence it was still the right length to satisfy all the earlier requirements. When they had finished erecting it, the triangle formed had each of its sides equal to a whole number of metres.

    How long was the fence?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1956]

     
    • Jim Randell 11:54 am on 3 December 2020 Permalink | Reply

      I thought the wording in this puzzle was a bit confusing. Moving the post 4 metres further from one side of the field is necessarily moving it 4 meters closer to another side of the field. I think we are to suppose the sides of the field are those that are used in forming the perimeter of the triangle, but in my code I considered all 8 potential positions.

      If the post is at (a, b), then we can show that the minimal area triangle (made with the x– and y-axes) is achieved when the fence runs from (2a, 0) to (0, 2b). So the post is at the mid-point of the fence.

      The final triangle is a Pythagorean triple with hypotenuse z, less than 60, and, if the hypotenuse passes through the point (a′, b′), then the other sides are, 2a′ and 2b′.

      So we need to look for points (a, b), where a and a′ differ by 2 or 4, and b and b′ differ by 4 or 2, such that (2a)² + (2b)² = z².

      This Python program runs in 46ms.

      Run: [ @repl.it ]

      from itertools import product
      from enigma import pythagorean_triples, Rational, printf
      
      # choose a rational implementation
      F = Rational()
      
      # consider pythagorean triples for the final triangle
      for (x, y, z) in pythagorean_triples(59):
        # and the position of the post
        (a1, b1) = (F(x, 2), F(y, 2))
        # consider the original position of the post
        for ((dx, dy), mx, my) in product([(2, 4), (4, 2)], (1, -1), (1, -1)):
          (a, b) = (a1 + mx * dx, b1 + my * dy)
          if a > 0 and b > 0 and 4 * (a * a + b * b) == z * z:
            printf("({x}, {y}, {z}) -> (a1, b1) = ({a1}, {b1}), (a, b) = ({a}, {b})")
      

      Solution: The fence is 25m long.

      The triangles are a (7, 24, 25) and a (15, 20, 25) triangle.

      The program finds 2 solutions as we don’t know which is the starting position and which is the final position:

      However, if the post is moved 2m closer to one of the axes and 4m further from the other axis, then blue must be the starting position and red the final position.

      Like

    • John Crabtree 3:30 pm on 4 December 2020 Permalink | Reply

      As shown above, the post is at the midpoint of the fence.
      Let the initial sides be A and B, and the new sides be A + 8 and B – 4.
      One can show that B = 2A + 10, and if the hypotenuse = B + n then A = 2n + sqrt(5n^2 + 20n),
      n = 1 gives A = 7, ie (7, 24, 25) and (15, 20, 25)
      n = 5 gives A = 25, ie (25, 60, 65) and (33, 56, 65)
      This explains the limit on the length of the fence being less than 60 metres
      BTW, n = 16 gives (72, 154, 170) and (80, 150, 170)

      Like

  • Jim Randell 9:27 am on 29 November 2020 Permalink | Reply
    Tags:   

    Teaser 1958: Pent up 

    From The Sunday Times, 26th March 2000 [link]

    The schoolchildren run around in a walled regular pentagonal playground, with sides of 20 metres and with an orange spot painted at its centre. When the whistle blows each child has to run from wherever they are to touch each of the five walls, returning each time to their starting point, and finishing back at the same point.

    Brian is clever but lazy and notices that he can minimize the distance he has to run provided that his starting point is within a certain region. Therefore he has chalked the boundary of this region and he stays within in throughout playtime.

    (a) How many sides does Brian’s region have?
    (b) What is the shortest distance from the orange spot to Brian’s chalk line?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1958]

     
    • Jim Randell 9:28 am on 29 November 2020 Permalink | Reply

      I wrote a program to plot points with a path distance that is the same as that of the central point, and you get a plot like this:

      This makes sense, as the shortest distance from a point to the line representing any particular side is a line through the point, perpendicular to the side. So for each side, if we sweep it towards the opposite vertex, we cover a “house shaped” region of the pentagon, that excludes two “wings” on each side. (The regions that overhang the base).

      Sweeping each of the five sides towards the opposite vertex, the intersection of these five “house shaped” regions is the shaded decagon:

      A line from the centre of one of the sides of this decagon, through the orange spot to the centre of the opposite side, is a minimal diameter of the decagon, and we see that this distance is the same as the length of one of the sides of the original pentagon. (It corresponds to the side swept towards the opposite vertex, in the position when it passes through the exact centre of the pentagon).

      So the minimum distance from the centre to the perimeter of the decagon is half this distance.

      Solution: (a) The region has 10 sides; (b) The shortest distance is 10 metres.


      If the playground had been a regular 4-gon (square) or 3-gon (equilateral triangle), then every interior point would correspond to a minimal path.

      For a 6-gon (hexagon) we get a smaller 6-gon inside as the minimal area, and for a 7-gon the area is the shape of a 14-gon.

      In general for a k-gon we get an area in the shape of a smaller k-gon (when k is even) or 2k-gon (when k is odd).

      The areas get smaller and smaller as k increases. In the limit we have a circular playground with a single point at the centre corresponding to the minimal path.

      Like

    • Hugh Casement 1:23 pm on 29 November 2020 Permalink | Reply

      I think he chalks a line that runs (for example) from the north vertex to a point roughly two thirds of the way down the ESE wall, then to the midpoint of the S wall, to a point roughly a third of the way up the WSW wall, and back to the N vertex. He then stays on that line rather than within it. When the whistles blows he can run right round the line, clockwise or anticlockwise, touching two walls at the N vertex. His total distance is about 81.75 m.

      Like

      • Jim Randell 2:39 pm on 29 November 2020 Permalink | Reply

        @Hugh: Except that after touching each wall you have to return to your starting point before you can set off to touch the next wall.

        Like

        • Hugh Casement 3:17 pm on 29 November 2020 Permalink | Reply

          Oh, I misread it, thinking he just has to return to the start point at the end.
          Sorry to introduce a red herring.
          Anyway in my version I was wrong that the intermediate points are about a third of the way along the walls from the south: they’re precisely two thirds of the way.

          Like

          • Hugh Casement 4:50 pm on 29 November 2020 Permalink | Reply

            Oops! Got it wrong again. Precisely one third, six and 2/3 metres.
            I’m really not awake today. Feel free to delete all my posts on this subject.

            Like

    • Hugh Casement 2:31 pm on 29 November 2020 Permalink | Reply

      Put t = tan(72°) = sqrt{5 + 2 sqrt(5)}.
      Then if the midpoint of the south wall has coordinates (0,0) and the north vertex is at (0, 10t),
      the lower right wall is y = t(x – 10) and the lower left wall is y = t(10 – x).

      Then a bit of calculus — or some trial & error by program — shows that the intermediate points that he touches are at roughly (±12.060113, 6.340375) and the length of his path is about 81.75134 metres. If he starts at a point off the line (on either side) his overall path is longer.

      Of course his chalk line could start at any vertex and go via the midpoint of the opposite side. He could draw five such overlapping quadrilaterals, and stick to any of them, swapping between them at will, so long as when the whistle blows he is not confused as to which line he is on.

      Like

  • Jim Randell 9:08 am on 22 November 2020 Permalink | Reply
    Tags:   

    Teaser 1948: Square ladder 

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

     

    Your task is to place a non-zero digit in each box so that:

    • the number formed by reading across each row is a perfect square, with the one in the top row being odd;
    • if a digit is used in a row, then it is also used in the next row up;
    • only on one occasion does the same digit occur in two boxes with an edge in common.

    Fill in the grid.

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1948]

     
    • Jim Randell 9:08 am on 22 November 2020 Permalink | Reply

      This Python 3 program constructs “ladders” such that the digits used in each row are a superset of the digits used in the smaller row below.

      We then check the ladders produced to find ones that satisfy the remaining conditions.

      It runs in 47ms.

      Run: [ @repl.it ]

      from enigma import irange, group, grid_adjacency, join, printf
      
      # collect 1-5 digit squares
      squares = group((str(i * i) for i in irange(0, 316)), by=len, st=(lambda s: '0' not in s))
      
      # construct a ladder of squares
      def ladder(k, ss):
        n = len(ss)
        # are we done?
        if n == k:
          yield ss
        else:
          # add an (n+1)-digit square whose digits are a superset of the previous square
          ps = set(ss[-1])
          for s in squares[n + 1]:
            if ps.issubset(s):
              yield from ladder(k, ss + [s])
      
      # adjacency matrix for 5x5 grid
      adj = grid_adjacency(5, 5)
      
      # choose a starting 1 digit square
      for s in squares[1]:
        for ss in ladder(5, [s]):
          # the final square is odd
          if ss[-1][-1] not in '13579': continue
      
          # form the digits into a square array (linearly indexed)
          g = join(s.ljust(5) for s in ss)
      
          # count the number of edges between 2 identical digits
          n = sum(any(i < j and d == g[j] for j in adj[i]) for (i, d) in enumerate(g) if d != ' ')
          if n != 1: continue
      
          # output solution
          for s in ss[::-1]:
            printf("{s}", s=join(s, sep=" "))
          printf()
      

      Solution: The completed grid is:

      The squares are: (95481, 5184, 841, 81, 1) = (309, 72, 29, 9, 1)².

      Like

    • Frits 3:49 pm on 22 November 2020 Permalink | Reply

      I didn’t use recursion as depth is limited.

      from itertools import permutations as perm
      from enigma import is_square
      
      to_num = lambda li: int("".join(li))
      
      # check if new value is correct
      def check(n, new, old):
        # as 5th row must be a square
        if not any(j in new for j in ('1', '4', '9')): 
          return False
        # check how many digits are same in same position 
        same[n] = sum(a == b for (a, b) in zip(tuple(old), new))
        if sum(same[:n+1]) > 1: 
          return False
        # new must be a square number
        if not is_square(to_num(new)): 
          return False   
        return True
       
      # generate 5 digit squares
      s5 = [i2 for i in range(101, 317) if '0' not in (i2 := str(i * i)) and 
                                           i2[-1] in ('1', '3', '5', '7', '9') and
                                           any(j in i2 for j in ('1', '4', '9'))] 
                 
      same = [0] * 4 
      for p5 in s5: 
        for p4 in perm(p5, 4):
          if not check(0, p4, p5): continue
          for p3 in perm(p4, 3):
            if not check(1, p3, p4): continue
            for p2 in perm(p3, 2):
              if not check(2, p2, p3): continue
              for p1 in perm(p2, 1):
                if not check(3, p1, p2): continue
                if sum(same) != 1: continue
                print(f"{p5}\n{to_num(p4)}\n{to_num(p3)}\n"
                      f"{to_num(p2)}\n{to_num(p1)}")
                
      # 95481
      # 5184
      # 841
      # 81
      # 1
      

      Like

      • Jim Randell 4:33 pm on 22 November 2020 Permalink | Reply

        I used a more literal interpretation of: “if a digit is used in a row, then it is also used in the next row up”.

        Which would allow (for example), 4761 to appear above 144.

        Like

        • Frits 4:46 pm on 22 November 2020 Permalink | Reply

          You are right.

          from enigma import SubstitutedExpression, is_square
          
          # ABCDE
          # FGHI
          # JKL
          # MN
          # O
          
          # the alphametic puzzle
          p = SubstitutedExpression(
            [
            "is_square(ABCDE)",
            "is_square(FGHI)",
            "is_square(JKL)",
            "is_square(MN)",
            "is_square(O)",
            
            # connect 2 rows
            "all(j in str(ABCDE) for j in str(FGHI))",
            "all(j in str(FGHI) for j in str(JKL))",
            "all(j in str(JKL) for j in str(MN))",
            "str(O) in str(MN)",
            
            # check how many digits are equal in same position
            "sum([A==F, B==G, C==H, D==I, F==J, G==K, H==L, J==M, K==N, M==O]) == 1",
            ],
            answer="ABCDE, FGHI, JKL, MN, O",
            digits=range(1, 10),
            # top row is odd
            d2i=dict([(k, "E") for k in {2, 4, 6, 8}]),
            distinct="",
            verbose=0)   
          
          # Print answers 
          for (_, ans) in p.solve():
            print(f"{ans}")
          
          # (95481, 5184, 841, 81, 1)
          

          Like

    • GeoffR 12:07 pm on 10 December 2020 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      %  Grid
      %  a b c d e
      %  f g h i
      %  j k l
      %  m n
      %  p
      
      var 0..9: a; var 0..9: b; var 0..9: c; var 0..9: d;
      var 0..9: e; var 0..9: f; var 0..9: g; var 0..9: h; 
      var 0..9: i; var 0..9: j; var 0..9: k; var 0..9: l; 
      var 0..9: m; var 0..9: n; var 0..9: p; 
      
      constraint a > 0 /\ f > 0 /\ j > 0 /\ m > 0 /\ p > 0;
      
      var 10..99:mn = 10*m + n;
      var 100..999:jkl = 100*j + 10*k + l;
      var 1000..9999:fghi = 1000*f + 100*g + 10*h + i;
      var 10000..99999: abcde = 10000*a + 1000*b + 100*c + 10*d + e;
      
      % sets of 2,3,4 and 5-digit squares
      set of int: sq2 = {n*n | n in 4..9};
      set of int: sq3 = {n*n | n in 10..31};  
      set of int: sq4 = {n*n | n in 32..99};  
      set of int: sq5 = {n*n | n in 100..316};
              
      % form five rows of required squares
      constraint p == 1 \/ p == 4 \/ p == 9;
      constraint mn in sq2 /\ jkl in sq3 /\ fghi in sq4;
      % square in the top row is odd
      constraint abcde in sq5 /\ abcde mod 2 == 1;
      
      % if a digit is used in a row, then it is also used
      % in the next row up - starting at bottom of ladder 
      constraint {p} subset {m, n};
      constraint {m, n} subset {j, k, l};
      constraint {j, k, l} subset {f, g, h, i};
      constraint {f, g, h, i} subset {a, b, c, d, e};
      
      % assume same digit occurs in two adjacent vertical boxes only
      constraint (sum ([a - f == 0, b - g == 0, c - h == 0, 
      d - i == 0, f - j == 0, g - k == 0, h - l == 0, 
      j - m == 0, k - n == 0, m - p == 0]) == 1)
      /\ % and check same digit does not occur in any two horizontal boxes
      (sum ([a - b == 0, b - c == 0, c - d == 0, d - e == 0,
      f - g == 0, g - h == 0, h - i == 0,
      j - k == 0, k - l == 0, m - n == 0]) == 0); 
      
      solve satisfy;
      
      output ["Grid: " ++ "\n" ++  show(abcde) ++ "\n" ++ show(fghi) ++ "\n" 
      ++ show(jkl) ++ "\n" ++ show(mn) ++ "\n" ++ show(p) ];
      % Grid: 
      % 95481
      % 5184
      % 841
      % 81
      % 1
      % time elapsed: 0.04 s
      % ----------
      % ==========
      % Finished in 477msec
      
      
      

      Like

  • Jim Randell 9:26 am on 15 November 2020 Permalink | Reply
    Tags:   

    Teaser 1946: Not the millennium bug 

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

    Do you remember all that fuss over the “Millennium bug”?

    On that New Year’s Day I typed a Teaser on my word processor. When I typed in 2000 it actually displayed and printed 1900. This is because whenever I type a whole number in figures the machine actually displays and prints only a percentage of it, choosing a random different whole number percentage each time.

    The first example was bad enough but the worrying this is that is has chosen even lower percentages since then, upsetting everything that I prepare with numbers in it. Luckily the percentage reductions have not cut any number by half or more yet.

    What percentage did the machine print on New Year’s Day?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1946]

     
    • Jim Randell 9:26 am on 15 November 2020 Permalink | Reply

      See also: Enigma 1419.

      We assume that both the original (prepared on 2000-01-01) puzzle, and the puzzle text presented above were created using the faulty program.

      So, on 2000-01-01 the setter typed a whole number, X, which was replaced by the value aX, where a is some whole number percentage less than 100% and greater than 50%.

      In relating the story for this puzzle, the setter has typed the values X and aX, and these have been replaced by values multiplied by b and c respectively, where b and c are different whole number percentages, less than a and greater than 50%.

      So, we have:

      bX = 2000
      c.aX = 1900

      This Python program runs in 44ms.

      Run: [ @repl.it ]

      from enigma import irange, div, printf
      
      # choose a percentage for b
      for b in irange(51, 98):
        X = div(2000 * 100, b)
        if X is None: continue
      
        # a > b
        for a in irange(b + 1, 99):
          aX = div(a * X, 100)
          if aX is None: continue
          c = div(1900 * 100, aX)
          if c is None or c == b or not(a > c > 50): continue
      
          # output solution
          printf("a={a} b={b} c={c} -> X={X} aX={aX}")
      

      Solution: On 2000-01-01 the machine used a value of 80%.

      So the setter was attempting to enter 3125, but instead the program printed 80% of this = 2500.

      In relating the story, the setter typed: “When I typed in 3125 it actually printed 2500”, but these values were replaced using percentages of 64% and 76%, to appear as: “When I typed in 2000 it actually printed 1900”, as in the puzzle text.

      Like

  • Jim Randell 11:19 am on 10 November 2020 Permalink | Reply
    Tags:   

    Teaser 1942: Eurosceptics 

    From The Sunday Times, 5th December 1999 [link]

    Ruritania is reluctant to adopt the euro as it has a sensible currency of its own. The mint issues the coins in four denominations, the value of each being proportional to its radius. The total value of the four, in euros, is 28.

    The four coins are available in a clever presentation pack. It consists of a triangular box of sides 13 cm, 14 cm and 15 cm. The largest coin just fits into the box, touching each of its sides, roughly as shown:

    Then there are three straight pieces of thin card inside the box. Each touches the large coin and is parallel to a side of the box. This creates three smaller triangles in the corners of the box. The three remaining coins just fit into the box, with one in each of these small triangles. Each coin touches all three sides of the triangle.

    Unfortunately I have lost the smallest coin from my presentation pack.

    What, in euros, is its value?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1942]

     
    • Jim Randell 11:20 am on 10 November 2020 Permalink | Reply

      See: [ @Wikipedia ] for more details on incircles and excircles.

      Suppose a, b, c, d are the radii of the four coins (from largest to smallest).

      The inradius of a triangle can be calculated as the area of the triangle divided by the semi-perimeter.

      So for the large triangle we have:

      a = A / S

      where:

      S = semi-perimeter = (13 + 14 + 15) / 2 = 21
      A = area = sqrt(21.8.7.6) = 84

      So:

      a = 84/21 = 4

      Then considering the triangle containing the next smallest coin (radius = b), this is a version of the large triangle scaled down by a factor of (say) q.

      So it has a semi-perimeter of 21q, and an area of 84q² so:

      b = 84q² / 21q = 4q
      q = b / 4

      But the largest coin is an excircle of this smaller triangle and so its radius is given by:

      a = b.21q / (21q – 13q)
      a = 21b / (21 – 13) = b(21/8)
      b = (8/21)a = 32/21

      Similarly, for coins in the other corners:

      c = (7/21)a = 4/3
      d = (6/21)a = 8/7

      Now, if the radii are multiplied by factor f to get the value in euros we have:

      (4 + 32/21 + 4/3 + 8/7)f = 28
      8f = 28
      f = 7/2

      So the coins are worth:

      f.a = 14 euros
      f.b = 5 + 1/3 euros
      f.c = 4 + 2/3 euros
      f.d = 4 euros

      Which do indeed give a total of 28 euros.

      So, we have worked out the radii and value of each of the four coins.

      Solution: The smallest coin is worth 4 euros.

      The four coins are:

      radius = 40.0 mm; value = 14.00 euro
      radius = 15.2 mm; value = 5.33 euro
      radius = 13.3 mm; value = 4.67 euro
      radius = 11.4 mm; value = 4.00 euro

      Here’s a program that performs the same calculations:

      Run: [ @repl.it ]

      from enigma import fdiv, sqrt, multiply, printf
      
      # total value of the coins
      T = 28
      
      # the sides of the large triangle
      sides = (13, 14, 15)
      
      # calculate the radius of the largest coin
      S = fdiv(sum(sides), 2)
      A = sqrt(S * multiply(S - x for x in sides))
      a = fdiv(A, S)
      
      # and the three coins in the corners
      (b, c, d) = (fdiv(a * (S - x), S) for x in sides)
      
      # multiplier f: radius -> value
      f = fdiv(T, a + b + c + d)
      
      # output the values of the coins
      printf("[S={S} A={A} f={f}]")
      for (r, n) in zip((a, b, c, d), "abcd"):
        printf("{n}: {r:.1f} mm -> {v:.2f} euro", r= r * 10, v=r * f)
      

      Like

  • Jim Randell 10:27 am on 8 November 2020 Permalink | Reply
    Tags:   

    Teaser 1935: Turner painting 

    From The Sunday Times, 17th October 1999 [link]

    “Yet more storms” is a gigantic painting in the State Gallery. It is currently on the wall of the 27-foot-wide “Modern masters” corridor, but the curator feels that it would look better on the 64-foot-wide “Britain’s impressionists” corridor, which meets the “Modern masters” one at right angles.

    So he instructs his staff to slide the painting around the corner without tilting it. His staff manage to turn the painting as requested, but had it been any wider it would not have fitted around the corner.

    How wide is the painting?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1935]

     
    • Jim Randell 10:27 am on 8 November 2020 Permalink | Reply

      We examined the general case of this problem in Enigma 34.

      And maximum width of the painting is given by:

      z = (a^(2/3) + b^(2/3))^(3/2)

      z = \left( a^{2/3} + b^{2/3} \right)^{3/2}

      In particular this means that if we have a Pythagorean triple (x, y, z) (so: x² + y² = z²), then the maximum width painting for corridors of width x³ and y³ is z³.

      In this case: a = 27 = 3³ and b = 64 = 4³, so z = 5³ = 125.

      Solution: The painting is 125 ft wide.

      Like

      • Jim Randell 10:20 pm on 11 November 2020 Permalink | Reply

        Or a numerical solution:

        from math import cos, sin, pi
        from enigma import fdiv, find_min, printf
        
        # width of the legs
        (a, b) = (27, 64)
        
        # length of rod
        z = lambda theta: fdiv(a, cos(theta)) + fdiv(b, sin(theta))
        
        # find the minimum length
        r = find_min(z, 0, 0.5 * pi)
        
        # output solution
        printf("width = {r.fv:g} ft")
        

        Like

    • Frits 4:13 pm on 10 November 2020 Permalink | Reply

      #    -----+-----------+
      #     27  .\          |
      #    -----+---\  W    |
      #           x |  \    |
      #             |y    \ |
      #             |.......+
      #             |       |
      #             |  64   |
      #
      # W =  Width painting
      #
      # W^2 = (x + 64)^2 + (y + 27)^2
      #
      # x / 27 = 64 / y --> xy = 27*64 --> y = 27*64/x
      #
      # W = ((x + 64)^2 + (27*64/x + 72)^2)^0.5
      #
      # W is minimal if W' = 0
      #
      # W' = 0.5(((x + 64)^2 + (27*64/x + 72)^2)^-0.5 * 
      #      (2 * (x + 64) + 2 * (27*64/x + 27)) * 
      #      -27*64/x^2  
      #
      # term 2, 3 = x + 64 + (27*64/x + 27)) * -27*64/x^2 
      #           = x + 64 - 27*27*64/x^2 - (27*64)^2/x^3
      #           = x^4 + 64 * x^3 - 27*27*64 * x - (27*64)^2
      #           = x^3 * (x + 64) - 27*27*64 * (x + 64)
      #           = (x^3 - 27*27*64) * (x + 64)
      #
      # W' = 0 if x = -64 or x = 36, disregard negative x
      #
      # x = 36 --> y = 48
      #
      # W^2 = (36 + 64)^2 + (48 + 27)^2 = 15625 --> W = 125
      

      Like

    • Frits 7:58 pm on 10 November 2020 Permalink | Reply

      from enigma import SubstitutedExpression
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
         # find the length of the shortest line segment from (UVW, 0) to (0, XYZ)
         # that passes through (27, 64).
         #
         # 64 / (UVW - 27) = (XYZ - 64) / 27
         
         "UVW*XYZ - 64*UVW == 27*XYZ",
         "UVW > 0",
         "XYX > 0",
        ],
        answer="((UVW)**2 + (XYZ)**2)**0.5, UVW, XYZ",
        d2i={},
        distinct="",
        accumulate=min,
        verbose=0)   
          
      # solve the puzzle
      r = p.run()
      
      ans = list(r.accumulate)
      print("answer =",ans[0])
      
      # answer =  125.0
      

      Like

  • Jim Randell 10:55 am on 3 November 2020 Permalink | Reply
    Tags:   

    Teaser 1929: Square trip 

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

    My car has an odometer, which measures the total miles travelled. It has a five-figure display (plus two decimal places). There is also a “trip” counter with a three-figure display.

    One Sunday morning, when the car was nearly new, the odometer showed a whole number which was a perfect square and I set the trip counter to zero. At the end of that day the odometer again showed a whole number that was a perfect square, and the trip counter showed an odd square.

    Some days later, the display on the odometer was four times the square which had been displayed on that Sunday evening, and once again both displays were squares.

    What were the displays on that last occasion?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1929]

     
    • Jim Randell 10:55 am on 3 November 2020 Permalink | Reply

      I supposed that the initial reading was less than 1000 miles, and the car travels less than 1000 miles on the particular Sunday.

      This Python program runs in 49ms.

      Run: [ @repl.it ]

      from enigma import irange, inf, is_square, printf
      
      def solve():
        # consider initial readings (when the car was nearly new)
        for a in irange(1, 31):
          r1 = a * a
      
          # at the end of the day
          for b in irange(a + 1, inf):
            r2 = b * b
            # the trip reading is an odd square
            t2 = r2 - r1
            if t2 > 1000: break
            if not(t2 % 2 == 1 and is_square(t2)): continue
      
            # some days later the odometer reads 4 times as far
            r3 = 4 * r2
            # and the trip reading is also a square
            t3 = (r3 - r1) % 1000
            if not is_square(t3): continue
      
            yield ((r1, 0), (r2, t2), (r3, t3))
      
      # find the first solution
      for ((r1, t1), (r2, t2), (r3, t3)) in solve():
        printf("{r1} + {t1}; {r2} + {t2}; {r3} + {t3}")
        break
      

      Solution: The displays were 2500, and 100.

      The trip was set to zero when the car had done 400 miles (= 20²), so it was still fairly new.

      Then after another 225 miles, the odometer would read 625 miles (= 25²), and the trip would read 225 (= 15²).

      The final reading was taken after another 1875 miles. The odometer reads 2500 miles (= 50²), and the trip reads the 3 least significant digits of 2100, which are 100 (= 10²).

      Like

      • Jim Randell 2:19 pm on 3 November 2020 Permalink | Reply

        As r1, r2, t2 are all squares, such that: r1 + t2 = r2 we can solve this puzzle using Pythagorean triples.

        Run: [ @repl.it ]

        from enigma import pythagorean_triples, is_square, printf
        
        # consider pythagorean triples
        for (x, y, z) in pythagorean_triples(100):
          # readings
          (r1, t1) = (y * y, 0)
          (r2, t2) = (z * z, x * x)
          if not(r1 < 1000 and t2 < 1000 and t2 % 2 == 1): continue
          r3 = 4 * r2
          t3 = (r3 - r1) % 1000
          if not is_square(t3): continue
          # output solution
          printf("{r1} + {t1}; {r2} + {t2}; {r3} + {t3} [{x}, {y}, {z}]")
        

        And it is probably not a great surprise that there is a (3, 4, 5) triangle hiding in the solution.

        Like

  • Jim Randell 12:17 pm on 29 October 2020 Permalink | Reply
    Tags:   

    Teaser 1928: Prime of life 

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

    Today my daughter was looking through her old scrapbook and came across a rhyme I had written about her when she was a little girl:

    Here’s a mathematical rhyme:
    Your age in years is a prime;
    Mine is too,
    And if you add the two
    The answer’s a square — how sublime!

    She was surprised to find that this is also all true today. Furthermore is will all be true again some years hence.

    How old are my daughter and I?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1928]

     
    • Jim Randell 12:18 pm on 29 October 2020 Permalink | Reply

      When rhyme was written the daughter’s age was some prime d, the father’s age some prime f, and (d + f) is a square number.

      Unless they were born on the same day at the same time one of them have their next birthday before the other.

      If it is the daughter, the ages progress like this:

      (d, f) → (d + 1, f) → (d + 1, f + 1) → (d + 2, f + 1) → (d + 2, f + 2) …

      And if the father has the next birthday, the ages progress like this:

      (d, f) → (d, f + 1) → (d + 1, f + 1) → (d + 1, f + 2) → (d + 2, f + 2) …

      So we just need to examine these sequences to look for two more occurrences of the ages being prime, and their sum being a square.

      This Python program runs in 44ms.

      Run: [ @repl.it ]

      from enigma import Primes, is_square, printf
      
      # primes for ages
      primes = Primes(150)
      
      # check for viable ages
      check = lambda a, b: a in primes and b in primes and is_square(a + b)
      
      # extend the list to length k, with birthdays a then b
      def extend(a, b, k=3):
        rs = [(a, b)]
        while a < 150 and b < 150:
          a += 1
          if check(a, b): rs.append((a, b))
          b += 1
          if check(a, b): rs.append((a, b))
          if not(len(rs) < k): return rs
      
      # consider the daughters age at the time of the rhyme
      for d in primes.irange(2, 16):
      
        # now consider possible ages for the father
        for f in primes.irange(d + 16, 50):
          # together they make a square
          if not is_square(d + f): continue
      
          # find solutions where daughters birthday is next
          rs = extend(d, f)
          if rs: printf("d -> f: {rs}")
      
          # find solutions where fathers birthday is next
          rs = extend(f, d)
          if rs: printf("f -> d: {rs}")
      

      Solution: Today the daughter is 7 and the father is 29.

      The three pairs of ages are:

      d = 2, f = 23: d + f = 25 = 5²
      d = 7, f = 29: d + f = 36 = 6²
      d = 61, f = 83: d + f = 144 = 12²

      So father’s birthday came first after the rhyme was written.

      Like

    • Frits 1:44 pm on 29 October 2020 Permalink | Reply

      from enigma import SubstitutedExpression, is_prime, is_square
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [# father (age CDE) is a lot older than daughter (age AB)
         "AB + 15 < CDE",
         "PQ > 0",
         "RS > 0",
         # daughter must have already been born PQ years ago (and prime)
         # "when she was a little girl"
         "0 < AB - PQ < 10",
         # maximum age father
         "CDE < 125",
         "CDE + RS < 125",
         
         # PQ years ago
         "is_prime(AB - PQ - w)",
         "is_prime(CDE - PQ - x)",
         "is_square(AB + CDE - 2 * PQ - w - x)",
         # now
         "is_prime(AB)",
         "is_prime(CDE)",
         "is_square(AB + CDE)",
         # RS years later
         "is_prime(AB + RS + y)",
         "is_prime(CDE + RS + z)",
         "is_square(AB + CDE + 2 * RS + y + z)",
         
         # uncertainty bits, not both of them may be true
         "x + w < 2",
         "y + z < 2",
        ],
        answer="AB, CDE, PQ, RS, w, x, y, z",
        symbols="ABCDEPQRSwxyz",
        verbose=0,
        d2i=dict([(k, "wxyz") for k in {2,3,4,5,6,7,8,9}]),
        distinct="",   # allow variables with same values
        #reorder=0,
      )
      
      # Print answer
      for (_, ans) in p.solve():
        print(f"ages daughter {ans[0] - ans[2] - ans[4]} {ans[0]} "
              f"{ans[0] + ans[3] + ans[6]}")
        print(f"ages father   {ans[1] - ans[2]  - ans[5]} "
              f"{ans[1]} {ans[1] + ans[3]  + ans[7]}")
        print(f"squares       {ans[0] + ans[1] - 2*ans[2]  - ans[4] - ans[5]} "
              f"{ans[0] + ans[1]} {ans[0] + ans[1] + 2*ans[3] + ans[6] + ans[7]}")
        
        
      # ages daughter 2 7 61
      # ages father   23 29 83
      # squares       25 36 144
      

      Like

    • Frits 4:16 pm on 29 October 2020 Permalink | Reply

      from enigma import printf
      
      # Prime numbers up to 124
      P =  [2, 3, 5, 7]
      P += [x for x in range(11, 100, 2) if all(x % p for p in P)]
      P += [x for x in range(101, 125, 2) if all(x % p for p in P)]
      
      # report two prime numbers (ascending) which sum to <n>
      def twoprimes(n, dif=0): 
        li = []
        i = 1
        p1 = 2 
        while(p1 < n - p1): 
          if n - p1 in P:
            # difference between primes is not exact
            if dif == 0 or (dif - 1 <= abs(n - 2 * p1) <= dif + 1): 
              li.append([p1, n - p1])
          p1 = P[i]
          i += 1
        return li  
      
      # square candidates for when daughter was a little girl   
      for i in range(4, 11):  # also allowing for people like B. Ecclestone
        for t1 in twoprimes(i * i): 
          # "when she was a little girl"
          if t1[0] > 9: continue
          dif1 = int(t1[1]) - int(t1[0])
          # square candidates for now
          for j in range(i + 1, 15):
            for t2 in twoprimes(j * j, dif1): 
              # square candidates for in te future
              for k in range(j + 1, 16):
                for t3 in twoprimes(k * k, dif1): 
                  printf("{t1[0]} + {t1[1]} = {su}", su = int(t1[0]) + int(t1[1]))
                  printf("{t2[0]} + {t2[1]} = {su}", su = int(t2[0]) + int(t2[1]))
                  printf("{t3[0]} + {t3[1]} = {su}", su = int(t3[0]) + int(t3[1]))
      

      Like

    • GeoffR 9:23 pm on 29 October 2020 Permalink | Reply

      % A Solution in MiniZinc     
      include "globals.mzn";
      
      % Three Daughters' ages
      % D1 = young girl, D2 = age now, D3 = future age
      var 1..10:D1; var 1..50:D2; var 1..80:D3;
      
      % Three corresponding Fathers ages
      var 13..40:F1; var 13..70:F2; var 13..105:F3;
      
      % All Fathers and Daughters ages are different
      constraint all_different ([F1, F2, F3, D1, D2, D3]);
      
      set of int: primes = {2, 3, 5, 7, 11, 13, 17,
      19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
      71, 73, 79, 83, 89, 97, 101, 103};
      
      set of int : squares = {16, 25, 36, 49, 64, 81,
       100, 121, 144, 169};
      
      % Age group constraints
      constraint F1 - D1 > 12 /\ D1 < 10 ;
      constraint D2 > D1 /\ D3 > D2;
      constraint F2 > F1 /\ F3 > F2;
      constraint F1 > D1 /\ F2 > D2 /\ F3 > D3;
      
      % 1st age differences between Father and Daughter
      constraint D2 - D1 = F2 - F1 + 1
      \/ D2 - D1 = F2 - F1 - 1;
      
      % Age difference is the same between the 2nd and 3rd age groups
      constraint F2 - D2 == F3 - D3;
      constraint F3 - F2 == D3 - D2;
      
      % All ages are prime numbers
      constraint F1 in primes /\ F2 in primes /\ F3 in primes;
      constraint D1 in primes /\ D2 in primes /\ D3 in primes;
      
      % All father and daughter group sums are squares
      constraint (F1 + D1) in squares /\ (F2 + D2) in squares 
      /\ (F3 + D3) in squares;
      
      solve satisfy;
      
      output[ "Father's ages in sequence are " ++ show(F1) ++
       ", " ++ show(F2) ++ ", " ++ show(F3)  
      ++"\nDaughters ages in sequence are " ++ show(D1) ++
       ", " ++ show(D2) ++  ", " ++ show(D3) ];
      
      % Father's ages in sequence are 23, 29, 83
      % Daughters ages in sequence are 2, 7, 61
      % ----------
      % ==========
      
      
      

      Like

      • Frits 10:25 am on 30 October 2020 Permalink | Reply

        I feel lines 11 and 12 are too restrictive. However, removing them gives the same result.

        Like

    • GeoffR 5:16 pm on 30 October 2020 Permalink | Reply

      @Frits:
      The constraint programming approach is to search for a state of the world in which a large number of constraints are satisfied at the same time. But we cannnot predict in advance which constraints are required for a solution. It is, of course, a different approach to imperative programming e.g. Python.

      It is therefore possible to cancel one or two constraints and still get a solution.
      We soon know if we have not got sufficient constraints when we don’t get a solution.

      Lines 11 and 12 are logical in that we know over several time scales that all the fathers and daughters ages will be different, so it is quite OK to make this a constraint, in my view.

      As an experiment, I remmed out Lines 22, 23 and 24, leaving Line 12 functional. This still gave the correct solution. I then remmed out Line 12 and I got three solutions – one correct and two wrong.
      This shows that Line 12 constraint is related to the constraints on Lines 22,23 and 24 in functionality.

      So we could equally say that Lines 22,23 and 24 could be removed, providing Line 12 was not removed.

      I think all the constraints I used have a reasonable basis, so it is probably best not to tinker with individual constraints and let constraint programming use its own internal procedures to find a solution.

      Like

    • Frits 11:26 am on 31 October 2020 Permalink | Reply

      @GeoffR,

      My only point is that this particular constraint is not part of the requirements and can potentially give cause to miss certain solutions.

      You say that “we know over several time scales that all the fathers and daughters ages will be different”. I think it is possible the daughter “today” can have the same age as her father at the time she was a little girl (same for later on).

      Example (if we forget about primes):

      Father’s ages in sequence are 23, 44, 83
      Daughters ages in sequence are 2, 23, 62

      You may be right if the line 11/12 constraint emerges from all the requirements but that is not immediately clear to me.

      Like

  • Jim Randell 8:57 am on 25 October 2020 Permalink | Reply
    Tags:   

    Teaser 1923: Get back to me 

    From The Sunday Times, 25th July 1999 [link]

    I met a nice girl at a party and asked for her phone number. To prove that she was no pushover she made me work for it.

    “My number has seven digits, all different”, she told me. “If you form the largest number you can with those seven digits and subtract from it the reverse of that largest number, then you get another seven-digit number”, she added.

    “Then if you repeat the process with that new seven-digit number, you get another seven-digit number”, she added. “And if you repeat the process enough times you’ll get back to my phone number”.

    This information did enable me to get back to her!

    What is her telephone number?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1923]

     
    • Jim Randell 8:58 am on 25 October 2020 Permalink | Reply

      When we apply the process to a number the order of the digits does not matter, as we immediately reorder the digits to make the largest possible number, and it’s reverse. So we only need to consider the possible collections of 7 distinct digits. If the same set of digits pops out of the process after several applications we have found a solution.

      As we keep applying the process we must eventually get to a collection of digits we have seen before (as there are only a finite number of collections of 7 digits). So if we haven’t found a solution by the time we get a repeated set of digits, then we are never going to find one.

      This Python program runs in 53ms.

      Run: [ @repl.it ]

      from enigma import subsets, irange, inf, nconcat, nsplit, ordered, printf
      
      # check the process, using a sequence of 7 ordered digits
      def check(ds):
        seen = set()
        ss = ds
        for i in irange(1, inf):
          if ss in seen: return None
          seen.add(ss)
          # make the largest number, and it's reverse
          n = nconcat(ss[::-1])
          r = nconcat(ss)
          # and subtract them
          s = n - r
          # are the digits in s the starting digits?
          ss = ordered(*(nsplit(s)))
          if len(ss) != 7: return None
          if i > 2 and ss == ds: return s
      
      # choose the 7 different digits in the phone number
      for ds in subsets(irange(0, 9), size=7):
        n = check(ds)
        if n is not None:
          printf("number = {n:07d}")
      

      Solution: The telephone number is: 7509843.

      Repeated application of the process yields:

      7509843: 9875430 – 0345789 = 9529641
      9529641: 9965421 – 1245699 = 8719722
      8719722: 9877221 – 1227789 = 8649432
      8649432: 9864432 – 2344689 = 7519743
      7519743: 9775431 – 1345779 = 8429652
      8429652: 9865422 – 2245689 = 7619733
      7619733: 9776331 – 1336779 = 8439552
      8439552: 9855432 – 2345589 = 7509843

      So we have to apply the process 8 times.

      Like

    • Frits 2:24 pm on 27 October 2020 Permalink | Reply

      # nice girl's phone number abcdefg
      #
      # last transformation:
      #
      # h1h2h3ml3l2l1 = abcdefg + l1l2l3mh3h2h1
      #
      # c1...c5 carry bits
      #
      # h1 = a + l1
      # h2 = b + l2 + c5
      # h3 = c + l3 + c4 - 10*c5
      # m  = d + m  + c3 - 10*c4
      # l3 = e + h3 + c2 - 10*c3
      # l2 = f + h2 + c1 - 10*c2
      # l1 = g + h1 - 10*c1
      #
      # l1 < h1 --> c1 = 1
      # l2 < h2 + 1 --> c2 = 1
      # l3 < h3 + 1 --> c3 = 1
      # m = d + m + 1 - 10*c4 --> d = 10*c4 - 1 --> c4 = 1 --> d = 9
      #
      # h1 = a + l1 = a + g + h1 - 10 --> a + g = 10
      # h2 = b + l2 + c5 = b + f + h2 - 9 + c5 --> b + f = 9 - c5
      # h3 = c + l3 + 1 - 10*c5 = c + e + h3 - 9 + 1 - 10*c5 --> c + e = 8 + 10*c5
      # --> c5 = 0 and c + e = 8
      

      With A+G = 10, B+F = 9, C+E = 8, D = 9 enigma [[SubstitutedExpression]] leaves 64 ABCDEFG combinations to check.

      Like

      • Frits 2:47 pm on 27 October 2020 Permalink | Reply

        Don’t know why I didn’t use carry bit c6 but the result is the same (c6 = 0).

        Like

  • Jim Randell 9:02 am on 20 October 2020 Permalink | Reply
    Tags:   

    Teaser 1917: Trick shots 

    From The Sunday Times, 13th June 1999 [link]

    Joe’s billiard table is of high quality but slightly oversized. It is 14 ½ feet long by 7 feet wide, with the usual six pockets, one in each corner and one in the middle of each long side.

    Joe’s ego is also slightly oversized and he likes to show off with his trick shots. One of the most spectacular is to place a ball at a point equidistant from each of the longer sides and 19 inches from the end nearest to him. He then strikes the ball so that it bounces once off each of the four sides and into the middle pocket on his left.

    He has found that he has a choice of directions in which to hit the ball in order the achieve this effect.

    (a) How many different directions will work?
    (b) How far does the ball travel in each case?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1917]

     
    • Jim Randell 9:03 am on 20 October 2020 Permalink | Reply

      As with beams of light, it is easier to mirror the table and allow the ball to travel in a straight line. (See: Enigma 1039, Enigma 1532, Teaser 2503).

      If we mirror the table along all four sides we get a grid-like pattern of tables and the path of the ball becomes a straight line between the source and the target pocket.

      The line must cross each colour side once. So vertically it must cross a green and a red (in some order) before ending up in the pocket. Which means only an upward line will do. Horizontally it must cross an orange and a blue line before hitting the pocket. The two possible paths are indicated in this diagram:

      We can then calculate the distances involved. In both cases the vertical distance is 2.5 widths = 210 inches.

      And the horizontal distances are: 2.5 lengths – 19 inches = 416 inches, and: 1.5 lengths + 19 inches = 280 inches.

      The required distances are then:

      >>> hypot(210, 416)
      466.0
      >>> hypot(210, 280)
      350.0
      

      Solution: (a) There are 2 directions which will work; (b) In once case the ball travels: 38 feet, 10 inches; in the other case: 29 feet, 2 inches.

      The paths of the ball on the table look like this:


      Like

  • Jim Randell 9:58 am on 13 October 2020 Permalink | Reply
    Tags:   

    Teaser 1915: Rabbit, rabbit, rabbit, … 

    From The Sunday Times, 30th May 1999 [link]

    In each of the four houses in a small terrace lives a family with a boy, a girl and a pet rabbit. One of the children has just mastered alphabetical order and has listed them thus:

    I happen to know that this listing gave exactly one girl, one boy and one rabbit at her, his or its correct address. I also have the following correct information: neither Harry nor Brian lives at number 3, and neither Donna nor Jumper lives at number 1. Gail’s house number is one less than Mopsy’s house number, and Brian’s house number is one less than Cottontail’s.

    Who lives where?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1915]

     
    • Jim Randell 9:58 am on 13 October 2020 Permalink | Reply

      See also: Teaser 2944.

      This Python program runs in 44ms.

      Run: [ @repl.it ]

      from enigma import subsets, printf
      
      # the names
      girls = "ADGK"
      boys = "BEHL"
      rabbits = "CFJM"
      
      # choose an ordering where exactly k of the original ordering is correct
      def choose(xs, k=1):
        for ys in subsets(xs, size=len, select="P"):
          if sum(x == y for (x, y) in zip(xs, ys)) == k:
            yield ys
      
      # choose an ordering for the boys
      for boy in choose(boys):
        # "neither H nor B live at number 3 [= index 2]"
        if boy[2] == 'H' or boy[2] == 'B': continue
      
        # choose an ordering for the rabbits
        for rabbit in choose(rabbits):
          # "J does not live at number 1 [=index 0]"
          if rabbit[0] == 'J': continue
          # "B's number is one less than C's"
          if boy.index('B') + 1 != rabbit.index('C'): continue
      
          # choose an ordering for the girls
          for girl in choose(girls):
            # "D does not live at number 1 [= index 0]"
            if girl[0] == 'D': continue
            # "G's number is one less than M's"
            if girl.index('G') + 1 != rabbit.index('M'): continue
      
            # output solution
            for (n, (g, b, r)) in enumerate(zip(girl, boy, rabbit), start=1):
              printf("{n}: {g} {b} {r}")
            printf()
      

      Solution: The occupants of each house are:

      1: Kelly, Harry, Flopsy
      2: Alice, Brian, Jumper
      3: Gail, Eric, Cottontail
      4: Donna, Laurie, Mopsy

      Like

    • Frits 4:56 pm on 13 October 2020 Permalink | Reply

      from enigma import SubstitutedExpression
      
      girls   = ("", "Alice", "Donna", "Gail", "Kelly")
      boys    = ("", "Brian", "Eric", "Harry", "Laurie")
      rabbits = ("", "Cottontail", "Flopsy", "Jumper", "Mopsy")
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
         # "B's number is one less than C's"
         "B + 1 = C",
         # "G's number is one less than M's" 
         "G + 1 = M",
         # listing gave exactly one girl, one boy and one rabbit at
         # her, his or its correct address
         "sum((A == 1, D == 2, G == 3, K == 4)) == 1",
         "sum((B == 1, E == 2, H == 3, L == 4)) == 1",
         "sum((C == 1, F == 2, J == 3, M == 4)) == 1",
        ],
        answer="ADGK, BEHL, CFJM",   
        digits=range(1,5),
        distinct=('ADGK', 'BEHL', 'CFJM'),
        # "D does not live at number 1"
        # "J does not live at number 1"
        # "neither H nor B live at number 3"
        d2i={1 : "CMDJ", 3 : "BH", 4 : "BG"}, 
        verbose=0,
        #reorder=0,
      )
      
      # Print answers
      for (_, ans) in p.solve(): 
        for k in "1234":
          x = [j+1 for x in ans for j, y in enumerate(str(x)) if y == k]
          print(f"house {k}: {girls[x[0]]:<6} {boys[x[1]]:<7} {rabbits[x[2]]}")
        
      # house 1: Kelly  Harry   Flopsy
      # house 2: Alice  Brian   Jumper
      # house 3: Gail   Eric    Cottontail
      # house 4: Donna  Laurie  Mopsy
      

      Like

    • John Crabtree 6:34 pm on 13 October 2020 Permalink | Reply

      Flopsy must be at #1.
      Brian cannot be at #2, otherwise Jumper and Mopsy are both correct or both incorrect.
      And so Brian is at #1, Cottontail at #3, Jumper at #1, Mopsy at #4, and Gail at #3, etc

      Like

  • Jim Randell 10:36 am on 8 October 2020 Permalink | Reply
    Tags:   

    Teaser 1908: Dunroamin 

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

    At our DIY store you can buy plastic letters of the alphabet in order to spell out your house name. Although all the A’s cost the same as each other, and all the B’s cost the same as each other, etc., different letters sometimes cost different amounts with a surprisingly wide range of prices.

    I wanted to spell out my house number:

    FOUR

    and the letters cost me a total of £4. Surprised by this coincidence I worked out the cost of spelling out each of the numbers from ONE to TWELVE. In ten out of the twelve cases the cost in pounds equalled the number being spelt out.

    For which house numbers was the cost different from the number?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1908]

     
    • Jim Randell 10:37 am on 8 October 2020 Permalink | Reply

      (See also: Teaser 2756, Enigma 1602).

      This Python program checks to see if each symbol can be assigned a value that is a multiple of 25p to make the required 10 words correct (and also checks that the remaining two words are wrong).

      Instead of treating the letters G, H, R, U separately, we treat them as GH, HR, UR, which reduces the number of symbols to consider by 1.

      The program runs in 413ms.

      Run: [ @repl.it ]

      from enigma import irange, subsets, update, unpack, diff, join, map2str, printf
      
      # <value> -> <symbol> mapping
      words = {
        100: ("O", "N", "E"),
        200: ("T", "W", "O"),
        300: ("T", "HR", "E", "E"),
        400: ("F", "O", "UR"),
        500: ("F", "I", "V", "E"),
        600: ("S", "I", "X"),
        700: ("S", "E", "V", "E", "N"),
        800: ("E", "I", "GH", "T"),
        900: ("N", "I", "N", "E"),
        1000: ("T", "E", "N"),
        1100: ("E", "L", "E", "V", "E" ,"N"),
        1200: ("T" ,"W", "E", "L", "V", "E"),
      }
      
      # decompose t into k numbers, in increments of step
      def decompose(t, k, step, ns=[]):
        if k == 1:
          yield ns + [t]
        elif k > 1:
          k_ = k - 1
          for n in irange(step, t - k_ * step, step=step):
            yield from decompose(t - n, k_, step, ns + [n])
      
      # find undefined symbols and remaining cost
      def remaining(v, d):
        (us, t) = (set(), v)
        for x in words[v]:
          try:
            t -= d[x]
          except KeyError:
            us.add(x)
        return (us, t)
      
      # find values for vs, with increments of step
      def solve(vs, step, d=dict()):
        # for each value find remaining cost and undefined symbols
        rs = list()
        for v in vs:
          (us, t) = remaining(v, d)
          if t < 0 or bool(us) == (t == 0): return
          if t > 0: rs.append((us, t, v))
        # are we done?
        if not rs:
          yield d
        else:
          # find the value with the fewest remaining symbols (and lowest remaining value)
          (us, t, v) = min(rs, key=unpack(lambda us, t, v: (len(us), t)))
          # the remaining values
          vs = list(x for (_, _, x) in rs if x != v)
          # allocate the unused symbols
          us = list(us)
          for ns in decompose(t, len(us), step):
            # solve for the remaining values
            yield from solve(vs, step, update(d, us, ns))
      
      # choose the 2 equations that are wrong
      for xs in subsets(sorted(words.keys()), size=2):
        # can't include FOUR
        if 400 in xs: continue
        # find an allocation of values
        for d in solve(diff(words.keys(), xs), 25):
          xvs = list(sum(d[s] for s in words[x]) for x in xs)
          if all(x != v for (x, v) in zip(xs, xvs)):
            # output solution
            wxs = list(join(words[x]) for x in xs)
            printf("wrong = {wxs}", wxs=join(wxs, sep=", ", enc="[]"))
            printf("-> {d}", d=map2str(d, sep=" ", enc=""))
            for (x, xv) in zip(wxs, xvs):
              printf("  {x} = {xv}p")
            printf()
            break
      

      Solution: NINE and TEN do not cost an amount equal to their number.

      One way to allocate the letters, so that ONEEIGHT, ELEVEN, TWELVE work is:

      25p = F, H, N, O, T
      50p = E, X
      150p = W, R
      200p = I, U
      225p = V
      350p = S
      500p = G
      700p = L

      (In this scheme: NINE costs £3 and TEN costs £1).

      But there are many other possible assignments.

      You can specify smaller divisions of £1 for the values of the individual symbols, but the program takes longer to run.

      It makes sense that NINE and TEN don’t cost their value, as they are short words composed entirely of letters that are available in words that cost a lot less.


      Here’s a manual solution:

      We note that ONE + TWELVE use the same letters as TWO + ELEVEN, so if any three of them are correct so is the fourth. So there must be 0 or 2 of the incorrect numbers among these four.

      Now, if ONE and TEN are both correct, then any number with value 9 or less with a T in it must be wrong. i.e. TWO, THREE, EIGHT. Otherwise we could make TEN for £10 or less, and have some letters left over. And this is not possible.

      If ONE is incorrect, then the other incorrect number must be one of TWO, ELEVEN, TWELVE, and all the remaining numbers must be correct. So we could buy THREE and SEVEN for £10, and have the letters to make TEN, with EEEHRSV left over. This is not possible.

      So ONE must be correct, and TEN must be one of the incorrect numbers.

      Now, if NINE is correct, then any number with value 7 or less with an I in it must be incorrect. Which would mean FIVE and SIX are incorrect. This is not possible.

      Hence, the incorrect numbers must be NINE and TEN.

      All that remains is to demonstrate one possible assignment of letters to values where NINE and TEN are incorrect and the rest are correct. And the one found by the program above will do.

      Like

    • GeoffR 6:36 pm on 8 October 2020 Permalink | Reply

      I used a wide range of upper .. lower bound values for letter variables.

      The programme would not run satisfactorily under the Geocode Solver, but produced a reasonable run time under the Chuffed Solver. It confirmed the incorrect numbers were NINE and TEN.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 10..900:O; var 10..900:N; var 10..900:E; var 10..900:T;
      var 10..900:W; var 10..900:H; var 10..900:R; var 10..900:F;
      var 10..900:U; var 10..900:V; var 10..900:I; var 10..900:L;
      var 10..900:G; var 10..900:S; var 10..900:X;
      
      constraint sum([
      O+N+E == 100, T+W+O == 200, T+H+R+E+E == 300, F+O+U+R == 400,
      F+I+V+E == 500, S+I+X == 600, S+E+V+E+N == 700,
      E+I+G+H+T == 800, N+I+N+E == 900, T+E+N == 1000,
      E+L+E+V+E+N == 1100, T+W+E+L+V+E == 1200]) == 10;
      
      solve satisfy;
      
      output [" ONE = " ++ show (O + N + E) ++
      "\n TWO = " ++ show (T + W + O ) ++
      "\n THREE = " ++ show (T + H + R + E + E) ++
      "\n FOUR = " ++ show (F + O + U + R) ++
      "\n FIVE = " ++ show (F + I + V + E) ++
      "\n SIX = " ++ show (S + I + X) ++
      "\n SEVEN = " ++  show (S + E + V + E + N) ++
      "\n EIGHT = " ++ show (E + I + G + H + T) ++
      "\n NINE = " ++ show (N + I + N + E) ++
      "\n TEN = " ++ show (T + E + N) ++
      "\n ELEVEN = " ++ show (E + L + E + V + E + N) ++
      "\n TWELVE = " ++ show (T + W + E + L + V + E) ++
      "\n\n [O N E T W H R F U V I L G S X = ]" ++ 
      show([O, N, E, T, W, H, R, F, U, V, I, L, G, S, X]) ];
      
      %  ONE = 100
      %  TWO = 200
      %  THREE = 300
      %  FOUR = 400
      %  FIVE = 500
      %  SIX = 600
      %  SEVEN = 700
      %  EIGHT = 800
      %  NINE = 171   << INCORRECT
      %  TEN = 101    << INCORRECT
      %  ELEVEN = 1100
      %  TWELVE = 1200
      %  Letter Values (One of many solutions)
      %  [O    N   E   T   W    H   R    F   U    V    I   L    G    S    X = ]
      %  [10, 71, 19, 11, 179, 13, 238, 10, 142, 461, 10, 511, 747, 130, 460]
      %  time elapsed: 0.04 s
      % ----------
      
      
      
      

      Like

      • Frits 7:47 pm on 11 October 2020 Permalink | Reply

        @GeoffR, As Jim pointed out to me (thanks) that FOUR can not be an incorrect number your MiniZinc program can be adapted accordingly. Now both run modes have similar performance.

        Like

    • Frits 4:56 pm on 11 October 2020 Permalink | Reply

      Pretty slow (especially for high maximum prizes).

      I combined multiple “for loops” into one loop with the itertools product function so it runs both under Python and PyPy.

      @ Jim, your code has some unexplained “# can’t include FOUR” code (line 62, 63).
      It is not needed for performance.

       
      from enigma import SubstitutedExpression
      from itertools import product
      
      mi = 25      # minimum prize
      step = 25    # prizes differ at least <step> pennies 
      ma = 551     # maximum prize + 1
      bits = set() # x1...x12 are bits, 10 of them must be 1, two them must be 0
      
      # return numbers in range and bit value 
      # (not more than 2 bits in (<li> plus new bit) may be 0)
      rng = lambda m, li: product(range(mi, m, step), 
                                 (z for z in {0, 1} if sum(li + [z]) > len(li) - 2))
      
      def report():
        print(f"ONE TWO THREE FOUR FIVE SIX "
        f"SEVEN EIGHT NINE TEN ELEVEN TWELVE"
        f"\n{O+N+E} {T+W+O}   {T+HR+E+E}  {F+O+UR}  {F+I+V+E} {S+I+X} "
        f"  {S+E+V+E+N}   {E+I+GH+T}  {N+I+N+E} {T+E+N}   {E+L+E+V+E+N}"
        f"   {T+W+E+L+V+E}"
        f"\n   O   N   E   T   W   L  HR   F  UR   I   V   S   X  GH"
        f"\n{O:>4}{N:>4}{E:>4}{T:>4}{L:>4}{W:>4}{HR:>4}{F:>4}{UR:>4}"
        f"{I:>4}{V:>4}{S:>4}{X:>4}{GH:>4}")
      
      for (O, N, E) in product(range(mi, ma, step), repeat=3):
        for x1 in {0, 1}:
          if x1 != 0 and O+N+E != 100: continue
          li1 = [x1]
          for (T, x10) in rng(ma, li1):
            if x10 != 0 and T+E+N != 1000: continue
            li2 = li1 + [x10]
            maxW = 201 - 2*mi if sum(li2) == 0 else ma
            for (W, x2) in rng(maxW, li2):
              if x2 != 0 and T+W+O != 200: continue
              li3 = li2 + [x2]
              for (I, x9) in rng(ma, li3):
                if x9 != 0 and N+I+N+E != 900: continue
                li4 = li3 + [x9]
                for L in range(mi, ma, step):
                  for (V, x12) in rng(ma, li4):
                    if x12 != 0 and T+W+E+L+V+E != 1200: continue
                    li5 = li4 + [x12]
                    for x11 in {0, 1}:
                      li6 = li5 + [x11]
                      if sum(li6) < 4 : continue
                      if x11 != 0 and E+L+E+V+E+N != 1100: continue
                      maxF = 501 - 3*mi if sum(li6) == 4 else ma
                      for (F, x5) in rng(maxF, li6):
                        if x5 != 0 and F+I+V+E != 500: continue
                        li7 = li6 + [x5]
                        maxSX = 601 - 2*mi if sum(li7) == 5 else ma
                        for (S, x7) in rng(maxSX, li7):
                          if x7 != 0 and S+E+V+E+N != 700: continue
                          li8 = li7 + [x7]
                          for (X, x6) in rng(maxSX, li8):
                            if x6 != 0 and S+I+X != 600: continue
                            li9 = li8 + [x6]
                            maxGH = 801 - 3*mi if sum(li9) == 7 else ma
                            for (GH, x8) in rng(maxGH, li9):
                              if x8 != 0 and E+I+GH+T != 800: continue
                              li10 = li9 + [x8]
                              maxHR = 301 - 4*mi if sum(li10) == 8 else ma
                              for (HR, x3) in rng(maxHR, li10):
                                if x3 != 0 and T+HR+E+E != 300: continue
                                li11 = li10 + [x3]
                                maxUR = 401 - 3*mi if sum(li11) == 9 else ma
                                for (UR, x4) in rng(maxUR, li11):
                                  if x4 != 0 and F+O+UR != 400: continue
      
                                  r = tuple(li11 + [x4])
                                  if sum(r) != 10: continue  
                                  # only report unique bits
                                  if r not in bits:
                                    report()
                                    bits.add(r)
                                    
      # ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE TEN ELEVEN TWELVE
      # 100 200   300  400  500 600   700   800  125 100   1100   1200
      #    O   N   E   T   W   L  HR   F  UR   I   V   S   X  GH
      #   25  25  50  25 550 150 175  50 325  25 375 200 375 700 
      

      Like

      • Jim Randell 5:17 pm on 11 October 2020 Permalink | Reply

        @Frits: The fact the FOUR cannot be one of the incorrect numbers is one of the conditions mentioned in the puzzle text.

        Like

    • GeoffR 8:56 am on 12 October 2020 Permalink | Reply

      @Frits: I never used the fact that FOUR cannot be one of the incorrect numbers.
      I only spelt out a condition in the teaser as part of the programme ie F+O+U+R = 400.
      I do not think my programme needs adapting – is OK as the original posting.

      Like

  • Jim Randell 9:10 am on 29 September 2020 Permalink | Reply
    Tags:   

    Teaser 1904: Neat answer 

    From The Sunday Times, 14th March 1999 [link]

    I have started with a four-figure number with its digits in decreasing order. I have reversed the order of the four digits to give a smaller number. I have subtracted the second from the first to give a four-figure answer, and I have seen that the answer uses the same four digits — very neat!

    Substituting letters for digits, with different letters being consistently used for different digits, my answer was NEAT!

    What, in letters, was the four-figure number I started with?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1904]

     
    • Jim Randell 9:19 am on 29 September 2020 Permalink | Reply

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

      The following run file executes in 91ms.

      Run: [ @repl.it ]

      #!/usr/bin/env python -m enigma -r
      
      SubstitutedExpression
      
      "WXYZ - ZYXW = NEAT"
      "ordered(N, E, A, T) == (Z, Y, X, W)"
      
      --distinct="WXYZ,NEAT"
      --answer="translate({WXYZ}, str({NEAT}), 'NEAT')"
      

      Solution: The initial 4-figure number is represented by: ANTE.

      So the alphametic sum is: ANTEETNA = NEAT.

      And the corresponding digits: 7641 – 1467 = 6174.

      Like

    • Hugh Casement 1:37 pm on 29 September 2020 Permalink | Reply

      If we had not been told the digits were in decreasing order there would have been three other solutions:
      2961 – 1692 = 1269, 5823 – 3285 = 2538, 9108 – 8019 = 1089.

      Like

      • Finbarr Morley 10:58 am on 24 November 2020 Permalink | Reply

        I’m not sure that’s right:
        2961-1692= 1269
        ANTE-ETNA=EATN
        It’s true, but it’s not NEAT!

        Like

        • Jim Randell 8:57 am on 25 November 2020 Permalink | Reply

          The result of the sum is NEAT by definition.

          Without the condition that digits in the number you start with are in descending order, we do indeed get 4 different solutions. Setting the result to NEAT, we find they correspond to four different alphametic expressions:

          9108 – 8019 = 1089 / TNEAAENT = NEAT
          5823 – 3285 = 2538 / ETNAANTE = NEAT
          7641 – 1467 = 6174 / ANTEETNA = NEAT
          2961 – 1692 = 1269 / ETANNATE = NEAT

          Like

    • GeoffR 6:30 pm on 29 September 2020 Permalink | Reply

      
      from itertools import permutations
      from enigma import nreverse, nsplit
      
      for p1 in permutations((9, 8, 7, 6, 5, 4, 3, 2, 1, 0), 4):
        W, X, Y, Z = p1
        if W > X > Y > Z:
          WXYZ = 1000 * W + 100 * X + 10 * Y + Z
          ZYXW = nreverse(WXYZ)
          NEAT = WXYZ - ZYXW
          N, E, A, T = nsplit(NEAT)
          # check sets of digits are the same
          if {W, X, Y, Z} == {N, E, A, T}:
            print(f"Sum is {WXYZ} - {ZYXW} = {NEAT}")
      
      # Sum is 7641 - 1467 = 6174
      
      

      Like

    • GeoffR 8:56 am on 25 November 2020 Permalink | Reply

      I checked Hugh’s assertion, changing my programme to suit, and it looks as though he is correct.
      My revised programme gave the original correct answer and the three extra answers, as suggested by Hugh.
      @Finnbarr:
      If we take NEAT = 1269, then ETAN – NATE = NEAT – (Sum is 2961 – 1692 = 1269)

      
      from itertools import permutations
      from enigma import nreverse, nsplit
       
      for p1 in permutations((9, 8, 7, 6, 5, 4, 3, 2, 1,0), 4):
          W, X, Y, Z = p1
          #if W > X > Y > Z:
          WXYZ = 1000 * W + 100 * X + 10 * Y + Z
          ZYXW = nreverse(WXYZ)
          NEAT = WXYZ - ZYXW
          if NEAT > 1000 and WXYZ > 1000 and ZYXW > 1000:
              N, E, A, T = nsplit(NEAT)
              # check sets of digits are the same
              if {W, X, Y, Z} == {N, E, A, T}:
                print(f"Sum is {WXYZ} - {ZYXW} = {NEAT}")
       
      # Sum is 9108 - 8019 = 1089
      # Sum is 7641 - 1467 = 6174  << main answer
      # Sum is 5823 - 3285 = 2538
      # Sum is 2961 - 1692 = 1269
      
      

      Like

    • Finbarr Morley 9:41 am on 30 November 2020 Permalink | Reply

      A maths student would view this as 4 unknowns (A,N,T, & E) and 4 Equations.
      So it can be uniquely solved with simultaneous equations.

      And there’s a neat ‘trick’ recognising the pairs of letters:

      A E
      E A

      And

      N T
      T N

      The ANSWER is the easy bit:

      ANTE
      7641

      The SOLUTION is as follows:

      Column 4321
             ANTE
           - ETNA
           = NEAT
      

      From the 1,000’s column 4 we seen that A>E, otherwise the answer would be negative.
      ∴ in the 1’s column, (where E is less than A) E must borrow 1 from the T in the 10’s column:

      Col.   2        1
             T-1   10+E
             N        A
             A        T  
      

      There are 2 scenarios:
      (A) N>T
      (B) N<T

      As before, T in the 10’s column must borrow 1 from the N in the 100’s column.

          4    3     2       1
          A  N-1  10+T-1  10+E             
        - E    T     N       A
        = N    E     A       T
      

      The equations for each of the 4 columns are:

      (1) 10+E-A = T
      (2) 10+T-1-N = A
      (3) N-1-T = E
      (4) A-E = N

      Rearrange to:

      (1) A-E = 10-T
      (4) A-E = N

      (2) N-T = 9-A
      (3) N-T = E+1

      (1-4) 10-T = N (because they both equal A-E) rearrange to N+T = 10
      (2-3) 9-A = E+1 (because they both equal N-T) rearrange to A+E = 8

      From here either solve by substituting numbers, or with algebra.

      SUBSTITUTION:

      A+E=8 and A>E. So A = 5,6,7 or 8

      A   E     A-E=N     N+T=10
      5   3         2       8  N>T
      6   2         4       6  N>T
      7   1         6       4  *** THIS IS THE ONLY SOLUTION ***
      8   0         8	      2 can’t have 2 numbers the same
      

      ALGERBRA:

                N+T=10             A+E=8
           (2)-(N-T=9-A)      (1)+(A-E=10-T)	
                 2T=1+A  			                    2A    =(18-T)
       (x2)      4T=2+(2A)
      
      ∴   4T=2+(18-T)   
          5T=20
           T=4
      ∴    N=6 (N+T=10)
      ∴    E=1  (N-T=1+E)
      ∴    A=7  (A+E=8)
      

      Check assumption (A) N>T TRUE (6>4)

      ANSWER:
      ANTE 7641
      - ETNA – 1467
      = NEAT = 6174

      Like

      • Finbarr Morley 9:43 am on 30 November 2020 Permalink | Reply

        Now repeat for Assumption (B) N < T, to see if it is valid.

        Equations are similar but different:

        This time the N in the 100’s column must borrow 1 from the A in the 1000’s column.

           4      3      2      1
        
          A-1   10+N    T-1   10+E             
        
        -  E      T      N      A
        
        =  N      E      A      T
        

        The equations for each of the 4 columns are:

        (1) 10+E-A=T (exactly the same as before)

        (2) T-1-N=A

        (3) 10+N-T=E

        (4) A-1-E=N

        Rearrange to:

        (1) A-E = 10-T

        (4) A-E = N+1

        (2) T-N = A+1

        (3) T-N = 10-E

        (1-4) 10-T=N+1 (because they both equal A-E) rearrange to T+N=9

        (2-3) A+1=10-E (because they both equal T-N) rearrange to A+E=9

        From here either solve by substituting numbers, or with algebra.

        SUBSTITUTION:

        A+E=9 and A>E.

        So A = 5,6,7 or 8 or 9

        A   E     A-E=N+1     N+T=9      T-N=10-E
        5   4         0         9           no           
        6   3         2         7           no
        7   2         4         5           no
        8   1         6         3           N<T
        9   0         8         1           N<T
        

        There are no valid answers with N<T.

        ALGEBRA:

                  T+N = 9               A+E = 9
              2) +T-N = A+1        1) +(A-E = 10-T)	
                 2T   = 10+A           2A   = 19-T
             x2) 4T   = 20+2A
        
        ∴   4T = 20+19-T   
            3T = 39
             T = 13
        

        Since this is not 0 to 9, the assumption (B) N<T is invalid.

        Assumption A is the only valid solution.

        There can be only one – William Shakespeare

        Like

        • Jim Randell 11:55 am on 30 November 2020 Permalink | Reply

          @Finbarr: Thanks for your comments. (And I hope I’ve tidied them up OK).

          Although I think we can take a bit of a shortcut:

          If we start with:

          WXYZ + ZYXW = NEAT
          where: W > X > Y > Z

          Then considering the columns of the sum we have:

          (units) T = 10 + Z W
          (10s) A = 9 + YX
          (100s) E = X – 1 – Y
          (1000s) N = WZ

          Then, (units) + (1000s) and (10s) + (100s) give:

          N + T = 10
          A + E = 8

          which could be used to shorten your solution.

          Like

          • Jim Randell 1:53 pm on 30 November 2020 Permalink | Reply

            Or we can extend it into a full solution by case analysis:

            Firstly, we see Z ≠ 0, as ZYXW is a 4-digit number.

            And W > 5, as 5 + 4 + 3 + 2 < 18.

            So considering possible values for W:

            [W = 6]

            There is only one possible descending sequence that sums to 18, i.e. (6, 5, 4, 3)

            So the sum is:

            6543 – 3456 = 3087

            which doesn’t work.

            [W = 7]

            W must be paired with 3 (to make 10) or 1 (to make 8).

            If it is paired with 3, then the other pair is 2+6. So the sum is:

            7632 – 2367 = 5265

            which doesn’t work.

            If it is paired with 1, then the other pair is either 2+8 or 4+6, which gives us the following sums:

            8721 – 1278 = 7443
            7641 – 1467 = 6174

            The first doesn’t work, but the second gives a viable solution: ANTEETNA = NEAT.

            And the following cases show uniqueness:

            [W = 8]

            W must be paired with 2, and the other pair is either 1+7 or 3+5, and we have already looked at (8, 7, 2, 1)

            8532 – 2358 = 6174

            This doesn’t work.

            [W = 9]

            W must be paired with 1, and the other pair either 2+6 or 3+5:

            9621 – 1269 = 8352
            9531 – 1359 = 8173

            Neither of these work.

            Like

    • Finbarr Morley 3:14 pm on 30 November 2020 Permalink | Reply

      I’m curious about the properties of these Cryptarithmetics.
      How is it that ANTE-ETNA=NEAT has 1 unique solution out of 10,000.
      But ANTE-ETNA=NAET has none?

      A Google search reveals some discussion:
      http://cryptarithms.awardspace.us/primer.html
      https://www.codeproject.com/Articles/176768/Cryptarithmetic
      https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1949-8594.1987.tb11711.x

      The latter seems to be heading for a discussion on the rules, but it’s ony the first page of an extract.
      Any thoughts from the collective on what the natural rules are?

      Like

  • Jim Randell 8:55 am on 27 September 2020 Permalink | Reply
    Tags:   

    Teaser 1892: Puzzling present 

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

    I have received an astonishing letter from a fellow puzzle-setter. He writes:

    “I am sending you a puzzle in the form of a parcel consisting of an opaque box containing some identical marbles, each weighing a whole number of grams, greater than one gram. The box itself is virtually weightless. If I told you the total weight of the marbles, you could work out how many there are.”

    He goes on:

    “To enable you to work out the total weight of the marbles I am also sending you a balance and a set of equal weights, each weighing a whole number of grams, whose total weight is two kilograms. Bearing in mind what I told you above, these weights will enable you to calculate the total weight of the marbles and hence how many marbles there are.”

    I thought that he was sending me these items under seperate cover, but I had forgotten how mean he is. He went on:

    “I realise that I can save on the postage. If I told you the weight of each weight you would still be able to work out the number of marbles. Therefore I shall not be sending you anything.”

    How many marbles were there?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1892]

     
    • Jim Randell 8:55 am on 27 September 2020 Permalink | Reply

      (See also: Enigma 1606).

      There must be a prime number of balls, each weighing the same prime number of grams.

      This Python program uses the same approach I took with Enigma 1606 (indeed if given a command line argument of 4000 (= the 4kg total of the weights) it can be used to solve that puzzle too).

      It runs in 48ms.

      Run: [ @repl.it ]

      from enigma import Primes, isqrt, divisor, group, arg, printf
      
      # total weights
      T = arg(2000, 0, int)
      
      # find prime squares up to T
      squares = list(p * p for p in Primes(isqrt(T)))
      
      printf("[T={T}, squares={squares}]")
      
      # each weight is a divisor of T
      for w in divisor(T):
      
        # make a "by" function for weight w
        def by(p):
          (k, r) = divmod(p, w)
          return (k if r > 0 else -k)
      
        # group the squares into categories by the number of weights required
        d = group(squares, by=by)
        # look for categories with only one weight in
        ss = list(vs[0] for vs in d.values() if len(vs) == 1)
        # there should be only one
        if len(ss) != 1: continue
      
        # output solution
        p = ss[0]
        printf("{n}x {w}g weights", n=T // w)
        printf("-> {p}g falls into a {w}g category by itself")
        printf("-> {p}g = {r} balls @ {r}g each", r=isqrt(p))
        printf()
      

      Solution: There are 37 marbles.

      There are 4× 500g weights (2000g in total), and when these are used to weigh the box we must find that it weighs between 1000g (2 weights) and 1500g (3 weights). The only prime square between these weights is 37² = 1369, so there must be 37 balls each weighing 37g.

      Telling us that “if I told you the weight of the weights you would be able to work out the number of marbles” is enough for us to work out the number of marbles, without needing to tell us the actual weight. (As there is only one set of weights that gives a category with a single prime square in). And we can also deduce the set of weights.

      However, in a variation on the puzzle where the set of weights total 2200g, we find there are two possible sets of weights that give a category with a single square in (8× 275g and 4× 550g), but in both cases it is still 1369 that falls into a category by itself, so we can determine the number (and weight) of the marbles, but not the weight (and number) of the weights. So the answer to the puzzle would still be the same.

      Likewise, if the set of weights total 3468g, there are 3 possible sets of weights that give a category with a single square in (6× 578g, 4× 867g and 3× 1156g). However in each of these cases 2809 falls into a category by itself, and so the solution would be 53 marbles each weighing 53g. Which is the answer to Enigma 1606.

      Like

    • Frits 11:36 pm on 27 September 2020 Permalink | Reply

       
      from enigma import SubstitutedExpression
      from collections import defaultdict
      
      # list of prime numbers
      P = {2, 3, 5, 7}
      P |= {x for x in range(11, 45, 2) if all(x % p for p in P)}
      # list of squared prime numbers
      P2 = [p * p for p in P]
      
      # check if only one total lies uniquely between k weights and k+1 weights
      def uniquerange(weight):
        group = defaultdict(list)
        for total in P2:
          # total lies between k weights and k+1 weights
          k = total // weight
          if group[k]:
            group[k].append(total)
          else:
            group[k] = [total]
      
        singles = [group[x][0] for x in group if len(group[x]) == 1]
        if len(singles) == 1:
          return singles[0]
          
        return 0  
            
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # ABCD is a weight and a divisor of 2000
          "2000 % ABCD == 0",
          "uniquerange(ABCD) != 0",
        ],
        answer="(ABCD, uniquerange(ABCD))",
        env=dict(uniquerange=uniquerange), # external functions
        verbose=0,
        d2i="",        # allow number representations to start with 0
        distinct="",   # allow variables with same values
      )
       
      # Print answers
      for (_, ans) in p.solve():
        print(f"{int(2000/ans[0])} x {ans[0]}g weigths")
        print(f"-> {ans[1]}g falls into a {ans[0]}g category by itself")
        print(f"-> {ans[1]}g = {ans[1] ** 0.5} balls @ {ans[1] ** 0.5}g each")
      

      Like

  • Jim Randell 8:31 am on 1 September 2020 Permalink | Reply
    Tags:   

    Teaser 1885: Sacred sign 

    From The Sunday Times, 1st November 1998 [link]

    The “Sacred Sign of Solomon” consists of a pentagon whose vertices lie on a circle, roughly as shown:

    Starting at one of the angles and going round the circle the number of degrees in the five angles of the large pentagon form an arithmetic progression; i.e., the increase from the first to the second equals the increase from the second to the third, etc.

    As you can see, the diagonals form a small inner pentagon and in the case of the sacred sign one of its angles is a right angle.

    What are the five angles of the larger pentagon?

    This puzzle was included in the book Brainteasers (2002, edited by Victor Bryant). The puzzle text above is taken from the book.

    [teaser1885]

     
    • Jim Randell 8:32 am on 1 September 2020 Permalink | Reply

      If the five sides of the pentagon are A, B, C, D, E, then the angles subtended by each side at the circumference of the circle are all equal, say, a, b, c, d, e.

      (The angles in the small pentagon, such as ace denote the sum of the three symbols, i.e. (a + c + e) etc).

      We see that: a + b + c + d + e = 180° (from many triangles, cyclic quadrilaterals, and the angles subtended at the centre of the circle).

      The internal angles of the pentagon are an arithmetic progression, say: (x – 2y, x – y, x, x + y, x + 2y).

      So, let’s say:

      a + b + e = x – 2y
      a + b + c = x – y
      b + c + d = x
      c + d + e = x + y
      a + d + e = x + 2y

      And these angles sum to 540°.

      5x = 540°
      x = 108°

      And if: x = b + c + d = 108°, then: a + e = 72°

      So starting with: a + d + e = x + 2y, we get:

      72° + d = 108° + 2y
      d = 36° + 2y

      Similarly we express each of the angles a, b, c, d, e in terms of y:

      a = 36° + y
      b = 36° – 2y
      c = 36°
      d = 36° + 2y
      e = 36° – y

      The internal angles of the small pentagon are:

      a + c + e = 108°
      a + b + d = 108° + y
      b + c + e = 108° – 3y
      a + c + d = 108° + 3y
      b + d + e = 108° – y

      And one of these is 90°.

      Our candidates are (b + c + e) and (b + d + e).

      b + c + e = 90° ⇒ y = 6°
      a = 42°
      b = 24°
      c = 36°
      d = 48°
      e = 30°

      And the angles of the large pentagon are then:

      a + b + e = 96°
      a + b + c = 102°
      b + c + d = 108°
      c + d + e = 114°
      a + d + e = 120°

      For the other candidate:

      b + d + e = 90° ⇒ y = 18°
      a = 54°
      b = 0°
      c = 36°
      d = 72°
      e = 18°

      This is not a viable solution, so:

      Solution: The angles of the larger pentagon are: 96°, 102°, 108°, 114°, 120°.

      Like

      • Jim Randell 11:54 am on 1 September 2020 Permalink | Reply

        Here’s a drawing of the symbol, with the two perpendicular lines indicated:

        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
Create your website with WordPress.com
Get started
<span>%d</span> bloggers like this: