Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

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

    Brain-Teaser 855: Pity the poor postman 

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

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

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

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

    On the unwarranted supposition that:

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

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

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

    [teaser855]

     
    • Jim Randell's avatar

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

      This Python program runs in 49ms.

      Run: [ @replit ]

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

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

      And the total sum of their numbers is 16.

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

      The total sum of their numbers is also 16.

      There are house numbers 1, 2, 2 remaining.

      Like

  • Unknown's avatar

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

    Teaser 2823: Queuing 

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

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

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

    At what time did Joe join the queue?

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

    [teaser2823]

     
    • Jim Randell's avatar

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

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

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

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

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

      Run: [ @replit ]

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

      Solution: Joe joined the queue at 10:06.

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

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

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

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

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

      Like

      • Jim Randell's avatar

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

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

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

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

        At what time did Joe join the queue?

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

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

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

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

        We get the solution:

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

        Like

  • Unknown's avatar

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

    Brain-Teaser 43: [Golf match] 

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

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

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

    This puzzle was originally published with no title.

    [teaser43]

     
    • Jim Randell's avatar

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

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

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

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

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

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

      This Python program runs in 53ms.

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

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

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

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

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

      Like

    • Hugh Casement's avatar

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

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

      Like

    • Frits's avatar

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

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

      Like

  • Unknown's avatar

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

    Teaser 3078: Digital daisy-chains 

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

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

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

    What is that total?

    [teaser3078]

     
    • Jim Randell's avatar

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

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

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

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

      This Python program runs in 51ms.

      Run: [ @replit ]

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

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

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

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

      Like

      • Jim Randell's avatar

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

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

        Run: [ @replit ]

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

        Like

    • GeoffR's avatar

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

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

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

      Like

    • GeoffR's avatar

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

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

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

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

      Like

    • Frits's avatar

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

      Doing it a bit differently and not using modulo.

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

      Like

  • Unknown's avatar

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

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

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

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

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

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

    This puzzle was originally published with no title.

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

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

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

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

    — Anthony French

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

    [teaser186]

     
    • Jim Randell's avatar

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

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

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

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

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

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

      Run: [ @replit ]

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

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

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

      Like

    • Frits's avatar

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

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

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

      Like

    • Frits's avatar

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

      This Python program runs in 4ms.

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

      Like

      • Jim Randell's avatar

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

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

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

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

        Like

    • John Crabtree's avatar

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

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

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

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

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

      Like

      • John Crabtree's avatar

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

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

        Like

  • Unknown's avatar

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

    Teaser 2822: Losing weight 

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

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

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

    How many members are there in the club?

    [teaser2822]

     
    • Jim Randell's avatar

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

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

      It runs in 54ms.

      Run: [ @replit ]

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

      Solution: There are 38 members in the club.

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


      Analytically:

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

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

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

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

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

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

      In general at the kth transfer, we have:

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

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 3077: Shuffling series schedules 

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

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

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

    [teaser3077]

     
    • Jim Randell's avatar

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

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

      It runs in 56ms.

      Run: [ @replit ]

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

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

      The two schedules are:

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


      Manually:

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

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

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

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

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

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

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

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

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

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

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

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

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

      So, there is only one possibility left:

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

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

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

      Like

      • Frits's avatar

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

        @Jim

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

        Like

        • Jim Randell's avatar

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

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

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

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

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

          Like

  • Unknown's avatar

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

    Brain-Teaser 859: Sick transit 

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

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

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

    thus symbolising the stark alternatives facing the nation.

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

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

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

    What are the two answers ?

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

    [teaser859]

     
    • Jim Randell's avatar

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

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

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

      The following Python program runs in 51ms.

      Run: [ @replit ]

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

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

      The digits from 0 to 11 are represented by:

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

      So the sums are:

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

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

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

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

      And the sums are:

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

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 2820: Three ages 

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

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

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

    As published there are 2 possible solutions to this puzzle.

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

    [teaser2820]

     
    • Jim Randell's avatar

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

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

      The following run file executes in 187ms.

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

      And this finds two viable solutions.

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

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

      Run: [ @replit ]

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

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

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

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

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

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

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

      Like

    • GeoffR's avatar

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

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

      Like

      • Frits's avatar

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

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

        Like

        • Jim Randell's avatar

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

          Or (still assuming 2-digit ages):

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

          or:

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

          Like

      • Frits's avatar

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

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

        Like

      • Frits's avatar

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

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

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

        Like

        • Jim Randell's avatar

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

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

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

          Like

          • Frits's avatar

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

            @Jim, very concise.

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

            Like

          • Frits's avatar

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

            @Jim,

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

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

            Like

            • Jim Randell's avatar

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

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

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

              Like

          • Jim Randell's avatar

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

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

            For example, this program becomes:

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

            Like

    • Frits's avatar

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

      @Jim, Thanks, I will look into it.

      Calculating rC after A is known is faster:

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

      Like

    • Frits's avatar

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

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

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

      Like

    • GeoffR's avatar

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

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

      Like

  • Unknown's avatar

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

    Teaser 3076: Bee lines 

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

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

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

    [teaser3076]

     
    • Jim Randell's avatar

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

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

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

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

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

      Run: [ @replit ]

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

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

      Solution: The sum of the distances is 397 cm.


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

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

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

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

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

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

      We find there are three viable boxes for this method:

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

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

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

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

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

      This method provides another two viable boxes:

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

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

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

      Like

      • Frits's avatar

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

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

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

        Like

        • Jim Randell's avatar

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

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

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

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

          Like

          • Jim Randell's avatar

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

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

            Like

      • Jim Randell's avatar

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

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

        Run: [ @replit ]

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

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

        This is probably the scenario intended by the setter.

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

        Like

        • Jim Randell's avatar

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

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

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

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

          Like

    • Brian Gladman's avatar

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

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

      Like

    • Frits's avatar

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

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

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

      Like

    • GeoffR's avatar

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

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

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

      Like

  • Unknown's avatar

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

    Brain-Teaser 848: The magic class 

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

    The class only had nine pupils.

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

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

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

    Three was the lowest mark awarded to a girl.

    What was the highest mark given to a boy?

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

    [teaser848]

     
    • Jim Randell's avatar

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

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

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

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

      The following run file executes in 70ms.

      Run: [ @replit ]

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

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

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

      Like

    • GeoffR's avatar

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

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

      Like

    • Frits's avatar

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

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 2815: PIN-in-the-middle 

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

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

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

    [teaser2815]

     
    • Jim Randell's avatar

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

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

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

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

      This Python program runs in 59ms.

      Run: [ @replit ]

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

      Solution: The PIN in the middle is 2592.

      And here is a complete grid:

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

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

      Like

    • Frits's avatar

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

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

      Like

  • Unknown's avatar

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

    Brain-Teaser 42: [Pounds and pence] 

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

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

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

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

    “Three what?” I asked.

    “Pounds, of course”, he replied.

    How many beans make five?

    This puzzle was originally published with no title.

    [teaser42]

     
    • Jim Randell's avatar

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

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

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

      Untangling the facts we are given:

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

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

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

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

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

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

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

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

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

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

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

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

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

      “two dozen carrots make three [lbs]”

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

      This leaves us with:

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

      We can break this down into:

      X = 7A + B + C

      where:

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

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

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

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

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

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

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

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

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

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

      Hence:

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

      Solution: There are 240 beans in 5 lb.

      We can summarise the calculated information as:

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

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

      Like

  • Unknown's avatar

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

    Teaser 3075: Prime cuts for dinner 

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

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

    What was the larger of the two prime numbers?

    [teaser3075]

     
    • Jim Randell's avatar

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

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

      It runs in 234ms.

      Run: [ @replit ]

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

      Solution: The larger of the two primes was 109.


      There are 30 tickets:

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

      And 30 is the only number that satisfies the conditions.

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

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

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

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

      In each case the larger prime is 109.

      And there are many ways to achieve the same sums.

      Like

    • GeoffR's avatar

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

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

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

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

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

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

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

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

      Like

  • Unknown's avatar

    Jim Randell 9:54 am on 26 August 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 840: Cake mix variations 

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

    A small party was held to promote a brand of Cake Mix. There were five varieties of cake, five beverages, and five sorts of savouries offered.

    I asked a sample group of five what they had chosen, and learned that each had had a different drink, a different slice of cake and two savouries.

    No one had picked the same two savouries as any other, and no more than two people had the same savoury.

    No one had cake of the same flavour as her beverage (tea being paired with plain sponge in this case).

    Dot told me that she had no coffee in any form, no cheese and no egg, but she did have a sausage roll and one thing with orange flavour.

    Eve had lemon drink, but no egg, and said that the one who had both egg and cheese did not drink coffee, but the tea drinker had cheese nibbles.

    Fran said the one who drank chocolate had lemon cake. Fran had shrimp vol-au-vent, and only one of the shrimp eaters had lemon in either form.

    Gill had a sausage roll and one orange item, and said that the one who had cheese had chocolate cake.

    Helen told me that the one who had coffee cake had a ham sandwich, but no cheese, and no one had stuffed egg as well as shrimp.

    Who had the ham sandwiches ?

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

    [teaser840]

     
    • Jim Randell's avatar

      Jim Randell 9:55 am on 26 August 2021 Permalink | Reply

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

      It runs in 81ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      # assign values to the names:
      #   Dot = 1; Eve = 2; Fran = 3; Gill = 4; Helen = 5
      #
      # and then assign these values to the comestibles:
      #   cakes: sponge = A; coffee = B; orange = C; lemon = D; chocolate = E
      #   drinks: tea = F; coffee = G; orange = H; lemon = I; chocolate = J
      #   savouries: cheese = K & L; egg = M & N; s roll = P & Q; shrimp = R & S; ham s = T & U
      
      SubstitutedExpression
      
      --digits="1-5"
      --distinct="ABCDE,FGHIJ,KMPRT,LNQSU,AF,BG,CH,DI,EJ,KL,MN,PQ,RS,TU"
      
      # remove duplicate solutions
      "T < U"
      
      # "Dot has no coffee, no cheese, no egg"
      "B != 1"
      "G != 1"
      "K != 1"
      "L != 1"
      "M != 1"
      "N != 1"
      
      # "Dot has a sausage roll, and something orange"
      "P == 1 or Q == 1"
      "C == 1 or H == 1"
      
      # "Eve had lemon drink"
      "I == 2"
      
      # "Eve had no egg"
      "M != 2"
      "N != 2"
      
      # "someone had egg, cheese and not coffee"
      "((M == K or M == L) and G != M) or ((N == K or N == L) and G != N)"
      
      # "tea drinker had cheese"
      "F == K or F == L"
      
      # "hot chocolate had lemon cake"
      "J == D"
      
      # "Fran had shrimp"
      "R == 3 or S == 3"
      
      # "only one shrimp eater also had lemon"
      "(R == D or R == I) + (S == D or S == I) = 1"
      
      # "Gill had sausage roll and something orange"
      "P == 4 or Q == 4"
      "C == 4 or H == 4"
      
      # "someone had cheese and chocolate cake"
      "(K == E) or (L == E)"
      
      # "coffee cake had a ham sandwich, but no cheese"
      "B == T or B == U"
      "B != K and B != L"
      
      # "no-one had egg and shrimp"
      "not intersect(([M, N], [R, S]))"
      
      # mention A
      "A > 0"
      
      # [optional]
      --template="(A B C D E) (F G H I J) (K L) (M N) (P Q) (R S) (T U)"
      --solution=""
      --denest=-1  # apply fix for CPython
      

      Solution: Dot and Eve had the ham sandwiches.

      The full solution is:

      D: Sponge cake; orange juice; sausage roll + ham sandwich
      E: Coffee cake; lemon squash; shrimp + ham sandwich
      F: Chocolate cake; tea; cheese + shrimp
      G: Orange cake; coffee; egg + sausage roll
      H: Lemon cake; hot chocolate; cheese + egg

      Like

  • Unknown's avatar

    Jim Randell 12:00 pm on 24 August 2021 Permalink | Reply
    Tags:   

    Teaser 2813: Katy’s piggy banks 

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

    Our granddaughter Katy has two piggy banks containing a mixture of 10p and 50p coins, making a grand total of 10 pounds. The first piggy bank contains less than the other, and in it the number of 50p coins is a multiple of the number of 10p coins.

    Katy chose a coin at random from the first piggy bank and then put it in the second. Then she chose a coin at random from the second and put it in the first. She ended up with the same amount of money in the first piggy bank as when she started. In fact there was a 50:50 chance of this happening.

    How much did she end up with in the first piggy bank?

    [teaser2813]

     
    • Jim Randell's avatar

      Jim Randell 12:01 pm on 24 August 2021 Permalink | Reply

      The following Python program all possible distributions of coins between the two piggy banks. It runs in 57ms.

      Run: [ @replit ]

      from enigma import (Rational, irange, express, div, printf)
      
      Q = Rational()
      
      # consider the amount in the first piggy bank (less than 5 pounds)
      for s1 in irange(0, 490, step=10):
        for (t1, f1) in express(s1, [10, 50]):
          # f1 is a (proper) multiple of t1
          d = div(f1, t1)
          if d is None or d < 2: continue
          n1 = f1 + t1
      
          # and the amount in the second piggy bank
          s2 = 1000 - s1
          for (t2, f2) in express(s2, [10, 50]):
            n2 = f2 + t2
      
            # probability of withdrawing a 50p from both banks
            p1 = Q(f1, n1) * Q(f2 + 1, n2 + 1)
      
            # probability of withdrawing a 10p from both banks
            p2 = Q(t1, n1) * Q(t2 + 1, n2 + 1)
      
            # together they should make 1/2
            if p1 + p2 == Q(1, 2):
              printf("s1={s1} ({f1}, {t1}), s2={s2} ({f2}, {t2}) [p1 = {p1}, p2 = {p2}]")
      

      Solution: Katy ended up with £3.20 in the first piggy bank.

      The first piggy bank had £3.20 in it (2× 10p + 6× 50p; and 6 is a proper multiple of 2).

      The second piggy bank had the £6.80 in it (13× 10p + 11× 50p).

      We assume the two denominations of coin are equally likely to be chosen.

      The same denomination must be chosen in both cases, so the probability of transferring a 50p from the first bank to the second is: 6/8 = 3/4.

      And there are now 12× 50p coins and 13× 50p coins in the second piggy bank, so the probability of transferring a 50p from the second bank back to the first is: 12/25.

      p1 = (3/4)(12/25) = 9/25

      Similarly the probability of moving a 10p back and forth is:

      p2 = (1/4)(14/25) = 7/50

      Adding these together gives the total probability of the same denomination coin moving back and forth:

      p1 + p2 = 9/25 + 7/50 = 1/2

      Like

    • John Crabtree's avatar

      John Crabtree 4:57 am on 27 August 2021 Permalink | Reply

      Let the total in the first bag = 10a + 50ma
      Let the total in the second bag = 10b + 50c
      The probability condition leads to (m – 1)(b – c – 1) = 2, and so m = 2 or 3, and b – c + m = 5
      This leads to c = 16 – ma + (a + 1)(m – 1)/6
      a < 5 and so m = 3 and a = 2.

      Like

    • Linda's avatar

      Linda 11:27 am on 27 January 2023 Permalink | Reply

      This is a brilliant problem

      Like

  • Unknown's avatar

    Jim Randell 9:23 am on 22 August 2021 Permalink | Reply
    Tags: by: R. Skeffington Quinn   

    Brain-Teaser 15: Bumper’s final ball 

    From The Sunday Times, 11th June 1961 [link]

    It is a lovely evening in August, and the final Test of the 1990 series has reached its climax. The rubber stands at two games each, the last ball of the last over of the match is now to come. Australia are batting and need six runs for victory. Whacker, their wicket-keeper, the last man in, has taken his stand. with his captain’s words ringing in his ears, “Six or bust!”.

    Bumper, the England bowler, has just taken the previous two wickets in succession, With a dim opinion of Whacker’s batting, he feels sure that a fast straight one will not only give England the Ashes, but will give him his hat-trick and his seventh wicket of the match.

    In a breathless hush he delivers a  fast straight one. Striding forward like a Titan, Whacker mows …

    The records of this match are scanty. The Australian total in two innings was 490. Except for one man run out in the first innings, their casualties fell to bowlers. The five England bowlers had averages of 14, 20, 25 (Bumper), 33, 43, each with a differing number of wickets.

    How many wickets did each take and who won the Ashes?

    This is another puzzle that I originally couldn’t find, but with a bit more searching of the archive I was able to unearth it.

    This puzzle completes the archive of Teaser puzzles from 1961 (when the first numbered puzzles appeared).

    This is the first of the numbered Teaser puzzles set by Charles Skeffington Quin (although it is credited to R. Skeffington Quinn), and was re-published as Brainteaser 1093 on 17th July 1983 to celebrate Mr Skeffington Quin’s 100th birthday.

    [teaser15]

     
    • Jim Randell's avatar

      Jim Randell 9:24 am on 22 August 2021 Permalink | Reply

      I think you need to something about cricket to solve this puzzle. Unfortunately I am not an expert in cricket, but here goes…

      We need to know the number of Australian batmen dismissed by the bowlers, in both innings. In the first innings, all 10 of the batsmen will have been dismissed, but one was run out, so only 9 were bowled. In the second innings, if Australia win, only 9 of the batsmen will have been dismissed (and Bumper will have taken 6 wickets), however if Bumper bowls out the last man all 10 of them will have been dismissed (and Bumper will have taken 7 wickets).

      So we are interested in the situation where either 18 wickets are taken (Australia win, and Bumper takes 6 wickets) or where 19 wickets are taken (England win, and Bumper takes 7 wickets).

      This Python program runs in 55ms.

      Run: [ @replit ]

      from enigma import (express, all_different, printf)
      
      # the averages (which happen to be an increasing sequence)
      avgs = (14, 20, 25, 33, 43)
      
      # how can a total of 490 be achieved using these averages?
      for (a, b, c, d, e) in express(490, avgs, min_q=1):
        # each bowler took a different number of wickets
        if not all_different(a, b, c, d, e): continue
        # we are interested in totals of 18 or 19
        t = a + b + c + d + e
        if t == 18 and c == 6:
          win = "Australia"
        elif t == 19 and c == 7:
          win = "England"
        else:
          continue
        # output solution
        (A, B, C, D, E) = (x * y for (x, y) in zip(avgs, (a, b, c, d, e)))
        printf("{a} for {A}, {b} for {B}, {c} for {C}, {d} for {D}, {e} for {E}; {win} win")
      

      Solution: The number of wickets taken by each bowler are: 5, 2, 7 (Bumper), 1, 4. England won.

      Like

  • Unknown's avatar

    Jim Randell 5:45 pm on 20 August 2021 Permalink | Reply
    Tags:   

    Teaser 3074: Timely overthrows 

    From The Sunday Times, 22nd August 2021 [link] [link]

    Without changing their size, Judith sews together one-foot squares of different colours that her mother has knitted, to make rectangular throws. These are usually all of the same dimensions using fewer than a hundred squares. She has observed that it takes her mother 20 per cent longer to knit each square than it takes her to sew two single squares together.

    As a one-off she has completed a square throw whose sides have the same number of squares as the longer side of her usual rectangular throws. The average time it took per square foot, both knitting and sewing, to complete the square throw was 2 per cent longer than that of the rectangular throws.

    What are the dimensions (in feet) of the rectangular throws?

    [teaser3074]

     
    • Jim Randell's avatar

      Jim Randell 6:19 pm on 20 August 2021 Permalink | Reply

      For a throw of dimensions (x, y) there are xy squares, each of which takes 6 units of time to knit.

      Each square has 4 edges, so there are 4xy edges, but the edges on the perimeter are not sewn together, so there are a total of 4xy − 2(x + y) edges that are sewn together, in pairs. And each pair take 5 units of time to sew together.

      Giving a total time for the throw of:

      time(x, y) = 6xy + 5(4xy − 2(x + y))/2
      time(x, y) = 6xy + 5(2xy − x − y)

      This Python program runs in 53ms.

      Run: [ @replit ]

      from enigma import irange, divisors_pairs, printf
      
      # total time for a throw measuring (x, y)
      time = lambda x, y: 6 * x * y + 5 * (2 * x * y - x - y)
      
      # consider the dimensions (x, y) of a "standard" throw (area < 100)
      for a in irange(1, 99):
        for (x, y) in divisors_pairs(a):
          if x == y: continue
          # total time involved
          t = time(x, y)
      
          # consider the time for a square (y, y) throw
          t2 = time(y, y)
          # the average time per square should be 2% longer
          # i.e. t2 / y^2 = 1.02(t / xy) => 100 x t2 = 102 y t
          if 100 * x * t2 == 102 * y * t:
            printf("a={a}; x={x} y={y} t={t}; t2={t2}")
      

      Solution: The standard throw is 5 ft × 7 ft.

      It has an area of 35 sq ft, and takes 500 time units to construct. i.e. 100/7 time units per square.

      A 7 ft × 7 ft throw has an area 49 sq ft, and takes 714 time units to construct. i.e. 102/7 time units per square.

      And we can easily see that this average is 1.02 times the average for a standard throw.

      Liked by 1 person

      • Jim Randell's avatar

        Jim Randell 6:39 pm on 20 August 2021 Permalink | Reply

        Solving the equation for the average time per square gives us:

        255y − 245x − 16xy = 0
        y = 245x / (255 − 16x)

        from enigma import irange, div, printf
        
        # calculate the dimension of a standard (x, y) throw
        for x in irange(1, 7):
          y = div(245 * x, 255 - 16 * x)
          if y is not None:
            printf("x={x} y={y}")
        

        Like

    • GeoffR's avatar

      GeoffR 11:45 am on 22 August 2021 Permalink | Reply

      I found one other answer, much larger than the puzzle answer, which satisfies Jim’s equation:
      255y – 245x – 16xy = 0.

      It is x = 15, y = 245.

      Like

      • Jim Randell's avatar

        Jim Randell 1:00 pm on 22 August 2021 Permalink | Reply

        And (5, 7), (15, 245) are the only (x, y) solutions in positive integers. (Although x = 7 is very close). At x ≥ 16, y becomes negative.

        Like

    • GeoffR's avatar

      GeoffR 2:41 pm on 23 August 2021 Permalink | Reply

      % A Solution in MiniZinc
      
      var 2..10:X;  % horizontal
      var 2..50:Y;  % vertical
      
      constraint Y > X;
      
      % Number of sewn edges = (Y - 1).X + (X - 1).Y
      % Area of rectangular throw = X * Y
      % Time to make throw and sew throw edges = 
      % 6.X.Y + 5.( (Y-1).X + (X-1).Y )
      
      % Square throw unit time = 51/50 * Rectangle throw unit time
      constraint (6*X*Y + 5*( (Y - 1)*X + (X - 1)*Y ) ) * 51 * Y * Y
      == (6*Y*Y + 10*Y*(Y - 1)) * 50 * X * Y;
      
      solve satisfy;
      
      output ["Throw dimensions (ft) are X = " ++ show(X) 
      ++ ", " ++ "Y = " ++ show(Y)];
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 1:45 pm on 17 August 2021 Permalink | Reply
    Tags:   

    Teaser 2811: Making arrangements 

    From The Sunday Times, 7th August 2016 [link] [link]

    Beth wrote down a three-figure number and she also listed the five other three-figure numbers that could be made using those same three digits. Then she added up the six numbers: it gave a total whose digits were all different, and none of those digits appeared in her original number.

    If you knew whether her original number was prime or not, and you knew whether the sum of the three digits of her original number was prime or not, then it would be possible to work out her number.

    What was it?

    [teaser2811]

     
    • Jim Randell's avatar

      Jim Randell 1:47 pm on 17 August 2021 Permalink | Reply

      If the number is ABC, then in order for the different orderings of (A, B, C) to make 6 different numbers A, B, C must all be distinct and non-zero. Then we have:

      ABC + ACB + BAC + BCA + CAB + CBA = PQRS
      ⇒ 222 × (A + B + C) = PQRS

      We can use two of the routines from the enigma.py library to solve this puzzle. [[ SubstitutedExpression() ]] to solve the alphametic problem, and [[ filter_unique() ]] to find the required unique solutions.

      The following Python program runs in 53ms.

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, filter_unique, unpack, printf)
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        ["222 * (A + B + C) = PQRS"],
        d2i={ 0: 'ABCP' },
        answer="(ABC, is_prime(ABC), is_prime(A + B + C))",
      )
      
      # if you knew (f1, f2) you could deduce n
      rs = filter_unique(
        p.answers(verbose=0),
        unpack(lambda n, f1, f2: (f1, f2)),
        unpack(lambda n, f1, f2: n),
      ).unique
      
      # output solution
      for (n, f1, f2) in rs:
        printf("number = {n} (prime = {f1}; dsum prime = {f2})")
      

      Solution: Beth’s number was 257.

      The only values for (A, B, C) that work are (2, 5, 7) and (3, 7, 9). These can be assembled in any order to give an original number that works.

      Of these 257 is the only arrangement that gives a prime number, with a digit sum that is not prime.

      Like

    • GeoffR's avatar

      GeoffR 9:39 am on 18 August 2021 Permalink | Reply

      I used four lists for the two primality tests for each potential 3-digit answer. The list with a single entry was Beth’s number.

      from itertools import product
      from enigma import is_prime
      
      # Four lists for primality tests
      TT, FT, FF, TF = [], [], [], []
      
      # Form six 3-digit numbers - Beth's number was abc
      for a,b,c in product(range(1,10), repeat=3):
        abc, acb = 100*a + 10*b + c, 100*a + 10*c + b
        bac, bca = 100*b + 10*a + c, 100*b + 10*c + a
        cab, cba = 100*c + 10*a + b, 100*c + 10*b + a
        pqrs = abc + acb + bac + bca + cab + cba
        # find four digits of the sum of six numbers
        p, q = pqrs//1000, pqrs//100 % 10
        r, s = pqrs//10 % 10, pqrs % 10
        
        # all seven digits are different
        if len(set((a, b, c, p, q, r, s))) == 7:
          
          # check the primality of number abc
          # and primality of the sum of its digits
          if is_prime(abc) and is_prime(a+b+c):
            TT.append(abc)  # true/true
            
          if not(is_prime(abc)) and is_prime(a+b+c):
            FT.append(abc)  # false/true
            
          if not(is_prime(abc)) and not(is_prime(a+b+c)):
            FF.append(abc)  # false/false
            
          if is_prime(abc) and not(is_prime(a+b+c)):
            TF.append(abc)  # true/false
      
      # find a single number in the four lists
      for L in (TT, FT, FF, TF):
        if len(L) == 1:
          print(f"Beth's number was {L[0]}.")
          
      print("True/True numbers = ", TT)
      print("False/True numbers = ", FT)
      print("False/False numbers = ", FF)
      print("True/False numbers = ", TF)
          
      # Beth's number was 257.
      # True/True numbers =  [379, 397, 739, 937]
      # False/True numbers =  [793, 973]
      # False/False numbers =  [275, 527, 572, 725, 752]
      # True/False numbers =  [257]
      
      

      Like

    • Frits's avatar

      Frits 3:02 pm on 18 August 2021 Permalink | Reply

        
      from itertools import permutations as perm
      from functools import reduce
      from enigma import is_prime
      
      # convert digits sequence to number
      d2n = lambda s: reduce(lambda x, y: 10 * x + y, s)
      
      # decompose number <t> into <k> increasing numbers from <ns>
      # so that sum(<k> numbers) equals <t>
      def decompose(t, k, ns, s=[]):
        if k == 1:
          if t in ns and t > s[-1]:
            yield s + [t]
        else:
          for n in ns:
            if s and n <= s[-1]: continue
            yield from decompose(t - n, k - 1, ns, s + [n])
      
      # 5 < a + b + c < 25
      for sumabc in range(6, 25):
        # pqrs = abc + acb + bac + bca + cab + cba
        pqrs = str(222 * sumabc)   
        
        # skip if duplicate digits
        if len(set(pqrs)) != len(pqrs): continue
        
        # determine minimum and maximum for a, b, c
        mi = max(1, sumabc - 9 - 8)
        ma = min(9, sumabc - 1 - 2)
        
        # determine digits not used in sum (must be valid for a, b, c)
        missing = [x for x in range(1, 10) if str(x) not in pqrs and mi <= x <= ma]
        if len(missing) < 3: continue
        
        # check if 3 digits of missing sum to <sumabc>
        for d in decompose(sumabc, 3, missing):
          if not is_prime(sumabc): 
            # check which permutatons of a, b, c are prime
            primes = [n for p in perm(d) if is_prime(n := d2n(p))]
            if len(primes) == 1:
              print("answer:", primes[0])
          else:     # sumabc is prime    
            # check which permutatons of a, b, c are non-prime
            nprimes = [n for p in perm(d) if not is_prime(n := d2n(p))]
            if len(nprimes) == 1:
              print("answer:", nprimes[0]) 
      

      Like

    • GeoffR's avatar

      GeoffR 3:08 pm on 19 August 2021 Permalink | Reply

      An easy solution with standard MiniZinc, although the answer needs to be interpreted from multiple output configuration.

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9: A; var 1..9: B; var 1..9: C; 
      var 1..9: P; var 0..9: Q; var 0..9: R; var 0..9: S; 
      
      constraint all_different([A, B, C, P, Q, R, S]);
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      var 100..999: ABC = 100*A + 10*B + C;
      var 100..999: ACB = 100*A + 10*C + B;
      var 100..999: BAC = 100*B + 10*A + C;
      var 100..999: BCA = 100*B + 10*C + A;
      var 100..999: CAB = 100*C + 10*A + B;
      var 100..999: CBA = 100*C + 10*B + A;
      var 1000..9999: PQRS = 1000*P + 100*Q + 10*R + S;
      
      var 6..24: sum_ABC = A + B + C;
      
      constraint ABC + ACB + BAC + BCA + CAB + CBA == PQRS;
      
      solve satisfy;
      
      output [ "[ABC, sum_ABC] = " ++ 
      show([ABC, is_prime(ABC), sum_ABC, is_prime(sum_ABC) ]) ];
      
      % key: 0 = not prime, 1 = prime
      
      % [ABC, sum_ABC] = [752, 0, 14, 0]
      % ----------
      % [ABC, sum_ABC] = [572, 0, 14, 0]
      % ----------
      % [ABC, sum_ABC] = [973, 0, 19, 1]
      % ----------
      % [ABC, sum_ABC] = [793, 0, 19, 1]
      % ----------
      % [ABC, sum_ABC] = [725, 0, 14, 0]
      % ----------
      % [ABC, sum_ABC] = [275, 0, 14, 0]
      % ----------
      % [ABC, sum_ABC] = [527, 0, 14, 0]
      % ----------
      % [ABC, sum_ABC] = [937, 1, 19, 1]
      % ----------
      % *** unique prime/non-prime test for ABC ***
      % [ABC, sum_ABC] = [257, 1, 14, 0] <<<
      % ----------
      % [ABC, sum_ABC] = [397, 1, 19, 1]
      % ----------
      % [ABC, sum_ABC] = [739, 1, 19, 1]
      % ----------
      % [ABC, sum_ABC] = [379, 1, 19, 1]
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 6:02 pm on 15 August 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 835: Traffic light entertainment 

    From The Sunday Times, 17th July 1977 [link]

    Newtown High Street has a linked traffic light system which consists of six consecutive sections, each a multiple of one-eighth of a mile, and each terminating in a traffic light.

    The lights are synchronised such that a vehicle travelling at 30 mph will pass each light at the same point in its 26-second operating cycle.

    This cycle can be considered as 13 seconds “stop” (red) and 13 seconds “go”(green).

    Charlie had studied the system and reckoned he could drive much faster than 30 mph and still get through the system without crossing a red light.

    In an experiment Alf, Bert and Charlie entered the first section at the same instant, travelling at 30, 50 and 75 mph respectively, with the first light turning green three seconds later.

    Charlie got through the whole system in less than two minutes without being stopped.

    “I was lucky though”, said Charlie. “I arrived at the last light just as it changed”.

    “My luck was non-existent”, said Bert. “I ran out of petrol after the third light and in any case would have been stopped at the second light had I not lost 10 seconds due to a delay in the second section!”

    What were the lengths of each section in order from the first?

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

    [teaser835]

     
    • Jim Randell's avatar

      Jim Randell 6:03 pm on 15 August 2021 Permalink | Reply

      (See also: Enigma 198).

      Using distance units of eighths of a mile. We see that the final light must be less than 20 units away from the start, and the time to travel each unit is:

      A: 30mph = 1 unit in 15s
      B: 50mph = 1 unit in 9s
      C: 75mph = 1 unit in 6s

      And the following conditions hold at the lights:

      A: makes it through all lights at green (and at the same point in the 26s cycle)
      B: makes it through light 1; but is stopped at light 2, however …
      B + 10s: makes it through lights 2 and 3
      C: makes it through all lights at green, but hits light 6 as it is changing.

      The following Python 3 program runs in 50ms.

      Run: [ @replit ]

      from enigma import irange, printf
      
      # 0 - 13 = green, 14 - 25 = red
      def green(t, t0):
        return (t - t0) % 26 < 14
      
      # solve starting with light k
      # k = index of next light
      # ds = length of light controlled sections
      # ts = start time of light "go" period
      def solve(k, ds, ts, A=0, B=0, C=0):
        # calculate times at the current light
        d = ds[-1]
        t = ts[-1]
        A += d * 15
        B += d * 9
        C += d * 6
      
        # at all lights, A and C get green lights 
        if not(green(A, t) and green(C, t)): return
        # at the first light...
        if k == 1:
          # B made it through
          if not(green(B, t)): return
        # at the second light ...
        elif k == 2:
          # B would have been stopped ...
          if green(B, t): return
          # but he arrived 10s late
          if not green(B + 10, t): return
        # at the third light ...
        elif k == 3:
          # B passed at green, 10s late
          if not green(B + 10, t): return
        # at the sixth light ...
        elif k == 6:
          # C arrived just as they changed
          if (C - t) % 13 != 0: return
      
        # are we done?
        if k == 6:
          # return the distances
          yield ds
        else:
          # move on to the next light
          for x in irange(1, 14 + k - sum(ds)):
            for z in solve(k + 1, ds + [x], ts + [t + 15 * x], A, B, C): yield z
      
      # consider distance to light 1
      for d in irange(1, 14):
        # solve the puzzle (first light goes green at t=3)
        for ds in solve(1, [d], [3]):
          printf("distances = {ds}")
      

      Solution: The lengths of the sections (in miles) are: 1st = 1/8; 2nd = 1/4; 3rd = 3/8; 4th = 1/8; 5th = 1/4; 6th = 1/8.

      The total length of the sections is 10/8 miles (= 1.25 miles), so C completes the course in 60s (= 1m) and A in 150s (= 2m30s).

      Here is a diagram showing the progress of A, B and C:

      A arrives at each light just before it changes to red. And C ends his journey by heading towards a red light at 75mph, which (fortunately) changes to green just as he reaches it.

      Like

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel
Design a site like this with WordPress.com
Get started