Recent Updates Page 39 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 1:17 pm on 10 December 2021 Permalink | Reply
    Tags:   

    Teaser 3090: Main line 

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

    Anton and Boris live next to a railway line. One morning a goods train passed Anton’s house travelling south just as a slower train passed Boris’s house travelling north. The goods train passed Boris’s house at the same time as a passenger train, heading north at a speed that was half as fast again as the goods train. Similarly, as the slower train passed Anton’s house it passed a passenger train; this was heading south at a speed that was three times as great as that of the slower train.

    The passenger trains then passed each other at a point 25 kilometres from Anton’s house before simultaneously passing the two houses.

    All four trains travelled along the same route and kept to their own constant speeds.

    How far apart do Anton and Boris live?

    [teaser3090]

     
    • Jim Randell's avatar

      Jim Randell 1:38 pm on 10 December 2021 Permalink | Reply

      This is an exercise is generating and solving simultaneous equations. No programming necessary.

      If we suppose B lives a distance d from A.

      Initially (at time 0) if the goods train passes A travelling south at speed 2v, then it reaches B at a time of (d / 2v).

      At this time, the passenger train, with a speed of 3v passes B, heading north.

      And the slow train, travelling at speed 2fv (i.e. some fraction of v), reaches A at a time of (d / 2fv).

      And at this time a train travelling at 6fv passes A heading south.

      These trains pass at time t1 at a point 25 km south of A:

      t1 = (d / 2fv) + (25 / 6fv) = (3d + 25) / 6fv
      t1 = (d / 2v) + ((d − 25) / 3v) = (5d − 50) / 6v
      ⇒ f = (3d + 25) / (5d − 50)

      And then at time (t1 + t2) the two trains pass A and B:

      t2 = 25 / 3v
      t2 = (d − 25) / 6fv
      ⇒ f = (d − 25) / 50

      Equating these:

      (3d + 25) / (5d − 50) = (d − 25) / 50
      10(3d + 25) = (d − 25)(d − 10)
      30d + 250 = d² − 35d + 250
      d² − 65d = 0
      ⇒ d = 65

      So A and B are 65 km apart, and f = 4/5.

      We are not given any times, so we cannot determine the actual speeds of the trains.

      Solution: Anton and Boris live 65 km apart.

      Like

  • Unknown's avatar

    Jim Randell 11:27 am on 9 December 2021 Permalink | Reply
    Tags:   

    Teaser 2843: Child’s play 

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

    Liam has a set of cards numbered from one to twelve. He can lay some or all of these in a row to form various numbers. For example the four cards:

    6, 8, 11, 2

    give the five-figure number 68112. Also, that particular number is exactly divisible by the number on each of the cards used.

    In this way Liam used his set of cards to find another much larger number that was divisible by each of the numbers on the cards used — in fact he found the largest such possible number.

    What was that number?

    [teaser2843]

     
    • Jim Randell's avatar

      Jim Randell 11:29 am on 9 December 2021 Permalink | Reply

      To reduce the search space we can use some shortcuts.

      Considering the cards that are included in the number, if the “10” card is included, then it would have to appear at the end, and would preclude the use of cards “4”, “8”, “12”.

      Similarly if “5” is included, and any even card, then the number must end in “10” and the cards “4”, “8”, “12” are excluded.

      If any of the cards “3”, “6”, “9”, “12” are included then the overall digit sum must be a multiple of 3.

      Similarly if “9” is included, then the overall digit sum must be a multiple of 9.

      If any of the even cards (“2”, “4”, “6”, “8”, “10”) are used, the resulting number must also be even.

      This Python program uses a number of shortcuts. It considers the total number of digits in the number, as once a number is found we don’t need to consider any number with fewer digits. It runs in 263ms.

      Run: [ @replit ]

      from enigma import (enigma, irange, group, subsets, Accumulator, join, printf)
      
      # the cards
      cards = list(irange(1, 12))
      
      # map cards to their digit sum, and number of digits
      dsum = dict((x, enigma.dsum(x)) for x in cards)
      ndigits = dict((x, enigma.ndigits(x)) for x in cards)
      
      # find maximum arrangement of cards in ss
      def find_max(ss):
        # look for impossible combinations
        if (10 in ss) and (4 in ss or 8 in ss or 12 in ss): return
        even = any(x % 2 == 0 for x in ss)
        if (5 in ss and even) and (4 in ss or 8 in ss or 12 in ss): return
        # calculate the sum of the digits
        ds = sum(dsum[x] for x in ss)
        # check for divisibility by 9
        if (9 in ss) and ds % 9 != 0: return
        # check for divisibility by 3
        if (3 in ss or 6 in ss or 12 in ss) and ds % 3 != 0: return
        # look for max rearrangement
        r = Accumulator(fn=max)
        fn = (lambda x: (lambda x: (-len(x), x))(str(x)))
        for s in subsets(sorted(ss, key=fn, reverse=1), size=len, select="P"):
          if even and s[-1] % 2 != 0: continue
          # done, if leading digits are less than current max
          if r.value and s[0] != r.data[0] and str(s[0]) < str(r.value): break
          # form the number
          n = int(join(s))
          if all(n % x == 0 for x in ss): r.accumulate_data(n, s)
        return (r.value, r.data)
      
      # sort subsets of cards into the total number of digits
      d = group(subsets(cards, min_size=1), by=(lambda ss: sum(ndigits[x] for x in ss)))
      
      r = Accumulator(fn=max)
      for k in sorted(d.keys(), reverse=1):
        printf("[considering {k} digits ...]")
      
        for ss in d[k]:
          rs = find_max(ss)
          if rs is None: continue
          r.accumulate_data(*rs)
      
        if r.value: break
      
      printf("max = {r.value} [{s}]", s=join(r.data, sep=","))
      

      Solution: The number is 987362411112.

      The cards are: (1, 2, 3, 4, 6, 7, 8, 9, 11, 12).

      Like

      • Frits's avatar

        Frits 6:52 pm on 9 December 2021 Permalink | Reply

        If “5” is included then the number must end in “10” or “5” and the cards “4”, “8”, “12” are excluded (not relying on an even card). I got this from Brian’s solution on his puzzle site.

        Like

        • Jim Randell's avatar

          Jim Randell 10:04 am on 10 December 2021 Permalink | Reply

          @Frits: Good point.

          So if “5” or “10” are used, none of “4”, “8”, “12” can be used. And vice versa, if “4”, “8” or “12” are used, none of “5” or “10” can be used. So at least 3 digits must be absent, and we can start the search from 12 digits.

          Here’s a neater version of my code:

          from enigma import (enigma, irange, group, subsets, mlcm, Accumulator, join, printf)
          
          # the cards
          cards = list(irange(1, 12))
          
          # map cards to their digit sum, and number of digits
          dsum = dict((x, enigma.dsum(x)) for x in cards)
          ndigits = dict((x, enigma.ndigits(x)) for x in cards)
          
          # find maximum arrangement of cards in ss
          def find_max(ss):
            d = mlcm(*ss)
            even = any(x % 2 == 0 for x in ss)
            # look for max rearrangement
            r = Accumulator(fn=max)
            fn = (lambda x: (lambda x: (-len(x), x))(str(x)))
            for s in subsets(sorted(ss, key=fn, reverse=1), size=len, select="P"):
              if even and s[-1] % 2 != 0: continue
              # done, if leading digits are less than current max
              if r.value and s[0] != r.data[0] and str(s[0]) < str(r.value): break
              # form the number
              n = int(join(s))
              if n % d == 0: r.accumulate_data(n, s)
            return (r.value, r.data)
          
          # reject impossible subsets
          def check(ss):
            # look for impossible combinations
            if (5 in ss or 10 in ss) and (4 in ss or 8 in ss or 12 in ss): return False
            # calculate the sum of the digits
            ds = sum(dsum[x] for x in ss)
            # check for divisibility by 9
            if (9 in ss) and ds % 9 != 0: return False
            # check for divisibility by 3
            if (3 in ss or 6 in ss or 12 in ss) and ds % 3 != 0: return False
            # looks OK
            return True
          
          # sort subsets of cards into the total number of digits
          d = group(subsets(cards, min_size=1), st=check, by=(lambda ss: sum(ndigits[x] for x in ss)))
          
          r = Accumulator(fn=max)
          for k in sorted(d.keys(), reverse=1):
            printf("[considering {k} digits ...]")
          
            for ss in d[k]:
              rs = find_max(ss)
              if rs is None: continue
              r.accumulate_data(*rs)
          
            if r.value: break
          
          printf("max = {r.value} [{s}]", s=join(r.data, sep=","))
          

          Like

    • GeoffR's avatar

      GeoffR 2:30 pm on 10 December 2021 Permalink | Reply

      In this first solution, I assumed it was reasonable for the largest number to start with the digits 9,8,7 to minimise a solution run-time. It also found the seven possible solutions for Liam’s numbers from which the maximum can be found.

      from enigma import Timer
      timer = Timer('ST2843', timer='E')
      timer.start()
      
      from itertools import permutations
      
      # assume number from cards is ABCDEFGHIJ, with A = 9, B = 8 and C = 7
      # Also no digit in A..J is 5 or 10 - see previous postings
      cards = set((1, 2, 3, 4, 6, 7, 8, 9, 11, 12))
      
      # Assume first three digits must be 9, 8 and 7 to maximise Liam's number
      A, B, C = 9, 8, 7
      a, b, c = str(A), str(B), str(C)
      
      cards2 = cards.difference([A, B, C])
      
      Liam_nums = []
      
      # permute remainder of cards
      for p1 in permutations(cards2, 7):
        D, E, F, G, H, I, J = p1
        d, e, f, g = str(D), str(E), str(F), str(G)
        h, i, j = str(H), str(I), str(J)
        num = int(a + b + c + d + e + f + g + h + i + j)
        if all(num % d == 0 for d in (A, B, C, D, E, F, G, H, I, J)):
          if num not in Liam_nums:
            Liam_nums.append(num)
      
      # find largest number in the list
      print (f"Largest possible number = { max(Liam_nums)}")
      timer.stop()      
      timer.report()
      
      # Largest possible number = 987362411112
      #[ST2843] total time: 0.0151168s (15.12ms)
      
      print(Liam_nums)
      # There are only seven possible numbers in Liam's list:
      # [987162411312, 987111312264, 987241136112, 987211614312,
      #  987341621112, 987362411112, 987113624112]
      
      
      

      In the second solution, I did a full permutation solution for the 12 cards, which had an expected longer run-time, as there were over 6,300 potential numbers in the list.

      
      from enigma import Timer
      timer = Timer('ST2843', timer='E')
      timer.start()
      
      from itertools import permutations
      
      # Assume digits 5 & 10 not included (previous postings)
      # Assume 10-digit number is ABCDEFGHIJ
      cards = set((1, 2, 3, 4, 6, 7, 8, 9, 11, 12))
      
      Liam_nums = []
      
      for p1 in permutations(cards):
        A, B, C, D, E, F, G, H, I, J = p1
        a, b, c = str(A), str(B), str(C)
        d, e, f, g = str(D), str(E), str(F), str(G)
        h, i, j = str(H), str(I), str(J)
        num = int(a + b + c + d + e + f + g + h + i + j)
        if all(num % d == 0 for d in (A, B, C, D, E, F, G, H, I, J)):
          if num not in Liam_nums:
            Liam_nums.append(num)
      
      # find largest number in the list
      print (f"Largest possible number = { max(Liam_nums)}")
      timer.stop()      
      timer.report()
      
      #Largest possible number = 987362411112
      #[ST2843] total time: 9.1977548s (9.20s)
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:00 am on 7 December 2021 Permalink | Reply
    Tags: by: Bryan Thwaites   

    Brain-Teaser 889: Counting the hours 

    From The Sunday Times, 20th August 1978 [link]

    At school the other day, little Johnny was working with one of those boards with twelve clock-points regularly spaced round a circle against which should be put twelve counters showing the hours.

    He was, in fact, in a bit of a daze about the whole thing and the only hour he was absolutely dead sure of was 12 whose counter he correctly placed. As for the eleven others, if the truth be told, he just put them around at random.

    But Jill, his teacher, spotted some curious things. She first noticed that, however she chose a quartet of counters which formed the corners of a square, their sum was always the same.

    Next, she saw that if she formed numbers by multiplying together the counters at the corners of each square, one of those numbers was more than six times one of the others.

    She also observed that the counters in each quartet were, starting at the lowest, in ascending order of magnitude in the clockwise direction, and that 12 was not the only correct counter.

    In break, she reported all this to her colleague, Mary, adding “If I were to tell you, in addition, how many hours apart from the 12 Johnny had got right, you could tell me which they were”.

    Which were they?

    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.

    [teaser889]

     
    • Jim Randell's avatar

      Jim Randell 10:01 am on 7 December 2021 Permalink | Reply

      There are three squares that can be constructed, using the numbers in positions (12, 3, 6, 9), (1, 4, 7, 10), (2, 5, 8, 11). These partition the 12 numbers into 3 sets of 4.

      The total sum of the numbers is T(12) = 78, so values for each set must sum to 78/3 = 26.

      The following Python program runs in 48ms. (Internal runtime is 680µs).

      Run: [ @replit ]

      from enigma import (irange, subsets, diff, ediv, tuples, multiply, cproduct, filter_unique, unpack, printf)
      
      # find viable layouts
      def generate():
        ns = list(irange(1, 12))
        T = sum(ns)
        t = ediv(T, 3)
      
        # choose 3 numbers to go with 12 (and they must be in ascending order)
        n12 = 12
        for (n3, n6, n9) in subsets(diff(ns, [n12]), size=3):
          s1 = (n3, n6, n9, n12)
          if sum(s1) != t: continue
          p1 = multiply(s1)
      
          # choose numbers for (1, 4, 7, 10)
          for s2 in subsets(diff(ns, s1), size=4):
            if sum(s2) != t: continue
            p2 = multiply(s2)
      
            # remaining numbers
            s3 = diff(ns, s1 + s2)
            p3 = multiply(s3)
      
            # one of the products must be more than 6 times one of the others
            if not (max(p1, p2, p3) > 6 * min(p1, p2, p3)): continue
      
            # assign s1 to (1, 4, 7, 10) and s2 to (2, 5, 8, 11)
            fn = lambda s: tuples(s, 4, circular=1)
            for ((n1, n4, n7, n10), (n2, n5, n8, n11)) in cproduct([fn(s2), fn(s3)]):
              # construct the arrangement
              vs = [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12]
              # find the numbers in the correct positions
              ss = tuple(n for (i, n) in enumerate(vs, start=1) if i == n)
              # there should be more than 1
              if len(ss) > 1:
                yield (vs, ss)
      
      # knowing len(ss) allows you to determine ss
      r = filter_unique(generate(), unpack(lambda vs, ss: len(ss)), unpack(lambda vs, ss: ss))
      
      # output solutions
      for (vs, ss) in r.unique:
        printf("{vs} -> {ss}")
      

      Solution: Johnny also placed 4 and 10 in the correct positions (as well as 12).

      There are two positions that lead to this:

      They both have squares with values (1, 2, 11, 12), (3, 4, 9, 10), (5, 6, 7, 8). But in the second one the (5, 6, 7, 8) square is rotated through 180°.

      The sums of the squares are all 26, and the products are (264, 1080, 1680), and 1680 > 1584 = 6 × 264.


      We find there are 14 possible arrangements that give the situation described.

      10 of them have 12 plus either 5, 7, or 8 in the correct position.

      2 of them have 12 plus (4, 5, 10) or (4, 8, 10) in the correct position.

      And the remaining two are those shown above with 12 plus (4, 10) in the correct position.

      Like

      • Frits's avatar

        Frits 9:22 am on 8 December 2021 Permalink | Reply

        @Jim, you also could have used decompose() to find 4 numbers which sum to “t”.

        Like

    • GeoffR's avatar

      GeoffR 7:50 pm on 7 December 2021 Permalink | Reply

      # four square group positions are (12,3,6,9), (1,4,7,10), (2,5,8,11)
      # sum of digits (1-12) = 78, so sum of each group is 26
      
      hours = set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
      H12 = 12   # set correct counter for Hour 12
      
      from itertools import permutations
      
      # 1st square group positions (12,3,6,9)
      for p1 in permutations(hours, 3):
        # counters for hours H3, H6 and H9
        H3, H6, H9 = p1  
        if H3 + H6 + H9 + H12 != 26:continue
        if not (H3 < H6 < H9 < H12):continue
        q1 = hours.difference([H3, H6, H9])
        
        # 2nd  square group positions (1,4,7,10)
        for p2 in permutations(q1, 4):
          # counters for hours H1, H4, H7, H9
          H1, H4, H7, H10 = p2
          if H1 +  H4 + H7 + H10 != 26:continue
          if not (H1 < H4 < H7 < H10):continue  
          q2 = hours.difference(p1).difference(p2)
          
          # 3rd square group positions (2,5,8,11)
          for p3 in permutations(q2,4):
            # counters for hours H2, H5, H8, H11
            H2, H5, H8, H11 = p3
            if H2 + H5 + H8 + H11 != 26:continue
            if not (H2 < H5 < H8 < H11):continue
            
            # find multiples of the corners of three groups
            m1 = H3 * H6 * H9 * H12
            m2 = H1 * H4 * H7 * H10
            m3 = H2 * H5 * H8 * H11
            m1, m3 = min([m1, m2, m3]), max([m1, m2, m3])
            if m1 < m2 < m3:
              # check one multiple is more than 6X another multiple
              if m2 > 6 * m1 or m3 > 6 * m1:
                print(f" Three products are {m1}, {m2}, and {m3}.")
                print(f" (H1, H2, H3) = ({H1}, {H2}, {H3})")
                print(f" (H4, H5, H6) = ({H4}, {H5}, {H6})")
                print(f" (H7, H8, H9) = ({H7}, {H8}, {H9})")
                print(f" (H10, H11, H12) = ({H10}, {H11}, {H12})")
      
      # Three products are 264, 1080, and 1680.
      # (H1, H2, H3) = (3, 5, 1)
      # (H4, H5, H6) = (4, 6, 2)
      # (H7, H8, H9) = (9, 7, 11)
      # (H10, H11, H12) = (10, 8, 12)
      # Only counters for hours 4, 10 and 12 are in the correct position.
      
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 9:21 am on 8 December 2021 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % sum of digits 1..12 = 78, sum of each of three squares = 26
      % counter was correctly placed for 12 o'clock
      int: H12 == 12; 
      
      % Hour values
      var 1..11: H1; var 1..11: H2; var 1..11: H3; var 1..11: H4;
      var 1..11: H5; var 1..11: H6; var 1..11: H7; var 1..11: H8;
      var 1..11: H9; var 1..11: H10; var 1..11: H11; 
      
      constraint all_different ([H1,H2,H3,H4,H5,H6,H7,H8,H9,H10,H11,H12]);
      
      % Corners sum for 1st square  (positions 12,3,6,9)
      constraint H12 + H3 + H6 + H9 == 26;
      constraint H3 < H6 /\ H6 < H9 /\ H9 < H12;
      
      % Corners sum for 2nd square  (positions 1,4,7,10)
      constraint H1 + H4 + H7 + H10 == 26;
      constraint H1 < H4 /\ H4 < H7 /\ H7 < H10;
      
      % Corners sum for 3rd square  (positions 2,5,8,11)
      constraint H2 + H5 + H8 + H11 == 26;
      constraint H2 < H5 /\ H5 < H8 /\ H8 < H11;
      
      % Multiples of corner values - (sum 4 corners = 26, av < 7),
      % LB = 1*2*3*4 = 24, UB approx 7^4 = 2401, say 2400 
      var 24..2400:m1; var 24..2400:m2; var 24..2400:m3; 
      
      constraint m1 == H3 * H6 * H9 * H12;
      constraint m2 == H1 * H4 * H7 * H10;
      constraint m3 == H2 * H5 * H8 * H11;
      
      constraint m1 < m2 /\ m2 < m3;
      % one of the multiples was more than six times one of the others
      constraint m2 > 6 * m1 \/ m3 > 6 * m1;
      
      solve satisfy;
      
      % H12 = 12 << Hour 12 correct - stated value
      
      % H1 = 3;
      % H2 = 5;
      % H3 = 1;
      % H4 = 4;   << Hour 4 correct
      % H5 = 6;
      % H6 = 2;
      % H7 = 9;
      % H8 = 7;
      % H9 = 11;
      % H10 = 10;  << Hour 10 correct
      % H11 = 8;
      % m1 = 264;
      % m2 = 1080;
      % m3 = 1680;
      % % time elapsed: 0.15 s
      % ----------
      % ==========
      % Finished in 182msec
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:57 am on 5 December 2021 Permalink | Reply
    Tags: by: Mr F. V. Rowden   

    Brain Teaser: The cottage 

    From The Sunday Times, 23rd December 1956 [link]

    A man sold his country cottage, receiving the proceeds in £5 notes, and decided to divide the money among his four sons. He first changed the money Into £1 notes. “To you, Arthur”, he said, “I will give one-quarter. You, Bernard, will take one-quarter of the remainder. Charles can have one-quarter of what is left, and David one-fourth of the rest. You will then each take one-quarter of the remainder”.

    It was found that before each successive sum could be divided exactly by four, it was necessary to remove one pound note, which the father put in his pocket £5 in all.

    How much did the cottage realise?

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961.

    [teaser-1956-12-23] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 8:58 am on 5 December 2021 Permalink | Reply

      This is a “monkey and coconuts” problem (see: Enigma 258, Puzzle #86).

      This program finds the first few possible solutions. The first of these is the only one that gives a viable price for the cottage (according to the published solution).

      Run: [ @replit ]

      from enigma import (irange, inf, div, arg, printf)
      
      def solve():
        # consider selling price of the cottage
        for x in irange(5, inf, step=5):
          t = x
      
          # A's amount
          t -= 1
          A = div(t, 4)
          if A is None: continue
      
          # B's amount
          t -= A + 1
          B = div(t, 4)
          if B is None: continue
      
          # C's amount
          t -= B + 1
          C = div(t, 4)
          if C is None: continue
      
          # D's amount
          t -= C + 1
          D = div(t, 4)
          if D is None: continue
      
          # and each gets 1/4 of what is left
          t -= D + 1
          q = div(t, 4)
          if q is None: continue
      
          yield (x, A + q, B + q, C + q, D + q)
      
      # find the first few solutions
      n = arg(1, 0, int, "number of solutions to find")
      for (i, (x, A, B, C, D)) in enumerate(solve(), start=1):
        printf("[{i}] x={x}; A={A} B={B} C={C} D={D}")
        if i == n: break
      

      Solution: The value of the cottage was £2045.

      The father keeps £5 and the split of the remaining £2040 is: A=672, B=544, C=448, D=376.

      Like

      • Jim Randell's avatar

        Jim Randell 9:37 am on 5 December 2021 Permalink | Reply

        Using this analysis [@wolfram] we see that the solutions to the problem are given by:

        N = 1024k − 3

        for k = 1, 2, 3, … where N is a multiple of 5.

        k = 1: N = 1021
        k = 2: N = 2045 (*** SOLUTION ***)

        And we can write a more efficient program:

        from enigma import (irange, inf, ediv, arg, printf)
        
        def solve():
          for k in irange(1, inf):
            x = 1024 * k - 3
            if x % 5 != 0: continue
        
            # calculate amounts
            t = x - 1
            A = ediv(t, 4)
            t -= A + 1
            B = ediv(t, 4)
            t -= B + 1
            C = ediv(t, 4)
            t -= C + 1
            D = ediv(t, 4)
            t -= D + 1
            q = ediv(t, 4)
        
            yield (x, A + q, B + q, C + q, D + q)
        
        # find the first few solutions
        n = arg(1, 0, int, "number of solutions to find")
        for (i, (x, A, B, C, D)) in enumerate(solve(), start=1):
          printf("[{i}] x={x}; A={A} B={B} C={C} D={D}")
          if i == n: break
        

        Like

  • Unknown's avatar

    Jim Randell 4:56 pm on 3 December 2021 Permalink | Reply
    Tags:   

    Teaser 3089: An old square 

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

    Archaeologists have excavated an ancient area that has the shape of a perfect square. Fragmentary documents give some information about its measurements: the number of major units along a side is less than the remaining number of minor units; there is a whole number of minor units in a major unit (no more than 10); and the area is a certain number of square major units plus a remainder of 15 square minor units.

    Expressed just in terms of minor units, how many are there along a side?

    [teaser3089]

     
    • Jim Randell's avatar

      Jim Randell 5:08 pm on 3 December 2021 Permalink | Reply

      If we suppose a major unit consists of x minor units (x ≤ 10), then the side of the square can be expressed as a major units plus b minor units (= ax + b minor units) where a < b < x.

      The following Python program runs in 45ms.

      Run: [ @replit ]

      from enigma import (irange, subsets, div, sq, printf)
      
      # consider x minor units in a major unit (no more than 10)
      # consider a major + b minor units (a < b < x)
      for (a, b, x) in subsets(irange(1, 10), size=3):
        # the side of the square is (ax + b) (minor units)
        s = a * x + b
        # can s^2 be expressed as k x^2 + 15?
        k = div(sq(s) - 15, sq(x))
        if k is not None:
          printf("x={x} a={a} b={b}; s={s} (= {a}x + {b}); s^2 = {k}x^2 + 15")
      

      Solution: The area is a square with each side measuring 41 minor units.

      So the entire area measures 41 × 41 = 1681 minor units.

      There are 7 minor units in a major unit (so the side of the square is 5 major + 6 minor units).

      And the area of the square can be expressed as 34 square major + 15 square minor units.

      Like

    • Frits's avatar

      Frits 3:28 pm on 4 December 2021 Permalink | Reply

      I have the feeling X might be logically determined as the Wolframalpha site for this equation only shows solutions for a specific positive X.

        
      #!/usr/bin/env python3 -m enigma -r
       
      SubstitutedExpression
       
      --base=11
       
      "B < X"
      
      # if X is even then A * X + B must be odd so B must be odd as well
      "X % 2 or B % 2"
      
      # if X is 10 then sq(A * X + B) ends on a 5 so B must be 5
      "X < 10 or B == 5"
      
      "A < B"
       
      "div(2 * A * B * X + B * B - 15, sq(X))"
       
      --answer="A * X + B"
      # if X is 5 then sq(A * X + B) ends on 0 or 5 so B must be 0 or 5, impossible
      --invalid="0|1|2|3|5,X"
      --invalid="0|1|10,B"
      # A can't be 8 as if X is 10 then B must be 5
      --invalid="0|8|9|10,A"
      #--verbose=256   # print generated code
      

      Like

    • GeoffR's avatar

      GeoffR 9:29 am on 5 December 2021 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % a major units, x minor units in one major unit and b minor units
      var 1..9:a; var 1..9:b; var 1..9:x;
      % S = side of perfect square
      var 1..50:S;
      
      constraint a < b  /\ b < x;
      constraint S == a * x + b;  
      
      % the area is a certain number of square major units
      % plus a remainder of 15 square minor units
      constraint S * S mod (x*x) == 15;
      
      solve satisfy;
      
      output["Side in minor units = " ++ show(S) ++ "  " 
      ++ "\n" ++ "a = " ++ show(a)  ++ ", b = " ++ show(b) ++ ", x = " ++ show(x)];
      
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 9:54 am on 5 December 2021 Permalink | Reply

        @GeoffR: x is “no more than 10”, so [[ var 1..10: x; ]] would be more appropriate.

        Like

    • GeoffR's avatar

      GeoffR 10:19 am on 5 December 2021 Permalink | Reply

      @Jim: Yes, correct. I was assuming x < 10.
      Fortunately, it does not affect the answer.

      Like

  • Unknown's avatar

    Jim Randell 8:42 am on 2 December 2021 Permalink | Reply
    Tags:   

    Teaser 2841: Crenellation aggregation 

    From The Sunday Times, 5th March 2017 [link] [link]

    The castle’s crenellated outer walls formed a pentagon, and on a family visit we decided to count the crenels. My son counted the number on each side and found that these totals on each side were five consecutive two figure numbers. My daughter and wife started together and then one of them walked clockwise around the walls and the other walked anticlockwise. They each counted the crenels they passed until they met. Their totals were two different prime numbers (with no prime number between the two). I consulted the tourist leaflet and found that the total number of crenels was in fact the product of three prime numbers.

    How many crenels were there in total?

    [teaser2841]

     
    • Jim Randell's avatar

      Jim Randell 8:43 am on 2 December 2021 Permalink | Reply

      We can express the the total number of crenels in three different ways (assuming no mistakes were made in the counting):

      t = 5n + 10 (where 10 ≤ n ≤ 95, so 60 ≤ t ≤ 485)
      t = p1 + p2 (where p1 and p2 are consecutive primes)
      t = q1 × q2 × q3 (where q1, q2, q3 are primes)

      This Python program looks at consecutive primes, until it finds a total that satisfies the remaining two expressions.

      It runs in 47ms.

      from enigma import (primes, tuples, div, printf)
      
      # consider consecutive pairs of primes (primes.between(29, 242) is sufficient)
      for (p1, p2) in tuples(primes.between(2, 485), 2):
        # form the total
        t = p1 + p2
        if t < 60: continue
        if t > 485: break
      
        # check it can be expressed as 5n + 10
        n = div(t - 10, 5)
        if n is None: continue
      
        # check it is the product of three prime numbers
        qs = primes.factor(t)
        if len(qs) != 3: continue
      
        # output solution
        printf("{t} = +({n}..{n4}) = {p1} + {p2} = *{qs}", n4=n + 4)
      

      Solution: There were 410 crenels in total.

      So we have:

      410 = 80 + 81 + 82 + 83 + 84
      410 = 199 + 211
      410 = 2 × 5 × 41

      Like

    • Frits's avatar

      Frits 10:59 pm on 2 December 2021 Permalink | Reply

      More analysis and using the 6n + 1 or 6n – 1 rule (just for fun).

      Other than 2 and 3, prime numbers must be of the form 6n + 1 or 6n – 1.

      Enigma function is_prime() is called only once.
      Function islice() is borrowed from enigma function first().

        
      from itertools import islice
      from enigma import is_prime
      
      # t = 5n + 10 (where 10 <= n <= 95, so 60 <= t <= 485)
      # t = p1 + p2 (where p1 and p2 are consecutive primes) ==> t is even
      # t = q1 × q2 × q3 (where q1, q2, q3 are primes and q1 <= q2 <= q3) 
      # t is even ==> q1 = 2, n is even and 30 <= q2 x q3 <= 242
      
      # n is even ==> n = 2 * m and 5 <= m <= 47
      # t = p1 + p2 = 2 * (q2 * q3) = 10m + 10 = (m + 1) * 10
      
      # t must end on a zero ==> q2 must be 5 and 6 <= q3 <= 48
      # t = 10 * q3 ==> q3 = m + 1
      
      # list of prime numbers from 6 up to 48 (used for q3)
      P = {3, 5, 7}
      P |= {x for x in range(11, 49, 2) if all(x % p for p in P)}
      P = {x for x in P if x > 5}
      
      # try to express next prime as 6k - 1 or 6k + 1
      def nextprime(n):
        # in this puzzle n always ends on a 5
        return list(islice([p for x in range(n // 6 + 1, 81) for i in [-1, 1] 
                    if is_prime(p := 6 * x + i) and p > n], 0, 1))[0]
      
      # list of prime numbers from 31 (prime before 5 x 7) up to 235 (5 x 47)
      P2 = {3, 5, 7, 11, 13, 17}
      P2 |= {x for x in range(19, 236, 2) if all(x % p for p in P2)}
      
      # try to express previous prime as 6k - 1 or 6k + 1
      def prevprime(n):
        # in this puzzle n always ends on a 5
        return list(islice([p for x in range(n // 6, 0, -1) for i in [1, -1] 
                    if (p := 6 * x + i) in P2 and p < n], 0, 1))[0]
      
      bef35 = prevprime(35) # prime number before 5 x lowest q3 prime
      P2 = {x for x in P2 if x >= bef35}
      
      # add one more prime (to be found by nextprime_P2() )
      np = nextprime(max(P2))
      P2.add(np)
      P2limit = np // 6 + 1  # variable to be used in nextprime_P2()
      
      # try to express next prime as 6k - 1 or 6k + 1
      def nextprime_P2(n):
        # in this puzzle n always ends on a 5
        return list(islice([p for x in range(n // 6 + 1, P2limit) for i in [-1, 1]
                    if (p := 6 * x + i) in P2 and p > n], 0, 1))[0]   
      
      # t = q1 x q2 x q3 = 2 x 5 x q3
      for q3 in P:
        t = 10 * q3  
        # find prime before half of t
        p1 = prevprime(5 * q3) 
        
        # t = p1 + p2 (where p1 and p2 are consecutive primes)
        if t - p1 not in P2: continue
        
        if nextprime_P2(5 * q3) != t - p1: continue
        
        print(t, "crenels in total")
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:30 pm on 3 December 2021 Permalink | Reply

        I’m considering adding [[ Primes.before(n) ]] and [[ Primes.after() ]] to the prime sieve class, which would give you the largest prime less than n and the smallest prime greater than n.

        [Note: These routines are now available in enigma.py]

        The puzzle could then be solved with:

        from enigma import (primes, printf)
        
        primes.expand(252)
        
        for q in primes.between(6, 48):
          m = 5 * q
          p1 = primes.before(m)
          p2 = primes.after(m)
          t = 2 * m
          if p1 + p2 == t:
            n = 2 * q - 2
            # output solution
            printf("t={t} = +({p1}, {p2}) = *(2, 5, {q}) = +({n}..{n4})", n4=n + 4)
        

        Like

  • Unknown's avatar

    Jim Randell 9:33 am on 30 November 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 888: Master pieces 

    From The Sunday Times, 13th August 1978 [link]

    The artist Pussicatto was exhibiting his new painting. It consisted of a 5-by-5 square of small squares with some of the small squares coloured black and the rest of the small squares coloured white.

    The forger Coppicatto sent six of his assistants to make copies of different parts of the painting. They returned with:

    Unfortunately five of the assistants could not remember which way up their  parts should go, and the other assistant, who gave his part the right way up, had copied the colour of one of the small squares wrongly. However the other five parts did cover the whole of the original painting.

    Reproduce the original Pussicatto painting.

    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.

    [teaser888]

     
    • Jim Randell's avatar

      Jim Randell 9:35 am on 30 November 2021 Permalink | Reply

      Considering the 5 pieces that are correct, but of unknown orientation. The entire painting is covered by these 5. In particular each of the corner sub-squares of the painting must correspond to 4 of the pieces (in some orientation), so we can look for those.

      The following Python 3 program runs in 110ms.

      Run: [ @replit ]

      from enigma import (irange, repeat, subsets, cproduct, join, printf)
      
      # the 6 pieces
      pieces = {
        1: (0, 1, 0, 0, 1, 0, 0, 1, 1),
        2: (1, 0, 1, 0, 0, 1, 0, 1, 1),
        3: (0, 1, 0, 1, 0, 0, 1, 1, 1),
        4: (0, 0, 1, 1, 0, 0, 0, 1, 1),
        5: (0, 0, 0, 0, 0, 1, 0, 1, 0),
        6: (1, 0, 0, 1, 1, 1, 0, 0, 1),
      }
      
      # rotate a piece
      rotate = lambda p: tuple(p[i] for i in (6, 3, 0, 7, 4, 1, 8, 5, 2))
      
      # return all 4 rotations
      rots = lambda p: repeat(rotate, p, 3)
      
      # placements for the corners
      corners = [(0, 0), (0, 2), (2, 0), (2, 2)]
      
      # place p at (x, y) in the grid
      # return a new grid or None
      def place(g, p, x, y):
        g_ = dict(g)
        for (j, v) in enumerate(p):
          (dx, dy) = divmod(j, 3)
          k = (x + dx, y + dy)
          v_ = g_.get(k)
          if v_ is None:
            g_[k] = v
          elif v_ != v:
            return None
        return g_
      
      # place pieces <ps> at locations <ls>
      def solve(ps, ls, g=dict()):
        # are we done?
        if not ps:
          yield g
        else:
          # place a piece in the next location
          (p, (x, y)) = (ps[0], ls[0])
          # consider possible rotations
          for p in rots(p):
            g_ = place(g, p, x, y)
            if g_ is not None:
              yield from solve(ps[1:], ls[1:], g_)
      
      # locate a piece from ps in grid g
      def locate(g, ps):
        for p in ps:
          for (x, y) in cproduct([(0, 1, 2), (0, 1, 2)]):
            if place(g, p, x, y):
              yield (x, y)
      
      # consider possible orderings of pieces
      for (p1, p2, p3, p4, p5, p6) in subsets(pieces.keys(), size=6, select="P"):
      
        # place the first 4 (in some orientation) in the corners
        for g in solve(list(pieces[i] for i in (p1, p2, p3, p4)), corners):
      
          # check the 5th piece fits in some orientation at some other location
          l5s = set(z for z in locate(g, rots(pieces[p5])) if z not in corners)
          if not l5s: continue
      
          # and the remaining piece has one square wrong but fits the right way up
          l6s = dict()
          for j in irange(0, 8):
            p = list(pieces[p6])
            p[j] ^= 1
            for z in locate(g, [p]):
              if z not in corners:
                l6s[z] = j
          if not l6s: continue
          if not any(a != b for (a, b) in cproduct([l5s, l6s.keys()])): continue
      
          # output solution
          printf("corners = [{p1}, {p2}, {p3}, {p4}]; other = {p5} @ {l5s}; wrong = {p6} @ {l6s}\n")
          for x in (0, 1, 2, 3, 4):
            printf("{row}", row=join((g[(x, y)] for y in (0, 1, 2, 3, 4)), sep=" ", enc="[]"))
          printf()
      

      Solution: The solution is as follows:

      There are 9 possible locations for a 3×3 sub-square of the 5×5 square. The 4 corners, the 4 edges, and the central sub-square.

      The corners consist of pieces 4, 5, 6, 1 (in suitable orientations), and piece 2 corresponds to the left edge sub-square. The central sub-square correspond to piece 3 (the right way up), except the cell marked “x” is the wrong colour.

      Note that each of the pieces 1 – 6 corresponds to a different 3×3 sub-square in the finished painting. If two pieces are allowed to correspond to the same sub-square, then this solution is not unique.

      The program produces 2 solutions corresponding to the same diagram. This is because piece 6 is the same when rotated through 180°.

      Like

    • Frits's avatar

      Frits 1:10 pm on 1 March 2024 Permalink | Reply

      When looking for similar puzzles to the 1994 IMO C1 question I stumbled upon this puzzle.

      @Jim, do you know a more elegant/compact alternative for diff_comb()?

      from enigma import SubstitutedExpression
      from itertools import product
      
      # the 6 pieces
      pieces = {
        1: (0, 1, 0, 0, 1, 0, 0, 1, 1),
        2: (1, 0, 1, 0, 0, 1, 0, 1, 1),
        3: (0, 1, 0, 1, 0, 0, 1, 1, 1),
        4: (0, 0, 1, 1, 0, 0, 0, 1, 1),
        5: (0, 0, 0, 0, 0, 1, 0, 1, 0),
        6: (1, 0, 0, 1, 1, 1, 0, 0, 1),
      }
      
      # rotate a piece clockwise
      rotate = lambda p: tuple(p[x] for x in [3 * i + j for j in range(2, -1, -1)
                                                        for i in range(3)])
      
      # return all 4 rotations
      def rotations(p):
        yield p
        for _ in range(3):
          p = rotate(p)
          yield p
      
      # dictionary of 3x3 box usage
      d_3x3 = dict()
      for k, v in pieces.items():
        for r in rotations(v):
          d_3x3[r] = d_3x3.get(r, set()) | {k}
      
      # placements for the corners
      corners = {(0, 0), (0, 2), (2, 0), (2, 2)}
      
      # get the four corner 3x3 boxes
      get_corners = lambda m: [topLeft_3x3(m, c) for c in corners]
      
      # check if a corner can be made by one of the pieces
      check_corner = lambda r: set() if r not in d_3x3 else d_3x3[r]
      
      # check if a combination with different values exists
      def diff_comb(s):
        for p in product(*s):
          if len(set(p)) == len(p):
            return True
        return False
      
      # return the 3x3 box values starting at the top left <tl>
      def topLeft_3x3(m, tl):
        (tlx, tly) = tl
        return tuple(m[tlx + x][tly + y]
                     for (x, y) in product((0, 1, 2), (0, 1, 2)))
      
      # perform checks for the two remaining pieces
      def check_oth(m, cs):
        # nine possible 3x3 boxes minus the four corner 3x3 boxes
        five3x3 = {topLeft_3x3(m, tl) for tl in product((0, 1, 2), (0, 1, 2))
                   if tl not in corners}
      
        # check all combinations of 4 corners
        for p in product(*cs):
          if len(set(p)) != len(p): continue
          # remaining two pieces
          rest = set(range(1, 7)).difference(p)
      
          found_rotated = []
          found_wrong = []
          for pc in rest:
            # check possible rotation (without the corners)
            for rot in [k for k, v in d_3x3.items() if pc in v]:
              if rot in five3x3:
                found_rotated.append((pc, rot))
      
            # check if a "right way up" piece has exactly 8 correct squares
            for a in five3x3:
              if sum([x == y for x, y in zip(a, pieces[pc])]) == 8:
               found_wrong.append((pc, a))
      
          # check options for 2 last pieces
          for (rn, r), (wn, w) in product(found_rotated, found_wrong):
            # different pieces
            if rn == wn: continue
            # both pieces must be at a different spot
            w_spots = {i for i, x in enumerate(five3x3) if x == w}
            r_spots = {i for i, x in enumerate(five3x3) if x == r}
            if len(w_spots | r_spots) < 2: continue
            return True
      
        return False
      
      A_Y = [chr(x) for x in range(ord("A"), ord("Y") + 1)]
      
      # 5x5 matrix of A-E, F-J, K-O, P-T, U-Y
      M = [[A_Y[5 * i + j] for j in range(5)] for i in range(5)]
      
      # all variable names in a 5x5 matrix layout (without quotes)
      mat = "((" + "), (".join(','.join(r) for r in M) + "))"
      
      answer = f"{mat}"
      
      exprs = []
      # check if every corner can be made from a piece
      for i, c in enumerate(get_corners(M), start=1):
        s = "(" + ', '.join(c) + ")"
        exprs.append(f"len(c{i} := check_corner({s})) > 0")
      
      # 4 corners use different pieces
      exprs.append("diff_comb([c1, c2, c3, c4])")
      
      # check remaining two pieces
      exprs.append(f"check_oth({mat}, [c1, c2, c3, c4])")
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        exprs,
        answer=answer,
        distinct="",
        env=dict(check_oth=check_oth,
                 check_corner=check_corner,
                 diff_comb=diff_comb),
        digits=range(0, 2),
        decl="global c1, c2, c3, c4",
        denest=2,
        reorder=0,    # necessary because of assignment statements
        verbose=0,    # use 256 to see the generated code
      )
      
      # print answers
      print("answer:")
      for ans in p.answers():
        for x in ans:
          print(f"{x}")
        print()     
      

      Like

      • Jim Randell's avatar

        Jim Randell 8:19 am on 12 June 2024 Permalink | Reply

        @Frits: You could use the [[ disjoint_cproduct() ]] function from Teaser 3220 to implement diff_comb.

        diff_comb(s)any(disjoint_cproduct(s))

        Like

        • Frits's avatar

          Frits 10:23 am on 12 June 2024 Permalink | Reply

          Thanks, I tested it. I had forgotten I had asked the question.

          Like

  • Unknown's avatar

    Jim Randell 11:03 am on 28 November 2021 Permalink | Reply
    Tags: by: Miss W. M. Jeffree   

    Brain Teaser: Noughts and crosses 

    From The Sunday Times, 23rd December 1956 [link]

    Each square is to be occupied by either a nought or a cross.

    The cross (or x) stands for one particular digit which you may discover for yourself.

    No light begins with nought.

    The following clues give the prime factors of the various lights, each letter representing a different prime:

    Across:
    (I) abcd
    (II) a²b²ef
    (III) ab²gh
    (IV) abij²k
    (V) a²bejl
    (VI) ab²ikm

    Down:
    (i) ab²ijkm
    (ii) a²beij²k
    (iii) ab²mnp
    (iv) a²bcde
    (v) abijkl

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. Prizes of £5, £3 and £1 were offered for the first 3 correct solutions.

    [teaser-1956-12-23] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 11:04 am on 28 November 2021 Permalink | Reply

      Once you get going I think it is easier to solve this puzzle by hand. But here is a fully programmed solution:

      The numbers (I) and (i) must be repdigits of the appropriate length with the appropriate prime factorisation patterns.

      This immediately gives us the required digit x, then we just have to fill out the remaining numbers, matching the factorisation patterns.

      This Python program runs in 49ms.

      Run: [ @replit ]

      from enigma import (irange, repdigit, singleton, intersect, subsets, nconcat, prime_factor, div, choose, diff, chain, multiply, nsplit, unzip, printf)
      
      # find factors of n, with multiplicities in ms
      def factor(n, ms={1, 2}):
        r = dict((k, list()) for k in ms)
        for (p, e) in prime_factor(n):
          if e not in r: return
          r[e].append(p)
        return r
      
      # consider values for the digit x
      for x in irange(1, 9):
      
        # (I) = xxxxx = a.b.c.d
        a1 = repdigit(5, x)
        fsa1 = factor(a1, {1})
        if fsa1 is None or not (len(fsa1[1]) == 4): continue
      
        # (i) = xxxxxx = a.b^2.i.j.k.m
        d1 = repdigit(6, x)
        fsd1 = factor(d1)
        if fsd1 is None or not (len(fsd1[2]) == 1 and len(fsd1[1]) == 5): continue
        b = singleton(fsd1[2])
        a = singleton(intersect([fsd1[1], fsa1[1]]))
      
        printf("x={x}; dn1={d1} ac1={a1}; a={a} b={b}")
      
        # calculate factorisations of x???? / (a.b)
        fs5 = dict()
        for ds in subsets((0, x), size=4, select="M", fn=list):
          ds.insert(0, x)
          n = nconcat(ds)
          n_ = div(n, a * b)
          if n_ is None: continue
          fs = factor(n_, {1, 2})
          if fs:
            fs5[n] = fs
      
        # look for values for (II), (III), (IV), (V)
        def fna6(a6):
          (a6, fs) = a6
          # there should be 4 single factors, one is b, none is a
          return (len(fs[2]) == 0 and len(fs[1]) == 4 and b in fs[1] and a not in fs[1])
      
        def fna5(a6, a5):
          (a5, fs) = a5
          # there should be 4 single factors, one is a, none is b
          return (len(fs[2]) == 0 and len(fs[1]) == 4 and a in fs[1] and b not in fs[1])
      
        def fna4(a6, a5, a4):
          (a4, fs) = a4
          # there should be 2 single factors and one double factor, [none is a or b]
          return (len(fs[2]) == 1 and len(fs[1]) == 2 and not intersect([[a, b], fs[1] + fs[2]]))
      
        def fna2(a6, a5, a4, a2):
          (a2, fs) = a2
          # there are no double factors, 4 single factors, one of them is a, one of them is b
          return (len(fs[2]) == 0 and len(fs[1]) == 4 and a in fs[1] and b in fs[1])
      
        def fna3(a6, a5, a4, a2, a3):
          (a3, fs) = a3
          # there no double factors, 3 single factors, one of them is b, none of them a
          return (len(fs[2]) == 0 and len(fs[1]) == 3 and b in fs[1] and a not in fs[1])
        
        for xs in choose(fs5.items(), [fna6, fna5, fna4, fna2, fna3], distinct=1):
          ((a6, fsa6), (a5, fsa5), (a4, fsa4), (a2, fsa2), (a3, fsa3)) = xs
          # determine factors
          j = singleton(fsa4[2])
          ik = fsa4[1]
          m = singleton(diff(fsa6[1], [b] + ik))
          e = singleton(diff(intersect([fsa2[1], fsa5[1]]), [a]))
          f = singleton(diff(fsa2[1], [a, b, e]))
          l = singleton(diff(fsa5[1], [a, e, j]))
          gh = diff(fsa3[1], [b])
          cd = diff(fsa1[1], [a, b])
      
          # check the down factorisations
          if not (d1 == multiply(chain([a, b, b, j, m], ik))): continue
          (_, d2, d3, d4, d5) = (nconcat(x) for x in unzip(nsplit(x) for x in (a1, a2, a3, a4, a5, a6)))
          if not (d2 == multiply(chain([a, a, b, e, j, j], ik))): continue
          if not (d4 == multiply(chain([a, a, b, e], cd))): continue
          if not (d5 == multiply(chain([a, b, j, l], ik))): continue
          np = div(d3, a * b * b * m)
          if np is None: continue
          np = factor(np, {1})
          if np is None or len(np[1]) != 2: continue
          np = np[1]
      
          # check all the numbers are different
          if len(chain([a, b, j, m, e, f, l], ik, gh, cd, np, fn=set)) != 15: continue
      
          printf("  (I)={a1} (II)={a2} (III)={a3} (IV)={a4} (V)={a5} (VI)={a6}")
          printf("  (i)={d1} (ii)={d2} (iii)={d3} (iv)={d4} (v)={d5}")
          printf("  -> j={j} ik={ik} m={m} e={e} f={f} l={l} gh={gh} cd={cd} np={np}")
          printf()
      

      Solution: The completed grid looks like this:

      We can determine the primes as:

      a = 2
      b = 3
      e = 5
      f = 367
      j = 11
      l = 101
      m = 37
      c × d = 41 × 271
      g × h = 47 × 71
      i × k = 7 × 13
      n × p = 17 × 53

      Note that although it may look like the prime factorisation may have been presented in numerical order, this is not the case for at least 2 of the clues.

      Like

      • Jim Randell's avatar

        Jim Randell 9:57 am on 29 November 2021 Permalink | Reply

        Here is my manual solution:

        (I) 11111 factorises as (41, 271), so that gives us two of a, b, c, d, and the remaining two come from the single digit x. So x = 6, and (a, b, c, d) = (2, 3, 41, 271) (in some order).

        (i) 666666 factorises as (2, 3², 7, 11, 13, 37), so b = 3, a = 2, and (i, j, k, m) = (7, 11, 13, 37), (c, d) = (41, 271).

        (iv) = (a², b, c, d, e) = 2 × (I) × e = 133332 × e, and consists of 6 digits chosen from (0, 6). The only possible prime value for e is 5, giving (iv) = 666660.

        (VI) = (a, b², i, k, m) = 18 × ikm. Choosing one of the values from (7, 11, 13, 37) for j we get:

        j = 7 ⇒ (VI) = 95238
        j = 11 ⇒ (VI) = 60606
        j = 13 ⇒ (VI) = 51282
        j = 37 ⇒ (VI) = 18018

        So j = 11, and (VI) = 60606.

        (IV) = (a, b, i, j², k) = 726 × ik. Choosing one of the values from (7, 13, 37) for m we get:

        m = 7 ⇒ (IV) = 349206
        m = 13 ⇒ (IV) = 188034
        m = 37 ⇒ (IV) = 66066

        So m = 37, ik = 7 × 13, and (IV) = 66066.

        (ii) = (a², b, e, i, j², k) = 660660.

        (II) = (a², b², e, f) = 180 × f, which matches 66?6?. The possible numbers are:

        (II) = 66660 ⇒ f = 370.3…
        (II) = 66060 ⇒ f = 367, prime!

        So, f = 367, and (II) = 66060.

        (V) = (a², b, e, j, l) = 660 × l, and matches 66?6?. The possibilities are:

        (V) = 66660 ⇒ l = 101
        (V) = 66066 ⇒ l = 100.1

        So l = 101 and (V) = 66660.

        (v) = (a, b, i, j, k, l) = 606606.

        (III) = (a, b², g, h) = 18 × gh, and matches 60?66. The possibilities are:

        (III) = 60666 ⇒ gh = 3370.3…
        (III) = 60066 ⇒ gh = 3337 = 47 × 71

        So (III) = 60066, and the grid is complete.


        The solution was published in the 6th January 1957 issue of The Sunday Times along with the following note:

        Some readers complained that this brain-teaser was too easy. Over eight hundred correct solutions were in fact received, but the number was in line with those recorded for earlier mathematical puzzles which appeared, at least, to be more diffcult.

        Like

    • Frits's avatar

      Frits 1:32 pm on 28 November 2021 Permalink | Reply

        
      from enigma import SubstitutedExpression, prime_factor, div, gcd
        
      # find multiplicities of factors of n
      mfac = lambda n: [[e for (p, e) in prime_factor(n)].count(i) for i in [1, 2]]
        
      # Z is cross digit  (1 - 9)
      #
      # A B C D E
      # F G H I J
      # K L M N O
      # P Q R S T 
      # U V W X Y
      # p q s t u
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # (I)   abcd
        "mfac(ABCDE * Z) == [4, 0]",
        # (II)  a²b²ef
        "mfac(FGHIJ * Z) == [2, 2]",
        # (III) ab²gh
        "mfac(KLMNO * Z) == [3, 1]",
        # (IV)  abij²k 
        "mfac(PQRST * Z) == [4, 1]",
        # (V)   a²bejl
        "mfac(UVWXY * Z) == [4, 1]",
        # (VI)  ab²ikm
        "mfac(pqstu * Z) == [4, 1]",
        
        # Down:
        # (i)   ab²ijkm
        "mfac(AFKPUp * Z) == [5, 1]",
        # (ii)  a²beij²k
        "mfac(BGLQVq * Z) == [4, 2]",
        # (iii) ab²mnp
        "mfac(CHMRWs * Z) == [4, 1]",
        # (iv)  a²bcde
        "mfac(DINSXt * Z) == [4, 1]",
        # (v)   abijkl
        "mfac(EJOTYu * Z) == [6, 0]",
        ],
        answer="([x * Z for x in [ABCDE, FGHIJ, KLMNO, PQRST, UVWXY, pqstu]]), \
                ([x * Z for x in [AFKPUp, BGLQVq, CHMRWs, DINSXt, EJOTYu]])",
        symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZpqstu",
        d2i=dict([(0, "ZABCDEFKPUp")] + 
                 [(k, "ABCDEFGHIJKLMNOPQRSTUVWXYpqstu") for k in range(2, 10)]),
        distinct="",
        env=dict(mfac=mfac),
        verbose=0,
      )
      
      # print answers
      for (_, ans) in p.solve():
        
        j = div(ans[1][0], ans[0][-1])            # (VI) and (i)
        if not j: continue
        
        l = div(j * ans[1][-1], ans[0][3])        # (IV) and (v)
        if not l: continue
        
        cd = div(j * l * ans[1][3], ans[0][4])    # (V) and (iv)
        if not cd: continue
        
        ab = div(ans[0][0], cd)                   # (I)
        if not ab: continue
        
        ik = div(ans[1][-1], j * l * ab)          # (v)
        if not ik: continue
        
        ef = div(ans[0][1], ab**2)                # (II) 
        if not ef: continue
       
        a2be = div(ans[0][4], j * l)              # (V) 
        if not a2be: continue
        
        bf = div(ans[0][1], a2be)                 # (II) 
        if not bf: continue
       
        f = gcd(ef, bf)
        e = ef // f
        b = bf // f
        a = ab // b
        m = div(ans[0][-1], a * b**2 * ik)        # (VI) 
        if not m: continue
        
        gh = div(ans[0][2], a * b**2)             # (III) 
        np = div(ans[1][2], a * b**2 * m)         # (iii) 
        print(" a b e  f   j  l   m  cxd  gxh ixk nxp\n", 
              a, b, e, f, j, l, m, cd, gh, ik, np, "\n")
        
        for a in ans[0]:
          print(a)
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:41 pm on 28 November 2021 Permalink | Reply

        A neat use of the [[ SubstitutedExpression() ]] solver.

        But do you think [[ mfac() ]] is strong enough? A number like 9240 would give an answer of [4, 0], but not match the pattern a⋅b⋅c⋅d.

        Although checking the numbers have the right number of single and double prime factors turns out to be enough to narrow down the solution space to a single candidate, even without matching up the primes in the patterns.

        Like

    • Frits's avatar

      Frits 9:37 pm on 28 November 2021 Permalink | Reply

      With a better “find multiplicities” function (thanks Jim).
      Most of the work is now done by [[ SubstitutedExpression() ]].

        
      from enigma import SubstitutedExpression, prime_factor, div, gcd as z
        
      # find multiplicities of factors of n
      def v(n, allowed):
        lst = [e for (p, e) in prime_factor(n)]
        # only consider factors with multiplicity 1 or 2
        if any(x > 2 for x in lst): return False
          
        return [lst.count(i) for i in [1, 2]] == allowed
      
      # multiply all items by n  
      def w(lst, n):
        return [x * n for x in lst]
        
      # Z is cross digit  (1 - 9)
      #
      # A B C D E
      # F G H I J
      # K L M N O
      # P Q R S T 
      # U V W X Y
      # p q s t u
      
      answer =  "(w([ABCDE, FGHIJ, KLMNO, PQRST, UVWXY, pqstu], Z)),"
      # a = ab / b 
      answer += "(div(ab * UVWXY * feh, FGHIJ * jl * lcg),"
      # b = bf / f 
      answer += "div(FGHIJ * jl * lcg, UVWXY * feh),"
      # e = ef / f  
      answer += "div(FGHIJ * Z, ab**2 * feh), " 
      answer += "feh, "                                                 # f
      answer += "jk, "                                                  # j
      answer += "lcg, "                                                 # l
      # m = ab²ikm / ab²ik)        
      answer += "div(pqstu * (UVWXY * feh), FGHIJ * EJOTYu),"
      answer += "div(ABCDE * Z, ab),"                                   # cd
      answer += "div(KLMNO * Z * UVWXY * feh, ab * FGHIJ * jl * lcg),"  # gh
      answer += "div(EJOTYu * Z, ab * jl * lcg),"                       # ik
      answer += "div(CHMRWs * Z * EJOTYu, ab * jl * lcg * pqstu)),"     # np
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # Up: 
        "v(ABCDE * Z, [4, 0])",   # (I)   abcd
        "v(FGHIJ * Z, [2, 2])",   # (II)  a²b²ef
        "v(KLMNO * Z, [3, 1])",   # (III) ab²gh
        "v(PQRST * Z, [4, 1])",   # (IV)  abij²k 
        "v(UVWXY * Z, [4, 1])",   # (V)   a²bejl
        "v(pqstu * Z, [4, 1])",   # (VI)  ab²ikm
        
        # Down:
        "v(AFKPUp * Z, [5, 1])",  # (i)   ab²ijkm
        "v(BGLQVq * Z, [4, 2])",  # (ii)  a²beij²k
        "v(CHMRWs * Z, [4, 1])",  # (iii) ab²mnp
        "v(DINSXt * Z, [4, 1])",  # (iv)  a²bcde
        "v(EJOTYu * Z, [6, 0])",  # (v)   abijkl
        
        # j = ab²ijkm / ab²ikm
        "div(AFKPUp, pqstu) = jk",           
        # l = j * abijkl / abij²k 
        "div(jk * EJOTYu, PQRST) = lcg",     
        # ab = abcd / (j*l*a²bcde / a²bejl) = (I) * (V) / (j * l * (iv) )
        "div(Z * ABCDE * UVWXY, jk * lcg * DINSXt) = ab",      
        # f = gcd(ef, bf)
        "z(div(FGHIJ * Z, ab**2), div(FGHIJ * jl * lcg, UVWXY)) = feh",  
        ],
        answer=answer,
        symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghjklpqstu",
        d2i=dict([(0, "ZABCDEFKPUp")] + 
                 [(k, "ABCDEFGHIJKLMNOPQRSTUVWXYpqstu") for k in range(2, 10)]),
        distinct="",
        env=dict(v=v,w=w,z=z),
        verbose=0,
      )
      
      # check and print answers
      for (_, ans) in p.solve():
        
        # check if all items are numbers
        if any(x is None for x in ans[1]): continue
        # check last 4 numbers on being a product of 2 primes
        if any(not v(x, [2, 0]) for x in ans[1][-4:]) : continue
        
        print("a b e  f   j  l   m  cxd  gxh ixk nxp") 
        print(*ans[1])
        
        for a in ans[0]:
          print(a)
      

      Like

  • Unknown's avatar

    Jim Randell 2:29 pm on 26 November 2021 Permalink | Reply
    Tags:   

    Teaser 3088: Bog standard deviation 

    From The Sunday Times, 28th November 2021 [link] [link]

     

    My four toilet rolls had zigzag-cut remnants (not unlike that shown). Curiously, each polygonal remnant had a different two-figure prime number of sides, each with a digit sum itself prime.

    Calculating the average number of sides for the four remnants and the average of their squares, I found that the average of the squares minus the square of the average had a value whose square root (the “standard deviation”) was a whole number.

    I also noticed that a regular convex polygon with a number of sides equal to the average number of sides would have an odd whole-number internal angle (in degrees).

    Give the “standard deviation”.

    [teaser3088]

     
    • Jim Randell's avatar

      Jim Randell 2:45 pm on 26 November 2021 Permalink | Reply

      A bit of a convoluted scenario, but the calculations are fairly straightforward.

      The average number of sides must be an integer to construct the polygon, and so the average of the squares must also be an integer, and so must the internal angle. So we can keep everything in the integers.

      This Python program runs in 46ms.

      Run: [ @replit ]

      from enigma import Primes, dsum, subsets, div, sq, is_square, printf
      
      primes = Primes(100)
      
      # candidate primes
      ps = list(p for p in primes.irange(10, 99) if dsum(p) in primes)
      
      # choose 4 of them
      for ns in subsets(ps, size=4):
        # calculate the mean, and mean square
        m = div(sum(ns), 4)
        m2 = div(sum(sq(n) for n in ns), 4)
        if m is None or m2 is None: continue
        # standard deviation
        sd = is_square(m2 - sq(m))
        if sd is None: continue
        # internal angle
        a = div(360, m)
        if a is None or a % 2 == 0: continue
      
        # output solution
        printf("{ns}; m={m} m2={m2} sd={sd} a={a}", a=180 - a)
      

      Solution: The “standard deviation” is 15.

      The prime numbers are: 23, 29, 47, 61.

      Which give a mean of 40, and a mean square of 1825. And a regular 40-sided polygon has internal angles of 171°.

      Without the regular polygon condition there are three potential integer solutions:

      (11, 29, 61, 67); m=42 sd=23
      (23, 29, 47, 61); m=40 sd=15
      (29, 43, 61, 67); m=50 sd=15

      A 42-gon and 50-gon don’t have whole number internal angles, so the requirement for the angle to be odd isn’t necessary (although it may help in a manual solution).

      Like

    • Frits's avatar

      Frits 5:44 pm on 26 November 2021 Permalink | Reply

      Starting from the (odd, even) divisor pairs of number 360.

        
      from enigma import divisor_pairs, dsum, is_square
       
      P = {3, 5, 7}
      P |= {2} | {x for x in range(11, 100, 2) if all(x % p for p in P)}
       
      # candidate primes
      P = sorted(p for p in P if p > 9 and dsum(p) in P)
      
      # get_standard_deviation by decomposing number <t> into <k> increasing 
      # numbers from <ns> so that sum(<k> numbers) equals <t>
      def get_standard_deviation(t, k, ns, sq_avg, s=[]):
        if k == 1:
          if t in ns and t > s[-1]:
            # average of the squares minus the square of the average is square
            sd = is_square(sum(x**2 for x in s + [t]) // 4 - sq_avg)
            if sd is not None:
              yield sd
        else:
          for n in ns:
            if s and n <= s[-1]: continue
            yield from get_standard_deviation(t - n, k - 1, ns, sq_avg, s + [n])
      
      min_avg = sum(P[:4]) / 4
      max_avg = sum(P[-4:]) / 4
      penmax_avg = sum([P[-5]] + P[-3:]) / 4   # penultimate maximum
      
      # determine number of sides for all possible angles
      lst_nsides = [nsides for a, nsides in divisor_pairs(360) 
                    if a % 2 and min_avg <= nsides <= max_avg]
      
      # skip smallest angle if number of sides is too close to max_avg
      if penmax_avg < lst_nsides[0] < max_avg: 
        lst_nsides = lst_nsides[1:]
      
      # solve for all possible angles
      for avg in lst_nsides:
        for sd in get_standard_deviation(4 * avg, 4, P, avg**2):
          print("answer:", sd)
      

      Like

  • Unknown's avatar

    Jim Randell 10:22 am on 25 November 2021 Permalink | Reply
    Tags: ,   

    Teaser 2839: Unwholesome 

    From The Sunday Times, 19th February 2017 [link] [link]

    I have written down three positive numbers, the highest of which is an even two-figure number. Also, one of the numbers is the average of the other two. I have calculated the product of the three numbers and the answer is a prime number.

    You might be surprised that the product of three numbers is a prime but, of course, they are not all whole numbers — at least one of them is a fraction.

    What is the largest of the three numbers, and what is the product of the three?

    As presented there are 2 solutions to this puzzle.

    [teaser2839]

     
    • Jim Randell's avatar

      Jim Randell 10:23 am on 25 November 2021 Permalink | Reply

      The largest number is a 2-digit even integer, say 2n.

      And say the smallest number is the fraction p/q (where p and q are co-prime).

      The middle number is the arithmetic mean of these = (p + 2nq)/2q.

      The product is then: p (p + 2nq) n / q².

      And this is an integer. No non-trivial divisor of q can divide into p or (p + 2nq), so n must be divisible by q².

      Furthermore for the product to a prime, we must have: q² = n, and p = 1.

      Leaving the prime product as: (2q³ + 1).

      And the numbers as: a = 1/q; b = (2q³ + 1)/2q; c = 2q².

      We can solve this manually (remembering c is a 2 digit number):

      q=2: c = 8 [too small]
      q=3: c = 18; product = 55 [not prime]
      q=4: c = 32; product = 129 [not prime]
      q=5: c = 50; product = 251 [*** SOLUTION ***]
      q=6: c = 72; product = 433 [*** SOLUTION ***]
      q=7: c = 98; product = 687 [not prime]
      q=8: c = 128 [too big]

      or programmatically:

      Run: [ @replit ]

      from enigma import (irange, inf, is_prime, printf)
      
      for q in irange(2, inf):
        # check c is 2 digits
        c = 2 * q * q
        if c < 10: continue
        if c > 99: break
        # check product is prime
        m = 1 + c * q
        if not is_prime(m): continue
        # output solution
        printf("q={q}; a=1/{q} b={m}/{d} c={c}; product={m}", d=2 * q)
      

      Either way we find there are two solutions:

      Solution: (1) The largest number is 50, and the product of the three numbers is 251; or:
      (2) The largest number is 72, and the product of the three numbers is 433.

      The published solution is: “largest = 50; product = 251”. Apparently the setter was unaware of the second solution.

      Like

  • Unknown's avatar

    Jim Randell 9:06 am on 23 November 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 887: The valetudinarians 

    From The Sunday Times, 6th August 1978 [link]

    “We have just been discussing our health”, said Alf, “and we have discovered that between us we share the same five complaints, and the same prescribed tablets for them. Each of us has two complaints, but no two have both the same, and no more than two of us take each sort of tablets. For instance two take red tablets, two blue, and so on. I do not have green ones. I take one lot the same as Ed, but they are not yellow, and I do not take kidney tablets”.

    Bob said: “I do not have green tablets. One heart patient also has tablets for sleeplessness”.

    Cyril said: “I do not have kidney tablets. I take one lot the same as Ed which are not for the heart. I do not take blue ones”.

    Don said: “I do not have heart trouble. My tablets are not yellow. Those who take green tablets do not also take blue ones”.

    Ed said: “I take white tablets which are not for the heart. The ones with nerves do not have indigestion, and nerve tablets are not yellow”.

    What colour were the heart tablets? Who took those for nerves?

    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.

    [teaser887]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 23 November 2021 Permalink | Reply

      I assumed each colour tablet is prescribed for the same condition in each patient.

      This Python program uses the [[ choose() ]] function from enigma.py, which I thought I would use more when I implemented it. It runs in 65ms.

      Run: [ @replit ]

      from enigma import (subsets, ordered, choose, intersect, singleton, multiset, join, printf)
      
      # tablet colours
      tablets = "BGRWY"
      
      # map complaints to tablets
      for (K, H, N, I, S) in subsets(tablets, size=len, select="P"):
      
        # "white tablets are not for the heart"
        if H == 'W': continue
      
        # "nerve tablets are not yellow"
        if N == 'Y': continue
      
        # there are 2 invalid combinations: nerves + indigestion, green + blue
        invalid = { ordered(N, I), ('B', 'G') }
        values = set(s for s in subsets(tablets, size=2)).difference(invalid)
      
        # choose tablets for each person:
        def fE(Es):
          # E has white
          if 'W' not in Es: return False
          return True
      
        def fA(Es, As):
          # A does not have green or kidney
          if 'G' in As or K in As: return False
          # A has one tablet the same as E, and it isn't yellow
          AE = singleton(intersect([As, Es]))
          if AE is None or AE == 'Y': return False
          return True
      
        def fC(Es, As, Cs):
          # C does not have kidney or blue
          if K in Cs or 'B' in Cs: return False
          # C has one tablet the same as E, and it isn't heart
          CE = singleton(intersect([Cs, Es]))
          if CE is None or CE == H: return False
          return True
      
        def fD(Es, As, Cs, Ds):
          # D does not have heart or yellow
          if H in Ds or 'Y' in Ds: return False
          return True
      
        def fB(Es, As, Cs, Ds, Bs):
          # B does not have green
          if 'G' in Bs: return False
          return True
      
        # make the choices
        for vs in choose(values, [fE, fA, fC, fD, fB], distinct=1):
          # one of them has heart + sleeplessness
          if not (ordered(H, S) in vs): continue
          # each tablet is used by two people
          if not multiset.from_seq(*vs).all_same(2): continue
      
          # output solution
          (Es, As, Cs, Ds, Bs) = (join(v, sep="+") for v in vs)
          printf("A={As} B={Bs} C={Cs} D={Ds} E={Es} [K={K} H={H} N={N} I={I} S={S}]")
      

      Solution: Heart tablets are blue. Cyril and Don took tablets for nerves.

      The complete situation is:

      Alf: blue (heart); yellow (indigestion)
      Bob: red (kidney); yellow (indigestion)
      Cyril: green (nerves); white (sleeplessness)
      Don: green (nerves); red (kidney)
      Ed: blue (heart); white (sleeplessness)

      Like

    • Frits's avatar

      Frits 1:32 pm on 30 November 2021 Permalink | Reply

        
      from itertools import combinations as comb, permutations as perm
      
      # filter on tablet, complaint and shared tablets (with, type, exclusion) 
      def filter(lst, exc_tabl, exc_cmpl=99, shr_with=[], shr_t=0, shr_e={}):
        if shr_with:
          shr_set = {shr_with[0][shr_t], shr_with[1][shr_t]}
          frz_set = {frozenset(), frozenset(shr_e)}
          
        return [(x, y) for x, y in lst if all(e not in {x[i], y[i]} 
               for i, e in enumerate([exc_tabl, exc_cmpl])) and (not shr_with or 
               shr_set.intersection({x[shr_t], y[shr_t]}) not in frz_set)]
      
      guys = ["Alf", "Bob", "Cyril", "Don", "Ed"]
      tablets = ["red", "blue", "green", "white", "yellow"]
      # [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]
      sorted_tablets = [i for i in range(5) for _ in range(2)]
      
      # red    0       indigestion   0
      # blue   1       heart         1
      # green  2       kidney        2
      # white  3       sleeplessness 3 
      # yellow 4       nerves        4
      
      # loop over complaints per tablet
      for p in perm(range(5)):
        # "white tablets are not for the heart"
        # "nerve tablets are not yellow"
        if p[3] == 1 or p[4] == 4: continue
        
        # combine two out of (tablets, complaints) list
        # there are 2 invalid combinations: nerves + indigestion, green + blue
        cmbs = [(x, y) for x, y in comb([(i, z) for i, z in enumerate(p)], 2) 
               if sorted((x[1], y[1])) != [0, 4] and
                  sorted((x[0], y[0])) != [1, 2]]
        
        # one of them has heart + sleeplessness          
        if all(sorted([x[1], y[1]]) != [1, 3] for x, y in cmbs): continue
        
        # E has white
        for E in [(x, y) for x, y in cmbs if 3 in {x[0], y[0]}]:
          notE = set(cmbs).difference({E})
          # A does not have green or kidney
          # A has one tablet the same as E, and it isn't yellow
          for A in filter(notE, 2, 2, E, 0, [4]):
            notAE = set(notE).difference({A})
            # C does not have kidney or blue
            # C has one tablet the same as E, and it isn't heart
            for C in filter(notAE, 1, 2, E, 1, [1]):
              notAEC = set(notAE).difference({C})
              # D does not have heart or yellow
              for D in filter(notAEC, 4, 1):
                notAECD = set(notAEC).difference({D})
                # B does not have green
                for B in filter(notAECD, 2):
                  ABCDE = [A, B, C, D, E]
                  
                  # each tablet is sorted by two people
                  if sorted(z for x, y in ABCDE for z in (x[0], y[0])) != \
                     sorted_tablets: continue
                  
                  # one of them has heart + sleeplessness     
                  if all(sorted([x[1], y[1]]) != [1, 3] for x, y in ABCDE): 
                    continue
                  
                  heart = set(x[0] if x[1] == 1 else y[0] 
                              for x, y in ABCDE if 1 in {x[1], y[1]})
                  if len(heart) != 1: continue 
                  print(f"Heart tablets are {tablets[heart.pop()]}.")
                  
                  nerves = [i for i, (x, y) in enumerate(ABCDE) 
                            if 4 in {x[1], y[1]}]
                  print(f"{guys[nerves[0]]} and {guys[nerves[1]]} "
                        f"took tablets for nerves")
      

      Like

  • Unknown's avatar

    Jim Randell 4:36 pm on 19 November 2021 Permalink | Reply
    Tags:   

    Teaser 3087: Hexagonia 

    From The Sunday Times, 21st November 2021 [link] [link]

    Hexagonia is a hexagonal republic and is divided into 24 numbered counties, as shown. The counties are to be reorganised into four departments, each composed of six edge-connected counties. No two departments will have the same shape or be reflections of each other, and the president’s residence in Department A will be built on an axis of symmetry of the department. Every sum of the county numbers in a department will be, in the prime minister’s honour, a prime number, and her mansion will be built in the middle of a county in Department B, on an axis of symmetry of the department, and as far as possible from the president’s residence.

    In what county will the Prime Minister’s mansion be built?

    This is the next puzzle after Teaser 2401 whose number has the property described in Teaser 2700.

    [teaser3087]

     
    • Jim Randell's avatar

      Jim Randell 5:30 pm on 19 November 2021 Permalink | Reply

      It looks like I implemented my polyiamonds.py module at just the right time (see Teaser 2838).

      I added the other hexiamonds to it and then used it to generate the only possible map. From this we see there are only 2 achiral shapes involved, and only one of them has an axis of symmetry that passes through the counties, so the required answer is determined directly from the map.

      The following Python program runs in 1.16s. Although by limiting the sets of pieces tested to those containing 2 chiral shapes, this can be reduced to 668ms.

      from enigma import (subsets, join, primes, printf)
      import polyiamonds
      
      # collect the possible hexiamonds
      shapes = polyiamonds.shapes("O6 I6 C6 E6 F6 G6 H6 J6 P6 S6 V6 X6".split())
      
      # map cells of the 24-hex grid to county numbers
      cells = [
        (0, 6), (0, 7), (1, 6), (1, 7), (2, 6), # 1 - 5
        (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), # 6 - 12
        (0, 3), (1, 2), (1, 3), (2, 2), (2, 3), (3, 2), (3, 3), # 13 - 19
        (1, 1), (2, 0), (2, 1), (3, 0), (3, 1), # 20 - 24
      ]
      county = dict((cell, i) for (i, cell) in enumerate(cells, start=1))
      grid = set(cells)
      
      primes.extend(130)
      
      # choose 4 of the shapes
      for ps in subsets(shapes.keys(), size=4):
      
        # attempt to fit the shapes into the grid
        ss = list(shapes[p] for p in ps)
        for g in polyiamonds.fit(ss, grid):
          # the sum of the counties in each region should be prime
          t = dict((n, 0) for n in (1, 2, 3, 4))
          for (k, v) in g.items():
            t[v] += county[k]
          if not all(v in primes for v in t.values()): continue
      
          printf("{ps}\n", ps=join(ps, sep=" ", enc="[]"))
          polyiamonds.output_grid(g)
      

      Solution: The Prime Minister’s Mansion is in county 16.

      The layout of the departments is as follows:

      The departments have the following prime totals: blue (H6) = 31; red (C6) = 61; green (E6) = 101; orange (J6) = 107.

      The two departments with axes of symmetry are the red one (C6) and the green one (E6).

      The red one’s axis is along the 6/13 border, so this gives us the location of the President’s Residence (so Department A is red), and the axis in the green department (Department B) is indicated with the dotted line. This passes through counties 15 and 16, but we want to be as far from the President’s Residence as possible, so the Prime Minister’s Mansion must be in county 16.

      In fact C6 is the only achiral piece that can give a prime total, and has an axis on a border, so C6 must be involved in the map with one of E6 or V6 (which are the remaining achiral pieces with axes that pass through cells). And the remaining 2 counties must be chosen from F6, G6, H6, I6, J6, P6, S6 (chiral).

      Like

      • Jim Randell's avatar

        Jim Randell 10:28 pm on 19 November 2021 Permalink | Reply

        Here is a faster version, where we only consider combinations of 2 achiral and 2 chiral pieces, and positions of pieces in the grid are only considered if the counties involved have a prime sum.

        It runs in 67ms.

        from enigma import (subsets, join, primes, defaultdict, intersect, exact_cover, printf)
        import polyiamonds
        
        # collect the possible hexiamonds
        achiral = "O6 C6 E6 V6 X6".split()
        chiral = "I6 F6 G6 H6 J6 P6 S6".split()
        shapes = polyiamonds.shapes(achiral + chiral)
        
        # map cells of the 24-hex grid to county numbers
        cells = [
          (0, 6), (0, 7), (1, 6), (1, 7), (2, 6), # 1 - 5
          (0, 4), (0, 5), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), # 6 - 12
          (0, 3), (1, 2), (1, 3), (2, 2), (2, 3), (3, 2), (3, 3), # 13 - 19
          (1, 1), (2, 0), (2, 1), (3, 0), (3, 1), # 20 - 24
        ]
        county = dict((cell, i) for (i, cell) in enumerate(cells, start=1))
        grid = set(cells)
        
        primes.extend(130)
        
        # look for placements that give a prime total
        ss = defaultdict(list)
        for (k, vs) in shapes.items():
          for cs in polyiamonds.placements(vs, grid):
            if sum(county[c] for c in cs) in primes:
              ss[k].append(cs)
        
        achiral = sorted(intersect([achiral, ss.keys()]))
        chiral = sorted(intersect([chiral, ss.keys()]))
        
        # choose 2 achiral shapes
        for ps1 in subsets(achiral, size=2):
          # and the rest are chiral
          for ps2 in subsets(chiral, size=2):
            ps = ps1 + ps2
            # look for an exact cover using these pieces
            for rs in exact_cover(list(ss[p] for p in ps), grid):
              # output solution
              printf("{ps}\n", ps=join(ps, sep=" ", enc="[]"))
              g = dict()
              for (i, cs) in enumerate(rs, start=1):
                g.update((c, i) for c in cs)
              polyiamonds.output_grid(g)
        

        Like

      • Frits's avatar

        Frits 5:20 pm on 20 November 2021 Permalink | Reply

        @Jim, you don’t answer the question

        Like

        • Jim Randell's avatar

          Jim Randell 5:48 pm on 20 November 2021 Permalink | Reply

          @Frits: I reveal the answers to competition puzzles after the deadline for entries is closed.

          Like

          • Frits's avatar

            Frits 6:57 pm on 20 November 2021 Permalink | Reply

            @Jim, I mean that your program doesn’t seem to print in what county will the Prime Minister’s mansion be built. You normally don’t hide the answer in your program output. It doesn’t matter.

            Like

  • Unknown's avatar

    Jim Randell 9:09 am on 18 November 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 884: Mice in the works 

    From The Sunday Times, 16th July 1978 [link]

    Recently a hot-drink vending machine was installed in our office. Very nice it is too — completely up to date it was when it was bought. There are five switches, a slot for your money, and a button. The switches are labelled TEA, COFFEE, CHOCOLATE, MILK and SUGAR, and you select the combination you want, put in your money, press the button, and out comes your drink. Why, you can even have coffolatea if you want!

    At least, this is the idea. Unfortunately, during the ten years it has been in store, “awaiting approval”, mice have chewed up the wiring. Mice with soldering irons, I should think. The result is now that no switch affects its “own” ingredient at all, but instead turns on two other ingredients, each ingredient being turned on by two different switches. However, if two switches are set which turn on the same ingredient, then they cancel each other out, and that ingredient doesn’t come out at all.

    The result is somewhat chaotic, though occasionally some of the output is actually drinkable. For instance, when you ask for white sweet coffee, you get unsweetened milky tea; when you ask for sweet milky chocolate, you get sweet chocolate without milk; and when you ask for unsweetened milky tea you get a glorious gooey mocha — i.e. chocolate and coffee with milk and sugar.

    Luckily, pressing the “deliver” button reinstates the original chaos, so that setting the same switches always gives the same results.

    So, what is the easiest way to get white coffee without sugar? (i.e. Name the fewest switches that will deliver just coffee and milk).

    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.

    [teaser884]

     
    • Jim Randell's avatar

      Jim Randell 9:12 am on 18 November 2021 Permalink | Reply

      We’ve seen puzzles like this before. See Enigma 91, Puzzle #103 (although this puzzle was published before either of them).

      I reused the code I wrote for Puzzle #103.

      The following Python 3 program runs in 60ms.

      Run: [ @replit ]

      from enigma import (multiset, subsets, diff, update, ordered, join, map2str, printf)
      
      # buttons
      buttons = 'T Cf Ch M S'.split()
      
      # map each value in <ks> to <n> other values chosen from mutliset <vs>
      # return map d: <ks> -> <n>-tuples from <vs>
      def make_map(ks, vs, n=2, d=dict()):
        # are we done?
        if not ks:
          yield d
        else:
          # choose n values for the next key
          k = ks[0]
          for ss in subsets(diff(vs.keys(), [k]), size=n):
            yield from make_map(ks[1:], vs.difference(ss), n, update(d, [(k, ordered(*ss))]))
      
      # outcome of selecting buttons <bs> using map <d>
      def select(d, bs):
        m = multiset()
        for b in bs:
          m.update_from_seq(d[b])
        return set(k for (k, v) in m.items() if v == 1)
      
      # consider possible maps
      for d in make_map(buttons, multiset.from_seq(buttons, count=2)):
      
        # select M+S+Cf -> M+T
        if not (select(d, ['M', 'S', 'Cf']) == {'M', 'T'}): continue
      
        # select S+M+Ch -> S+Ch
        if not (select(d, ['S', 'M', 'Ch']) == {'S', 'Ch'}): continue
      
        # select M+T -> Ch+Cf+M+S
        if not (select(d, ['M', 'T']) == {'Ch', 'Cf', 'M', 'S'}): continue
      
        # answer, easiest way to get Cf+M?
        for bs in subsets(buttons, min_size=1):
          if select(d, bs) == {'Cf', 'M'}:
            fmt = lambda s: join(sorted(s), sep="+")
            printf("{bs} -> Cf+M {d}", bs=fmt(bs), d=map2str(((k, fmt(v)) for (k, v) in d.items()), arr="->", enc="[]"))
      

      Solution: The easiest way to get coffee and milk is to select “Coffee” and “Milk”.

      There is also a combination of 3 buttons that will give coffee and milk: “Chocolate”, “Tea” and “Sugar”.

      The items delivered by the buttons are:

      Coffee: Chocolate + Milk
      Chocolate: Tea + Sugar
      Milk: Coffee + Chocolate
      Sugar: Coffee + Tea
      Tea: Milk + Sugar

      Like

    • John Crabtree's avatar

      John Crabtree 5:44 pm on 25 November 2021 Permalink | Reply

      Using the same switch twice selects nothing. Using all five switches selects nothing. Call this statement S4
      Using all of the switches in statements S1, S2, S3 and S4 results in: using S gives Cf and T
      S1 reduces to: using Cf and M gives Cf and M
      This is one possible answer, but we do not know that it is the shortest.
      From S1 and S, using M gives Cf
      From S2 and S, using M gives Ch as well as Cf (from above)
      From S1, using Cf gives Ch and M
      From S2, using Ch gives S and T
      From S3, using T gives M and S

      Since no one button gives Coffee with Milk, pressing Coffee and Milk is the simplest way to get Coffee with Milk

      This solution is much simpler that the official one in the book.

      Like

  • Unknown's avatar

    Jim Randell 9:08 am on 16 November 2021 Permalink | Reply
    Tags:   

    Teaser 2838: King Lear IV 

    From The Sunday Times, 12th February 2017 [link] [link]

    King Lear IV’s realm consisted of a regular hexagon divided into 24 counties that were equal-sized equilateral triangles. In his will he wanted to share the counties among his six daughters, each daughter’s portion having the property that, if you walked in a straight line between any two points in it, then you remained in her portion. If two daughters’ portions had the same area then they had to be of different shapes (and not the mirror image of each other). He wanted Cordelia to have a single county (his favourite county on the edge of the kingdom), he wanted Goneril to have a hexagonal-shaped portion, and he knew the number of counties he wanted to allocate to each of the other daughters, with Reagan’s portion being the largest of all. It turned out that his requirements uniquely determined Goneril’s and Reagan’s counties.

    What, in increasing order, were the numbers of counties allocated to the six daughters?

    [teaser2838]

     
    • Jim Randell's avatar

      Jim Randell 9:10 am on 16 November 2021 Permalink | Reply

      I originally solved this puzzle using the Polyform Puzzler framework [link], the problem then becomes how to interface to framework, but it allowed me to quickly produce a solution using a variation on the code I had previously written for Enigma 321.

      But having subsequently packaged my polyominoes code written for Enigma 321 into a separate module, I thought I could do the same for polyiamonds.

      So I wrote a package polyiamonds.py [@github] that knows enough about the convex shapes that fit into the 24-hex grid, and used that to solve this puzzle.

      Here is a reference for polyiamonds [link]. I used the same names, but added octiamonds “d8” (= “diamond”) and “t8” (= “trapezium”) to produce a complete set of convex polyiamonds that will fit in the required hexagonal grid. In total there are 12 possible shapes.

      The program looks for 4 additional pieces to go with “T1” and “O6” (which we are told are there), and one of these pieces must be larger than “O6”. The position of the “T1” piece is fixed along the edge of grid. The program then looks for packings where the positions of Regan’s (the largest piece) and Goneril’s piece (“O6”) are uniquely determined. It runs in 67ms.

      from enigma import (defaultdict, subsets, diff, union, irange, ordered, join, printf)
      import polyiamonds
      
      # convex polyiamonds up to size 8 that fit in the hexagon
      pieces = {
        "T1": 1,
        "D2": 2,
        "I3": 3,
        "T4": 4, "I4": 4,
        "I5": 5,
        "O6": 6, "I6": 6,
        "I7": 7, "D7": 7,
        "d8": 8, "t8": 8,
      }
      
      # collect the shapes
      shapes = polyiamonds.shapes(pieces.keys())
      
      # make the hexagonal grid
      grid = union(set((x, y) for y in irange(y1, y2)) for (x, y1, y2) in [(0, 3, 7), (1, 1, 7), (2, 0, 6), (3, 0, 4)])
      # remove (1, 1) for T1
      grid.remove((1, 1))
      
      # accumulate results by the position of G (= O6) and R (= largest)
      def key(g, ps):
        cells = lambda g, k: ordered(*(c for (c, n) in g.items() if n == k))
        return (cells(g, ps.index("O6") + 1), cells(g, len(ps)))
      
      # choose 4 pieces to go with T1 and O6
      r = defaultdict(set)
      for ps in subsets(diff(pieces.keys(), ["T1", "O6"]), size=4, fn=list):
        # check the size
        ss = list(pieces[p] for p in ps)
        m = max(ss)
        if not (m > 6 and sum(ss) == 17 and ss.count(m) == 1): continue
      
        # attempt to fit the shapes into the grid
        ps.extend(["T1", "O6"])
        ps = sorted(ps, key=lambda p: pieces[p])
        ks = tuple(pieces[p] for p in ps)
        printf("{ps} -> {ks}\n", ps=join(ps, sep=" ", enc="[]"), ks=join(ks, sep=" ", enc="()"))
        n = 0
        for g in polyiamonds.fit(list(shapes[p] for p in ps if p != "T1"), grid, start=2):
          r[ks].add(key(g, ps))
          g[(1, 1)] = 1 # place T1 as 1
          polyiamonds.output_grid(g)
          n += 1
        printf("[{n} ways]\n")
      
      # look for a unique solution
      for (k, vs) in r.items():
        if len(vs) == 1:
          printf("solution = {k}")
      

      Solution: The numbers of counties allocated to the daughters are: 1, 2, 4, 4, 6, 7.

      I found three allocations that allow the grid to be packed. They are summarised in the following diagram:

      (Click to enlarge).

      The allocation that has only one way to pack the grid gives the solution.

      Like

  • Unknown's avatar

    Jim Randell 2:26 pm on 14 November 2021 Permalink | Reply
    Tags:   

    Teaser 2505: [Letter values] 

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

    I have taken 15 letters of the alphabet and given each of them a numerical value. They are not all positive, they are not all whole numbers and they are not all different. In fact, four of the letters have the same positive value.

    Using these values, I can work out the total value of some words by adding up the values of their letters.

    So, ONE has total value 1, TWO has total value 2, and so on up to SEVENTEEN, which has total value 17.

    What is the value of NOUGHT?

    This puzzle was originally published with no title.

    [teaser2505]

     
    • Jim Randell's avatar

      Jim Randell 2:27 pm on 14 November 2021 Permalink | Reply

      See also: Teaser 1908, Teaser 2756, Enigma 1602, Enigma 1278, Enigma 1292.

      It turns out that choosing 2 symbols to be the same, and then checking for other values with the same value as the 2 we chose is much faster than trying all combinations of 4 symbols.

      The following Python program runs in 105ms.

      Run: [ @replit ]

      from enigma import (irange, int2words, union, matrix, subsets, trim, join, map2str, printf)
      
      # we are interested in the values of the words "one" ... "seventeen"
      words = dict((i, int2words(i)) for i in irange(1, 17))
      
      # determine symbols used
      symbols = sorted(union(words.values()))
      n = len(symbols)
      
      # a function for making equations
      eq = matrix.equation(symbols)
      
      eqs = list(eq(w, i) for (i, w) in words.items())
      
      # choose the first k symbols to have the same (positive) value
      k = 2
      for ss in subsets(trim(symbols, tail=4 - k), size=k, fn=list):
        s0 = ss[0]
        eqs_ = list(eq ({s0: -1, s: 1}) for s in ss[1:])
      
        try:
          vs = matrix.linear(eqs + eqs_)
        except ValueError:
          continue
      
        # check the shared value is positive, and used exactly 4 times
        m = dict(zip(symbols, vs))
        v = m[s0]
        sv = sorted(s for s in symbols if m[s] == v)
        if not (v > 0 and len(sv) == 4 and sv[:k] == ss): continue
      
        # output solution
        ans = sum(m[s] for s in 'nought')
        printf("nought={ans} {vs} {sv}",
          vs=map2str(m, sep=" ", enc="[]"),
          sv=join(sv, sep="=", enc="[]"),
        )
      

      Solution: NOUGHT = 23/2 (= 11.5).

      The values are:

      E = I = S = W = 0
      F = R = U = V = 5/2
      G = 15/2
      H = −5
      L = 4
      N = 9/2
      O = −7/2
      T = 11/2
      X = 6

      So the four letters that share a positive value are F, R, U, V.

      (E, I, S, W, also share a value, but this is zero).


      We immediately see that E = 0. (As 14 = 4 + 10, 16 = 6 + 10, 17 = 7 + 10), and then I = 0 (as 13 = 3 + 10). So we could exclude these from the letters we consider when we choose some of them to be the same, to give a slight speed improvement.

      This observation can also form the start of a manual solution.

      Like

      • Frits's avatar

        Frits 3:33 pm on 15 November 2021 Permalink | Reply

        @Jim, symbols[:n + k – 4] makes more sense to me. fi, now you miss the situation e, v, w and x being the same.

        Like

        • Jim Randell's avatar

          Jim Randell 3:45 pm on 15 November 2021 Permalink | Reply

          @Frits: Yes it should be [:n + k − 4].

          I might add a [[ trim(s, head, tail) ]] function to enigma.py to make it easier to avoid these “off-by-1” errors (which are all too easy to make with Python indices).

          Like

  • Unknown's avatar

    Jim Randell 4:05 pm on 12 November 2021 Permalink | Reply
    Tags:   

    Teaser 3086: Four-sided dice game 

    From The Sunday Times, 14th November 2021 [link] [link]

    I have two identical four-sided dice (regular tetrahedra). Each die has the numbers 1 to 4 on the faces. Nia and Rhys play a game in which each of them takes turns throwing the two dice, scoring the sum of the two hidden numbers. After each has thrown both dice three times, if only one of them scores an agreed total or over, then he or she is the winner. Otherwise the game is drawn.

    After Rhys had taken two throws, before Nia took her second throw she noticed that they both had a chance of winning, but if things had gone differently, they could have reached a situation where Rhys had scored a lower total on his two throws, and Nia had scored twice her current total with a single throw, then in that situation Nia’s chance of winning would be 35 times what it was in reality.

    What are Nia’s and Rhys’ actual scores (so far) and what is the agreed winning total?

    I have modified the wording of this puzzle slightly to make it clearer (hopefully).

    [teaser3086]

     
    • Jim Randell's avatar

      Jim Randell 6:05 pm on 12 November 2021 Permalink | Reply

      After a couple of false starts with no solution found, I decided that we are looking for a hypothetical probability of N winning that is 35× what the actual probability is.

      This Python program runs in 58ms.

      Run: [ @replit ]

      from enigma import (Rational, multiset, subsets, multiply, printf)
      
      Q = Rational()
      
      # possible scores and their frequency for a single throw of the pair
      scores = multiset.from_seq(a + b for (a, b) in subsets((1, 2, 3, 4), size=2, select="M"))
      T = scores.size()
      
      # possible totals from 1, 2 throws
      total1 = set(scores.keys())
      total2 = set(sum(s) for s in subsets(total1, size=2, select="R"))
      total3 = set(sum(s) for s in subsets(total1, size=3, select="R"))
      
      # calculate probabilities of reaching target <tgt> from totals <ts> with <k> additional throws
      def probabilities(tgt, ts, k):
        d = T ** k
        ps = dict()
        for t in ts:
          n = sum(multiply(scores[x] for x in xs) for xs in subsets(total1, size=k, select="M") if t + sum(xs) >= tgt)
          ps[t] = Q(n, d)
        return ps
      
      # consider possible targets
      for tgt in total3:
      
        # probabilities of reaching target after one and two throws
        p1 = probabilities(tgt, total1, 2)
        p2 = probabilities(tgt, total2, 1)
      
        # look for possible N totals, where 2N is also possible
        for (N, pN) in p1.items():
          if not (0 < pN < 1): continue
          pN_ = p1.get(2 * N)
          if pN_ is None: continue
      
          # consider possible R, pR values
          for (R, pR) in p2.items():
            if not (0 < pR < 1): continue
      
            # calculate N's actual probability of winning
            w = pN * (1 - pR)
            w_ = 35 * w
            if w_ > 1: continue
      
            # look for lower hypothetical R_ value, that gives N a probability of w_
            for (R_, pR_) in p2.items():
              if not (R_ < R and pN_ * (1 - pR_) == w_): continue
      
              # output solution
              printf("tgt={tgt}; N={N} pN={pN} pN_={pN_}; R={R} pR={pR}; R_={R_} pR_={pR_}")
      

      Solution: Nia has scored 2 on her single throw. Rhys has scored 13 from his two throws. The target total is 17.

      N has a 5/256 (≈ 2.0%) probability of reaching the target, and R’s probability would be 13/16 (≈ 81%).

      So N’s overall probability of winning is: (5/256)(3/16) = 15/4096 (≈ 0.37%).

      However if N had scored twice as much on her throw (i.e. 4) she would have a 35/256 (≈ 14%) probability of reaching the target, and if R had scored only 9 on his two throws, his probability would be 1/16 (≈ 6.3%).

      And the overall probability of N winning in this situation would be: (35/256)(15/16) = 525/4096 (≈ 13%).

      And we see: 35 × 15/4096 = 525/4096, as required.

      Like

      • Jim Randell's avatar

        Jim Randell 11:13 am on 14 November 2021 Permalink | Reply

        Here is a faster version of my program. It avoids recalculation of probabilities, and keeps values in the integers.

        Run: [ @replit ]

        from enigma import (multiset, multiply, subsets, cached, printf)
        
        # possible scores, and their frequency for 1 throw of 2 dice
        scores = multiset.from_seq(a + b for (a, b) in subsets((1, 2, 3, 4), size=2, select="M"))
        T = scores.size()
        T2 = T * T
        T3 = T * T2
        
        # possible totals from 1, 2, 3 throws
        total = { 1: sorted(scores.keys()) }
        total.update((i, sorted(set(sum(s) for s in subsets(total[1], size=i, select="R")))) for i in (2, 3))
        
        # probability of scoring n or more in k throws
        @cached
        def prob(n, k):
          if n < 0: return 0
          return sum(multiply(scores[x] for x in xs) for xs in subsets(total[1], size=k, select="M") if sum(xs) >= n)
        
        # consider possible targets
        for tgt in total[3]:
        
          # consider actual total for N after 1 throw
          for N in total[1]:
            N_ = 2 * N
            if not (N_ in total[1]): continue
            # calculate N's actual probability of reaching the target in 2 throws
            pN = prob(tgt - N, 2) # / T2
            if not (0 < pN < T2): continue
        
            # calculate N's hypothetical probability of reaching the target in 2 throws
            pN_ = prob(tgt - N_, 2) # / T2
        
            # consider possible R, pR values    
            for R in total[2]:
              pR = prob(tgt - R, 1) # / T
              if not (0 < pR < T): continue
        
              # calculate N's actual probability of winning
              w = pN * (T - pR) # / T3
              w_ = 35 * w
              if w_ > T3: continue
        
              # look for lower hypothetical R_ value, that gives N a probability of w_
              for R_ in total[2]: 
                if not (R_ < R): break
                pR_ = prob(tgt - R_, 1)
                if not (pN_ * (T - pR_) == w_): continue
        
                # output solution
                printf("tgt={tgt}; N={N} pN={pN}/{T2} N_={N_} pN_={pN_}/{T2}; R={R} pR={pR}/{T}; R_={R_} pR_={pR_}/{T}")
        

        Like

        • Frits's avatar

          Frits 11:03 pm on 14 November 2021 Permalink | Reply

          @Jim, your Friday night version is still the fastest when run with PyPy (timeit, 20 times 8 executions). My program also benefits from the @cached function (do you know if there is a @cached variant for lambda functions?)

          Like

          • Jim Randell's avatar

            Jim Randell 9:27 am on 15 November 2021 Permalink | Reply

            I am currently targeting CPython 3.9 as my primary platform (although I am expecting to switch to CPython 3.10 before long), but I also like my code to run on older versions of Python (including Python 2.7 where possible).

            I measured the internal run time of my original program as 7.1ms and my faster version as 1.1ms. So I was happy enough with the performance improvement. And both times are dwarfed by the overhead of running Python.

            The [[ cached ]] function can be called directly as well as used as a decorator. For example, in Teaser 3081:

            recurring = cached(lambda n: enigma.recurring(1, n, recur=1))
            

            Note that if you are targeting a Python 3 environment you can use [[ functools.lru_cache ]] which comes with the Python standard library.

            I’ve added the [[ cache() ]] function to enigma.py which will use [[ functools.cache ]] if available (it was added in Python 3.9), otherwise it uses [[ enigma.cached ]].

            Like

    • Frits's avatar

      Frits 12:32 pm on 13 November 2021 Permalink | Reply

      Not storing the product structures.
      I have let the winning total start from 7, I didn’t see an easy deduction to use a better starting value.

        
      rom itertools import product
      from fractions import Fraction as RF
      
      N = 4         # number of sides
      GC = 35       # greater chance of winning
      
      # number of times out of N**rep attempts you will reach the limit
      ntimes = lambda rep, limit: sum(sum(p2) >= limit
                                  for p2 in product(range(1, N + 1), repeat=rep)) 
      
      # loop over winning totals
      for wt in range(7, 6 * N + 1):
        for nia in range(2, N + 1): 
          # number of times out of N**4 tries Nia will reach the winning total
          nt_nia = ntimes(4, wt - nia)        
          # they both have a chance of winning
          if nt_nia == 0: continue
         
          nt_nia_double = ntimes(4, wt - 2 * nia) 
         
          # GC * w1 = w2
          # nt_nia * GC / nt_nia_double = (N**2 - nt_rhys_less) / (N**2 - nt_rhys))
          if not (1 < nt_nia * GC / nt_nia_double <= N**2): continue
             
          # w1 * GC <= 1 so nt_nia * (N**2 - nt_rhys) * GC / N**6 <= 1
          # (N**2 - nt_rhys) <= N**6 / (GC * nt_nia)
          min_nt_rhys = N**2 - N**6 / (GC * nt_nia)
         
          for rhys in range(5, 4 * N + 1):
            # number of times out of N**2 attempts Rhys will reach the winning total
            nt_rhys = ntimes(2, wt - rhys)   
            # they both have a chance of winning
            if nt_rhys == 0 or nt_rhys < min_nt_rhys: continue        
       
            # calculate Nia's winning chance
            w1 = RF(nt_nia * (N**2 - nt_rhys), N**6)
            if w1 == 0: break
           
            for rhys_less in range(4, rhys):
              nt_rhys_less = ntimes(2, wt - rhys_less)    
             
              # calculate Nia's winning chance under better conditions
              w2 = RF(nt_nia_double * (N**2 - nt_rhys_less), N**6)
             
              if w2 == GC * w1:
                print(f"Nia's score: {nia}, Rhys's score: {rhys}"
                      f" and the agreed winning total: {wt}")
              elif w2 < GC * w1:
                break
      

      Like

    • Hugh Casement's avatar

      Hugh Casement 12:58 pm on 14 November 2021 Permalink | Reply

      “35 times greater” would mean 36 times as much. Just the sort of silly expression that journalists use. Altogether badly worded.

      Like

      • Jim Randell's avatar

        Jim Randell 1:28 pm on 14 November 2021 Permalink | Reply

        Yes. I suppose “35 times” will do. (We can see it must have a greater value, as N has a non-zero actual probability of winning).

        I did a quick check and there is no solution to the puzzle where N’s hypothetical probability is 36× the actual probability.

        Originally I was thrown by what were were comparing N’s hypothetical probability to. (I thought maybe it was R’s hypothetical probability, bit it turns out it is N’s actual probability).

        Like

  • Unknown's avatar

    Jim Randell 9:11 am on 11 November 2021 Permalink | Reply
    Tags: by: J P Mernagh   

    Brain-Teaser 883: Goals galore 

    From The Sunday Times, 9th July 1978 [link]

    We’ve grown tired of these low-scoring, defensive football matches in our locality, and so it was agreed last March for the annual competition between the five villages, each playing the others once, that no goalkeepers would be allowed, and each game would continue until nine goals had been scored.

    Each village won 2 matches, and scored a different number of goals in each match. In the games, each possible result occurred twice. We had to decide the tournament on the total of goals scored, and happily, all five totals were different.

    Blackton, the eventual champions, lost 2-7 to Appleton. Easton were last with 11 goals fewer than Blackton.

    The Drafton centre-forward remarkably scored a hat-trick in each match, which included a last-second winner against Blackton.

    Caxton scored in every match and could indeed have won the league if they had scored twice more in their match against Blackton. As it was they finished one goal ahead of Drafton in the final totals.

    What was the score between Easton and Appleton; and what was the score between Caxton and Drafton?

    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.

    [teaser883]

     
    • Jim Randell's avatar

      Jim Randell 9:12 am on 11 November 2021 Permalink | Reply

      I used the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this puzzle.

      The following run file executes in 357ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      # let the matches be:
      #
      #  A vs B = F - G
      #  A vs C = H - I
      #  A vs D = J - K
      #  A vs E = L - M
      #  B vs C = N - P
      #  B vs D = Q - R
      #  B vs E = S - T
      #  C vs D = U - V
      #  C vs E = W - X
      #  D vs E = Y - Z
      
      SubstitutedExpression
      
      # each village scored a different number of goals in each match
      # (and so they also each conceded a different number of goals in each match)
      --distinct="FHJL,GNQS,IPUW,KRVY,MTXZ,GIKM,FPRT,HNVX,JQUZ,LSWY"
      
      # each match goes to 9-goals
      "9 - F = G"
      "9 - I = H"
      "9 - K = J"
      "9 - L = M"
      "9 - P = N"
      "9 - R = Q"
      "9 - T = S"
      "9 - U = V"
      "9 - W = X"
      "9 - Y = Z"
      
      # each team won 2 matches
      "sum([F > 4, H > 4, J > 4, L > 4]) = 2"
      "sum([G > 4, N > 4, Q > 4, S > 4]) = 2"
      "sum([I > 4, P > 4, U > 4, W > 4]) = 2"
      "sum([K > 4, R > 4, V > 4, Y > 4]) = 2"
      "sum([M > 4, T > 4, X > 4, Z > 4]) = 2"
      
      # each result occurred twice
      --code="twice = lambda *ss: multiset.from_seq(ordered(*s) for s in ss).all_same(2)"
      "twice((F, G), (H, I), (J, K), (L, M), (N, P), (Q, R), (S, T), (U, V), (W, X), (Y, Z))"
      
      # each side scored a different number of goals
      "all_different(F + H + J + L, G + N + Q + S, I + P + U + W, K + R + V + Y, M + T + X + Z)"
      
      # A vs B = 7 - 2
      --assign="F,7"
      --assign="G,2"
      
      # D beat B
      --invalid="0-4,R"
      --invalid="5-9,Q"
      
      # D had a hat-trick in each match
      --invalid="0-2,KRVY"
      --invalid="7-9,JQUZ"
      
      # E has 11 goals fewer than B"
      "(G + N + Q + S) - (M + T + X + Z) = 11"
      
      # C finished 1 goal ahead of D
      "(I + P + U + W) - (K + R + V + Y) = 1"
      
      # B were the eventual champions
      "G + N + Q + S > max(F + H + J + L, I + P + U + W)"
      
      # C scored in every match
      --invalid="0,IPUW"
      --invalid="9,HNVX"
      
      # C could have scored 2 more goals against B ...
      --invalid="8-9,P"
      --invalid="0-1,N"
      # ... and been champions
      "I + P + U + W + 2 > max(F + H + J + L, G + N + Q + S - 2, I + P + U + W)"
      
      # [optional] neater output
      --template="F-G H-I J-K L-M; N-P Q-R S-T; U-V W-X; Y-Z"
      --solution=""
      

      Solution: Appleton vs. Easton = 3 – 6; Caxton vs. Grafton = 2 – 7.

      The full results are:

      A vs B = 7 – 2
      A vs C = 0 – 9
      A vs D = 6 – 3
      A vs E = 3 – 6
      B vs C = 8 – 1
      B vs D = 4 – 5
      B vs E = 9 – 0
      C vs D = 2 – 7
      C vs E = 8 – 1
      D vs E = 4 – 5

      B = 23 for; 13 against
      C = 20 for; 16 against
      D = 19 for; 17 against
      A = 16 for; 20 against
      E = 12 for; 24 against

      Like

    • Frits's avatar

      Frits 12:28 pm on 12 November 2021 Permalink | Reply

      Similar but with fewer variables (including the hat-trick requirement).

        
      #!/usr/bin/env python3 -m enigma -r
      
      # let the matches be:
      #
      #  A vs B = F - ..
      #  A vs C = G - ..
      #  A vs D = H - ..
      #  A vs E = I - ..
      #  B vs C = J - ..
      #  B vs D = K - ..
      #  B vs E = L - ..
      #  C vs D = M - ..
      #  C vs E = N - ..
      #  D vs E = O - ..
      SubstitutedExpression
      
      # each village scored a different number of goals in each match
      # (and so they also each conceded a different number of goals in each match)
      --distinct="FGHI, JKL, MN"
      
      "9 - F not in {J, K, L}"
      "all_different(9 - G, 9 - J, M, N)"
      "all_different(9 - H, 9 - K, 9 - M, O)"
      "all_different(9 - I, 9 - L, 9 - N, 9 - O)"
      
      # each team won 2 matches
      "sum([F > 4, G > 4, H > 4, I > 4]) = 2"
      "sum([9 - F > 4, J > 4, K > 4, L > 4]) = 2"
      "sum([9 - G > 4, 9 - J > 4, M > 4, N > 4]) = 2"
      "sum([9 - H > 4, 9 - K > 4, 9 - M > 4, O > 4]) = 2"
      "sum([9 - I > 4, 9 - L > 4, 9 - N > 4, 9 - O > 4]) = 2"
      
      # each result occurred twice
      --code="twice = lambda *y: sorted(sorted(x) for x in y) == \
                      sorted([[0, 9], [1, 8], [2, 7], [3, 6], [4, 5]] * 2)"
      "twice((F, 9-F), (G, 9-G), (H, 9-H), (I, 9-I), (J, 9-J), (9-K, K), 
             (9-L, L), (9-M, M), (N, 9-N), (O, 9-O))"
      
      # each side scored a different number of goals
      "all_different(F + G + H + J, 
                     9 - F + J + K + L, 
                     18 - G - J + M + N, 
                     27 - H - K - M + O, 
                     36 - I - L - N - O)"
      
      # A vs B = 7 - 2
      --assign="F,7"
      
      # D beat B and scored at least 3 goals in each match
      --invalid="5-9,K"
      --invalid="0-2,O"
      --invalid="7-9,HM"
      
      # E has 11 goals fewer than B"
      "11 - (9 - F + J + K + L - (36 - I - L - N)) = O"
      
      # C finished 1 goal ahead of D
      "(18 - J + M + N) - (27 - H - K - M + O) - 1 = G"
      
      # B were the eventual champions
      "9 - F + J + K + L > max(F + G + H + J, 18 - G - J + M + N)"
      
      # C scored in every match
      --invalid="0,MN"
      --invalid="9,GJ"
      
      # C could have scored 2 more goals against B ...
      --invalid="0-1,J"
      # ... and been champions
      "20 - G - J + M + N > max(F + G + H + J, 7 - F + J + K + L)"
      
      # [optional] neater output
      --template="F, G, H, I, J, K, L, M, N, O"
      --solution=""
      

      Like

  • Unknown's avatar

    Jim Randell 8:46 am on 9 November 2021 Permalink | Reply
    Tags:   

    Teaser 2834: Degrees of freedom 

    From The Sunday Times, 15th January 2017 [link] [link]

    I bought an odd thermometer from an old curiosity shop. On its linear scale the freezing point and boiling point of water were higher than they are on the centigrade scale. In fact the freezing point was a prime number and, higher up the scale, the boiling point was a perfect square. There was only one number on the scale where it actually agreed with the corresponding centigrade temperature. That number was the negative of an odd prime (and not the same prime as the one mentioned earlier).

    On this new scale, what are the freezing and boiling points of water?

    There are now 2500 puzzles available between the Enigmatic Code and S2T2 sites. See [link].

    [teaser2834]

     
    • Jim Randell's avatar

      Jim Randell 8:46 am on 9 November 2021 Permalink | Reply

      A straightforward program finds the answer. The following Python program runs in 48ms.

      Run: [ @replit ]

      from enigma import (primes, irange, inf, div, printf)
      
      def solve():
      
        # consider possible squares (for the boiling point)
        for n in irange(11, inf):
          b = n * n
      
          # consider possible primes (for the freezing point)
          for f in primes.between(2, b - 100):
      
            # compute when the temperature scales coincide
            d = b - f - 100
            if not (d > 0): continue
            v = div(100 * f, d)
            if v is None or not (v > 2 and v != f and v in primes): continue
      
            yield (n, b, f, v)
      
      # find the first solution
      for (n, b, f, v) in solve():
        printf("n={n} b={b} f={f} v={v}")
        break
      

      Solution: The freezing point is at 41. The boiling point is at 961.

      The scales coincide at −5.

      To convert from Celsius you can use:

      y = (46/5)x + 41

      However, if the scales were allowed to coincide at −2, there would be further solutions:

      f = 31, b = 1681 (= 41²), y = (33/2)x + 31, coincide at −2
      f = 71, b = 3721 (= 61²), y = (73/2)x + 71, coincide at −2


      A bit of analysis gives a shorter program:

      The scales coincide at −v where:

      v = 100f / (b − f − 100)

      And v is an odd prime number different from f.

      The prime factorisation of the numerator is: 2 × 2 × 5 × 5 × f.

      So we immediately see:

      v = 5
      b = 21f + 100

      And a short program provides the answer:

      from enigma import (primes, inf, is_square, printf)
       
      for f in primes.irange(2, inf):
        b = 21 * f + 100
        if is_square(b):
          printf("f={f} b={b}")
          break
      

      Alternatively, we can further analyse the expression for b, which is a square, say n²:

      n² = 21f + 100
      n² − 100 = 21f
      (n − 10)(n + 10) = 3 × 7 × f

      Considering the factor that does not contain f:

      n − 10 = 1 ⇒ n = 11, f = 1 [not prime]
      n + 10 = 1 ⇒ n = −9, f = −19/21 [non-integral]
      n − 10 = 3 ⇒ n = 13, f = 23/7 [non-integral]
      n + 10 = 3 ⇒ n = −7, f = −17/7 [non-integral]
      n − 10 = 7 ⇒ n = 17, f = 9 [not prime]
      n + 10 = 7 ⇒ n = −3, f = −13/3 [non-integral]
      n − 10 = 21 ⇒ n = 31, f = 41 [*** SOLUTION ***]
      n + 10 = 21 ⇒ n = 11, f = 1 [not prime]

      Like

  • Unknown's avatar

    Jim Randell 9:08 am on 7 November 2021 Permalink | Reply
    Tags: by: H. G. ApSimon   

    A Brain-Teaser: [Boxes and ladders] 

    From The Sunday Times, 12th April 1957 [link]

    A schoolmaster set a problem of the following type to each of four pupils:

    “A ladder of length ___ rests squarely, and more steeply than, 45 degrees, against a (vertical) wall, with its foot on the (horizontal) ground distant ___ from the base of the wall. A cubical box fits flush into the angle of the wall and the ground, and just touches the ladder. What is the length of side of the box?”

    and he filled in the gaps in the problem as set out with Integral numbers of inches such that the answer would also be an integral number of inches.

    To each pupil, however, he gave different values for the length of ladder (all less than 150 ft.) and for the distance of its foot from the base of the wall. The answers submitted to him by the four pupils were all the same, and all were correct.

    What were the four different pairs of values he gave to his pupils, and what was the common answer implied by them?

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

    This puzzle was originally published with no title.

    [teaser-1957-04-12] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 7 November 2021 Permalink | Reply

      The distance along the ground, x, the height up the wall, y, and the length of the ladder, z, form a Pythagorean Triple (x, y, z).

      And the side of the cube, k, is then given by:

      k = xy / (x + y)

      The following Python program runs in 48ms.

      Run: [ @replit ]

      from enigma import (pythagorean_triples, div, group, unpack, printf)
      
      # generate (x, y, z, k) solutions
      def generate(Z):
        for (x, y, z) in pythagorean_triples(Z):
          k = div(x * y, x + y)
          if k is not None:
            yield (x, y, z, k)
      
      # collect (x, y, z, k) solutions by the value of k
      # z < 150 ft = 1800 in
      d = group(generate(1799), by=unpack(lambda x, y, z, k: k))
      
      # look for k values with 4 (or more) solutions
      for (k, vs) in d.items():
        if len(vs) < 4: continue
        printf("k={k} [{n} triangles]", n=len(vs))
        for (x, y, z, k) in vs:
          printf("  x={x} y={y} z={z}")
        printf()
      

      Solution: The (length, distance) pairs given to the students were (in inches): (1189, 820), (1225, 735), (1547, 595), (1739, 564). The common answer was 420 inches.

      Like

    • GeoffR's avatar

      GeoffR 11:01 am on 10 November 2021 Permalink | Reply

      Using the pythagorean_triples function from enigma.py, this function uses x < y < z, so there is no need to check the ladder angle is greater than 45 degrees, as y is always greater than x.

      Let cube side = c and ladder angle with ground = A
      Let y = p + c and x = c + q

      Tan A = p / c = c / q, so (y – c) / c = c /(x – c)
      This simplifies to c = (y * x) / (y + x)

      
      from enigma import pythagorean_triples
      from collections import defaultdict
      
      BT1 = defaultdict(list)
      
      for x, y, z in pythagorean_triples(1799):
          # find integer value of cube side
          q, r = divmod(x * y, x + y)
          if q > 0 and r == 0:
              BT1[q] += [(x, y, z)]
      
      for k, v  in BT1.items():
          # Looking for four sets of values for (x, y, z)
          if len(v) > 3:
              print(f"Cube side = {k}")
              print(f"Triangles: {v}")
      

      Like

    • John Crabtree's avatar

      John Crabtree 3:57 pm on 11 November 2021 Permalink | Reply

      I think that this brain teaser is very hard to tackle manually. Hugh ApSimon presents a parameter method for generating the primitive solutions in his book “Mathematical Byways in Ayling, Beeling and Ceiling” which was published in 1984. See Chapter 2 on pages 7-12. Chapter 1 on pages 3-6 is a lead in.

      Like

  • Unknown's avatar

    Jim Randell 4:34 pm on 5 November 2021 Permalink | Reply
    Tags:   

    Teaser 3085: Lucky progression 

    From The Sunday Times, 7th November 2021 [link] [link]

    I wrote down a number which happened to be a multiple of my lucky number. Then I multiplied the written number by my lucky number to give a second number, which I also wrote down. Then I multiplied the second number by my lucky number again to give a third, which I also wrote down. Overall, the three numbers written down used each of the digits 0 to 9 exactly once.

    What were the three numbers?

    [teaser3085]

     
    • Jim Randell's avatar

      Jim Randell 4:44 pm on 5 November 2021 Permalink | Reply

      The following Python program runs in 51ms. (Internal runtime is 4ms).

      from enigma import (irange, inf, nsplit, flatten, printf)
      
      # consider lucky numbers, n
      for n in irange(2, inf):
        # and the initial multiple
        for k in irange(2, inf):
          # the three numbers
          ns = list(k * n**x for x in (1, 2, 3))
          # collect the digits in the numbers
          ds = flatten(nsplit(n) for n in ns)
          if len(ds) > 10: break
          if len(ds) == 10 and len(set(ds)) == 10:
            printf("ns = {ns} [n = {n}; k = {k}]")
        if k == 2: break
      

      Solution: The three numbers are: 306, 918, 2754.

      The lucky number is 3.

      So we have:

      102 × 3 = 306
      306 × 3 = 918
      918 × 3 = 2754

      Like

    • GeoffR's avatar

      GeoffR 5:34 pm on 5 November 2021 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 2..10: LN; % my lucky number
      
      var 1..9: A; var 0..9: B; var 0..9: C;
      var 1..9: D; var 0..9: E; var 0..9: F;
      var 1..9: G; var 0..9: H; var 0..9: I; var 0..9: J;
      
      % the three numbers written down have different digits
      constraint all_different ([A, B, C, D, E, F, G, H, I, J]);
      
      % My three numbers
      var 100..999: ABC = 100*A + 10*B + C;
      var 100..999: DEF = 100*D + 10*E + F;
      var 1000..9999: GHIJ = 1000*G + 100*H + 10*I + J;
      
      % First number is a multiple of my lucky number
      constraint ABC mod LN == 0;
      
      % Second and third lucky numbers
      constraint ABC * LN == DEF /\ DEF * LN == GHIJ;
      
      solve satisfy;
      
      output ["My three numbers are " ++show(ABC) ++ ", " ++ show(DEF) ++ 
      " and " ++ show(GHIJ)
      ++ "\n" ++ "Lucky number is " ++ show(LN) ];
      
      
      

      Like

    • NigelR's avatar

      NigelR 5:51 pm on 5 November 2021 Permalink | Reply

      First number must be less than 1000 or the three numbers would have more than 10 digits between them. Code below runs in 39ms:

      for lucky in range(1,100):
          for first in range(1,1000):
              if first%lucky!=0:
                  continue
              second = first*lucky
              third = second*lucky
              res=str(first)+str(second)+str(third)
              if len(set(res)) == 10 and len(res) == 10:
                  print (‘first: ‘, first,’  second: ‘, second,’  third:’, third,’  lucky:’, lucky)
                  exit()
      

      Like

    • GeoffR's avatar

      GeoffR 8:52 am on 6 November 2021 Permalink | Reply

      # Assume a digit split of 3:3:4 for 10 digits for three numbers.
      # The multiplier is less than 10 between two increasing 3-digit numbers
      # The three numbers are ABC, DEF and GHIJ and the lucky number is LN
      
      from enigma import nsplit, all_different
      
      from enigma import Timer
      timer = Timer('ST3085')
      timer.start()
      
      for LN in range(2, 10):
        for ABC in range(102, 987):
          if ABC % LN != 0:
            continue
          A, B, C = nsplit(ABC)
          if not all_different(A, B, C):
            continue
          DEF = ABC * LN
          if DEF < 100 or DEF > 999:
            continue
          D, E, F = nsplit(DEF)
          if not all_different(A, B, C, D, E, F):
            continue
          GHIJ = DEF * LN
          if GHIJ < 1000 or GHIJ > 9999:
            continue
          G, H, I, J = nsplit(GHIJ)
          if not all_different(A, B, C, D, E, F, G, H, I, J):
            continue
          print(f"My three numbers are {ABC}, {DEF} and {GHIJ}")
          timer.stop()
          timer.report()  # 4.98ms (I9 processor)
          
      
      
      
      

      Like

      • Frits's avatar

        Frits 10:33 am on 9 November 2021 Permalink | Reply

        @GeoffR, a digit split of 2:3:5 is also possible.

        Like

    • GeoffR's avatar

      GeoffR 11:09 am on 9 November 2021 Permalink | Reply

      @Frits:
      I decided to try a 3:3:4 digit split first as this seemed a more likely candidate for a solution. As this digit split gave a single answer, I did not try a 2:3:5 digit split.

      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