Tagged: by: J S Rowley Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 11:18 am on 20 August 2024 Permalink | Reply
    Tags: by: J S Rowley   

    Brain Teaser: Circular tour 

    From Sunday Times Brain Teasers 1974 [link]

    Arley, Barley, Carley, Darley, Earley and Farley are six villages connected, in that order, by a circular bus service. A passenger is allowed to board at any village and alight at any other or complete the whole circuit. The distances from any village to any other village on the route are all different and all an exact number of miles, consisting of every possible whole number from 1 mile up to the distance for the whole circuit.

    Arley to Barley is 1 mile. The distance from Farley to Arley is greater than that from Barley to Carley but less than that from Earley to Farley.

    What is the distance from Earley to Farley?

    This puzzle is taken from the book Sunday Times Brain Teasers (1974), but it is not a puzzle that originally appeared in The Sunday Times.

    [teaser-book-1974-p95]

     
    • Jim Randell's avatar

      Jim Randell 11:20 am on 20 August 2024 Permalink | Reply

      We have encountered puzzles involving magic circles before (see: Teaser 1986, Enigma 985, Puzzle #171), and developed tools for generating them

      This Python program considers magic circles of size 6, and then looks for ones that meet the specified requirements.

      It runs in 75ms. (Internal runtime is 4.1ms).

      Run: [ @replit ]

      from magic_circles import magic_circle
      from enigma import printf
      
      # look for a size 6 magic circle (including reflections)
      for ds in magic_circle(6, refl=1):
        # distance AB = 1
        assert ds[0] == 1
        # distance EF > distance FA > distance BC
        if not (ds[4] > ds[5] > ds[1]): continue
        # output solution
        printf("distance EF = {ds[4]} [ds = {ds}]")
      

      Solution: The distance from Earley to Farley is 12 miles.

      And we have the following distances around the circle:

      A→B = 1
      B→C = 2
      C→D = 7
      D→E = 4
      E→F = 12
      F→A = 5

      Like

      • Ruud's avatar

        Ruud 2:09 pm on 21 August 2024 Permalink | Reply

        A bit verbose and certainly very brute force:

        import itertools
        
        ab = 1
        for bc, cd, de, ef, fa in itertools.permutations(range(2, 20), 5):
            ac = ab + bc
            ad = ac + cd
            ae = ad + de
            af = ae + ef
            bd = bc + cd
            be = bd + de
            bf = be + ef
            ba = bf + fa
            ce = cd + de
            cf = ce + ef
            ca = cf + fa
            cb = ca + ab
            df = de + ef
            da = df + fa
            db = da + ab
            dc = db + bc
            ea = ef + fa
            eb = ea + ab
            ec = eb + bc
            ed = ec + cd
            fb = fa + ab
            fc = fb + bc
            fd = fc + cd
            fe = fd + de
        
            if ef > fa > bc and af+fa == 31:
                if {ab, ac, ad, ae, af, bc, bd, be, bf, ba, cd, ce, cf, ca, cb, de, df, da, db, dc, ef, ea, eb, ec, ed, fa, fb, fc, fd, fe} == set(range(1, 31)):
                    print(ab, ac, ad, ae, af, bc, bd, be, bf, ba, cd, ce, cf, ca, cb, de, df, da, db, dc, ef, ea, eb, ec, ed, fa, fb, fc, fd, fe)
                    print(ef)
        

        Like

    • Lise Andreasen's avatar

      Lise Andreasen 10:27 pm on 20 August 2024 Permalink | Reply

      So… The bus travels 31 miles on 1 circuit. And there’s also 2 villages, 31 miles apart on the circuit. Is this the distance from a village to itself?

      Like

      • Jim Randell's avatar

        Jim Randell 8:14 am on 21 August 2024 Permalink | Reply

        That’s right. A complete circuit is 31 miles, but we can also travel all the distances between 1 and 30 miles.

        A→B = 1; B→A = 30
        B→C = 2; C→B = 29
        A→C = 3; C→A = 28
        D→E = 4; E→D = 27
        F→A = 5; A→F = 26
        F→B = 6; B→F = 25
        C→D = 7; D→C = 24
        F→C = 8; C→F = 23
        B→D = 9; D→B = 22
        A→D = 10; D→A = 21
        C→E = 11; E→C = 20
        E→F = 12; F→E = 19
        B→E = 13; E→B = 18
        A→E = 14; E→A = 17
        F→D = 15; D→F = 16

        Like

        • Lise Andreasen's avatar

          Lise Andreasen 6:29 pm on 21 August 2024 Permalink | Reply

          Just an odd way to describe the 31 miles “distance”.

          “The distances from any village to any other village on the route are all different and all an exact number of miles, consisting of every possible whole number from 1 mile up to the distance for the whole circuit.”

          Like

    • GeoffR's avatar

      GeoffR 9:48 am on 21 August 2024 Permalink | Reply

      There are 6 single trips between villages and 6 trips for each of 2,3,4 and 5 bus stops between villages e.g. AB, BC, CD, DE, EF, FA for single stops between villages. There is only 1 circular bus trip visiting all 6 villages i.e. AB + BC + CD + DE + EF + FA.
      In total, therefore, there are 5 X 6 + 1 = 31 total bus trips.
      This MiniZinc solution enumerates all possible bus trips.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      % Villages are A,B,C,D,E,F
      
      % Assumed upper bounds of six distances between villages
      var 1..15:AB; var 1..15:BC; var 1..15:CD; var 1..15:DE; var 1..15:EF; var 1..15:FA;
      
      constraint all_different ([AB, BC, CD, DE, EF, FA]);
      
      constraint FA > BC /\ FA < EF;
      constraint AB == 1; 
      
      % There are 31 possible distances between villages, as shown below:
      var set of int: soln_nums == {AB, BC, CD, DE, EF, FA,  % 1 stop
      AB+BC, BC+CD, CD+DE, DE+EF, EF+FA, FA+AB,              % 2 stops
      AB+BC+CD, BC+CD+DE, CD+DE+EF, DE+EF+FA, EF+FA+AB, FA+AB+BC,  % 3 stops
      AB+BC+CD+DE, BC+CD+DE+EF, CD+DE+EF+FA, DE+EF+FA+AB, EF+FA+AB+BC, FA+AB+BC+CD, % 4 stops
      AB+BC+CD+DE+EF, BC+CD+DE+EF+FA, CD+DE+EF+FA+AB, DE+EF+FA+AB+BC, % 5 stops
      EF+FA+AB+BC+CD, FA+AB+BC+CD+DE, 
      AB+BC+CD+DE+EF+FA};  % 6 stops
      
      set of int: req_nums = 1..31;
       
      constraint soln_nums == req_nums;
      
      constraint card(soln_nums) == AB + BC + CD + DE + EF + FA;
      
      solve satisfy;
      
      output["[AB, BC, CD, DE, EF, FA] = " ++ show( [AB, BC, CD, DE, EF, FA] )
      ++"\n" ++ "Distance from Earley to Farley =  "  ++ show(EF) ++ " miles."];
      
      % [AB, BC, CD, DE, EF, FA] = [1, 2, 7, 4, 12, 5]
      % Distance from Earley to Farley =  12 miles.
      % ----------
      % ==========
      
      
      

      Like

    • Ruud's avatar

      Ruud 11:24 am on 22 August 2024 Permalink | Reply

      A bit less verbose:

      import itertools
      
      succ = dict(zip("abcdef", "bcdefa"))
      
      for x5 in itertools.permutations(range(2, 20), 5):
          dist = dict(zip("ab bc cd de ef fa".split(), [1, *x5]))
          for d in "abcdef":
              d1 = succ[d]
              for _ in range(4):
                  dist[d + succ[d1]] = dist[d + d1] + dist[d1 + succ[d1]]
                  d1 = succ[d1]
          if dist["ef"] > dist["fa"] > dist["ac"] and set(dist.values()) == set(range(1, 31)):
              print(dist["ef"])
      

      Like

    • Frits's avatar

      Frits 4:10 pm on 22 August 2024 Permalink | Reply

      Using my Puzzle #171 code but now using decomposition.

           
      from itertools import permutations
      
      # for a list with n numbers check if the set of sums of 1...n-1 adjacent items
      # (also circular) is complete (with n.(n-1) different sums)
      def allOccurOnce(s, ln):                                         
        sms = set(s)
        for i in range(ln):
          for j in range(2, ln):
            if (sm := sum((s * 2)[i:i + j])) in sms: 
              return False
            sms.add(sm)
        return True  
      
      # decompose <t> into <k> increasing numbers from <ns>
      def decompose(t, k, ns, s=[]):
        if k == 1:
          if t in ns:
            yield s + [t]
        else:
          for i, n in enumerate(ns):
            # make sure we don't overshoot
            if 2 * t < (2 * n + k - 1) * k: break
            yield from decompose(t - n, k - 1, ns[i + 1:], s + [n])
      
      N = 6
      T = N**2 - N + 1
      H = int(T // 2)
      
      # for a magic circle with N numbers and total sum T
      # the highest possible circle number is T - N(N-1)/2 which equals H + 1
      
      # exclude mandatory numbers 1 and 2 from the decomposition
      for dc in decompose(T - 3, N - 2, range(3, H + 2)):
        # weave in the number 2
        for p in permutations([2] + dc):
          p1 = (1, ) + p
          
          # distance EF > distance FA > distance BC
          if not (p1[4] > p1[5] > p1[1]): continue
          # can we make N.(N - 1) different sums?
          if not allOccurOnce(p1, N): continue
          print(f"answer: {p1[4]} miles")
      

      Like

  • Unknown's avatar

    Jim Randell 10:49 am on 25 July 2024 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 416: Sharing sweets 

    From The Sunday Times, 27th April 1969 [link]

    The Binks family had a birthday party recently for Ann, the youngest child. A tin of toffees was shared among the seven youngest members of the family, whose ages were all different and less than 20.

    It was decided that the younger children should have more than the older; so Mr Binks suggested that each should receive the number obtained by dividing the total number of toffees by his or her age. This was done and it so happened that all the divisions worked out exactly and no toffees were left over.

    Mary received 18 sweets.

    How much older was she than Ann?

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

    [teaser416]

     
    • Jim Randell's avatar

      Jim Randell 10:51 am on 25 July 2024 Permalink | Reply

      Labelling the children in age order A, …, G, so A is Ann, and the total number of toffees is T, we have:

      T = T/A + T/B + T/C + T/D + T/E + T/F + T/G

      1 = 1/A + 1/B + 1/C + 1/D + 1/E + 1/F + 1/G

      So we need to find 7 different reciprocals (with denominators less than 20) that sum to 1. And we can use the [[ reciprocals() ]] function from the enigma.py library to do that (originally written for Enigma 348).

      We can then assign one of the values T/B, …, T/G to 18 (for Mary), and check that all the remaining T/X values give a whole number.

      The following Python program runs in 66ms. (Internal runtime is 753µs).

      Run: [ @replit ]

      from enigma import (reciprocals, printf)
      
      # find 7 different reciprocals that sum to 1
      for rs in reciprocals(7, 1, 1, M=19, g=1):
        # Ann is the youngest
        A = rs.pop(0)
        # Mary received 18 sweets
        for M in rs:
          T = M * 18
          if not all(T % r == 0 for r in rs): continue
          # output solution
          printf("A={A} M={M} [T={T} rs={rs}]", rs=[A] + rs)
      

      Solution: Mary (10) is 7 years older than Ann (3).

      There is only one set of 7 reciprocals that works:

      1 = 1/3 + 1/4 + 1/9 + 1/10 + 1/12 + 1/15 + 1/18

      There are 180 toffees, and the ages are:

      3 → 60 toffees (Ann)
      4 → 45 toffees
      9 → 20 toffees
      10 → 18 toffees (Mary)
      12 → 15 toffees
      15 → 12 toffees
      18 → 10 toffees
      total = 180 toffees

      Like

    • GeoffR's avatar

      GeoffR 1:32 pm on 25 July 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..19:A; var 1..19:B; var 1..19:C; var 1..19:D;
      var 1..19:E; var 1..19:F; var 1..19:G;
      
      constraint all_different ([A, B, C, D, E, F, G]);
      % Order childrens ages
      constraint A < B /\ B < C /\ C < D /\ D < E /\ E < F /\ F < G;
      
      var 28..200:T;  % total number of toffees
      
      % Distribute toffees to seven children, with none left over
      constraint T  mod A == 0 /\ T  mod B == 0 /\ T  mod C == 0 /\ T  mod D == 0 
      /\ T  mod E == 0 /\ T  mod F == 0 /\ T  mod G == 0;
      
      constraint T == T div A  + T div B + T div C + T div D 
      + T div E + T div F + T div G;
      
      solve satisfy;
      
      output ["Childrens ages = " ++ show ( [A,B,C,D,E,F,G] )
      ++ "\n" ++ "Number of toffees = " ++ show(T) ];
      
      % Childrens ages = [3, 4, 9, 10, 12, 15, 18]
      % Number of toffees = 180
      % ----------
      % ==========
      

      As Mary received 18 sweets, she was 10 years old, as 10 * 18 = 180.
      As the youngest was Ann, she was 3 years old, 7 years younger than Mary.

      Like

    • Ruud's avatar

      Ruud 6:40 am on 26 July 2024 Permalink | Reply

      [removed]

      Like

      • Jim Randell's avatar

        Jim Randell 9:21 am on 26 July 2024 Permalink | Reply

        @Ruud: Can you explain the reasoning behind your code? I tried changing the 18 to 24 to solve a variation of the puzzle, but it did not give a correct answer. (I also added the missing imports to your program).

        Like

        • ruudvanderham's avatar

          ruudvanderham 12:02 pm on 26 July 2024 Permalink | Reply

          @Jim
          My solution was incorrect. Could you remove it?
          Here’s my revised one, which is not much different from yours:

          import itertools
          import math
          
          number_of_toffee_mary = 18
          for ages in itertools.combinations(range(1, 20), 7):
              if math.isclose(sum(1 / age for age in ages), 1):
                  for age in ages:
                      if all(divmod(age * number_of_toffee_mary, try_age)[1] == 0 for try_age in ages):
                          print(f"{ages=}. Number of toffees={number_of_toffee_mary*age}. Mary is {age-ages[0]} years older than Ann")

          Like

    • Frits's avatar

      Frits 4:59 pm on 27 July 2024 Permalink | Reply

      from itertools import combinations
      
      M = 19
      N = 7
      
      # return divisors of <n> between 2 and <m>
      def divs(n, m=M):
        lst = [d for d in range(2, int(n**.5) + 1) if n % d == 0 and d <= m]
        return lst + [d for x in lst[::-1] if x * x != n and (d := n // x) <= m]
      
      # reciprocal sum
      def rs(s): 
        i, t = 0, 0.0
        ln = len(s)
        while t < 1 and i < ln:
          t += 1 / s[i]
          i += 1
        return i == ln and round(t, 5) == 1
      
      # Mary received 18 sweets
      for m in range(2, M + 1):
        # pick <N> divisors
        for c in combinations(divs(18 * m), N):
          # reciprocal sum must be 1
          if rs(c) == 1: 
            print(f"answer: {c}")
      

      Like

  • Unknown's avatar

    Jim Randell 8:19 am on 18 June 2024 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 339: Cross country 

    From The Sunday Times, 5th November 1967 [link]

    Tom, Dick and Harry had come up the school together and in three successive years had competed in the Junior, Colts and Senior, cross-country races.

    Every year they finished in the same positions but interchanged so that no boy came in the same position twice. The same applied to the numbers on their vests, all six numbers being different, odd, and greater than 1.

    When each boy multiplied his position number by the number he was wearing at the time, the nine results were all different and together totalled 841.

    Dick beat the other two in the Junior race, but the number he was wearing was smaller than his position number; but he was wearing the highest card number in the Senior race, and this was [also] smaller than his position number.

    Harry’s three products had a sum smaller than that of either of the other two.

    What were the three boys’ positions in the Colts race and what numbers were they wearing?

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

    [teaser339]

     
    • Jim Randell's avatar

      Jim Randell 8:20 am on 18 June 2024 Permalink | Reply

      Let the finishing positions be (best to worst) (a, b, c), and the runner numbers be (smallest to largest) (x, y, z).

      These six numbers are all different, odd, and greater than 1.

      Taking the cartesian product of these sets we get:

      {a, b, c} × {x, y, z} = {ax, ay, az, bx, by, bz, cx, cy, cz}

      And for each boy in each race the product of his runner number and his finishing position gives a unique value, so they correspond directly with the 9 values produced by this cartesian product.

      The following Python program considers divisor pairs of 841, and then splits each divisor into 3 odd numbers meeting the requirements. We then use the [[ SubstitutedExpression() ]] solver from the enigma.py library to assign runner numbers and finishing positions in each race.

      The program runs in 116ms. (Internal runtime is 38ms).

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, divisors_pairs, decompose, div, cproduct, sprintf as f, join, printf)
      
      # find 3 odd numbers that give the required total
      def numbers(t):
        r = div(t - 3, 2)
        if r:
          for (a, b, c) in decompose(r, 3, min_v=1, sep=1):
            yield (2 * a + 1, 2 * b + 1, 2 * c + 1)
      
      # solve for positions <pos>, numbers <num>
      def solve(pos, num):
        # let the positions and numbers in the races be:
        #           pos   | num
        #           T D H | T D H
        #  junior:  A B C | D E F
        #  colt:    G H I | J K L
        #  senior:  M N P | Q R S
      
        # an expression to calculate the sum of products
        def fn(ps, ns):
          ps = join((f("pos[{x}]") for x in ps), sep=", ", enc="[]")
          ns = join((f("num[{x}]") for x in ns), sep=", ", enc="[]")
          return f("dot({ps}, {ns})")
        # construct an alphametic puzzle
        exprs = [
          # "D beat T and H in the junior race ..."
          "pos[B] < pos[A]",
          "pos[B] < pos[C]",
          # "... but his runner number was less than his position number"
          "num[E] < pos[B]",
          # "D has the largest runner number in the senior race ..."
          "num[R] > num[Q]",
          "num[R] > num[S]",
          # "... but his runner number was less than his position number"
          "num[R] < pos[N]",
          # "the sum of H's products were smaller than T's or D's"
          f("{H} < min({T}, {D})", T=fn("AGM", "DJQ"), D=fn("BHN", "EKR"), H=fn("CIP", "FLS")),
        ]
        p = SubstitutedExpression(
          exprs,
          base=3, # values are (0, 1, 2)
          distinct="ABC DEF GHI JKL MNP QRS AGM BHN CIP DJQ EKR FLS".split(),
          env=dict(pos=pos, num=num),
        )
        # solve the alphametic
        for s in p.solve(verbose=""):
          # output solution
          for (t, ps, ns) in zip(["junior", "colt  ", "senior"], ["ABC", "GHI", "MNP"], ["DEF", "JKL", "QRS"]):
            ((pT, pD, pH), (nT, nD, nH)) = ((pos[s[k]] for k in ps), (num[s[k]] for k in ns))
            printf("{t}: T (#{nT}) = {pT}; D (#{nD}) = {pD}; H (#{nH}) = {pH}")
      
      # consider the values of (a + b + c) * (x + y + z)
      for (p, q) in divisors_pairs(841, every=1):
        if p < 15 or q < 15: continue
        for (abc, xyz) in cproduct([numbers(p), numbers(q)]):
          # all numbers and products are different
          if len(set(abc + xyz)) != 6: continue
          if len(set(i * j for (i, j) in cproduct([abc, xyz]))) != 9: continue
          # solve the puzzle
          solve(abc, xyz)
      

      Solution: In the Colts race Tom (#15) finished 5th; Dick (#11) finished 7th; Harry (#3) finished 17th.

      The full results are:

      Junior: Tom (#11) = 17th; Dick (#3) = 5th; Harry (#15) = 7th
      Colts: Tom (#15) = 5th; Dick (#11) = 7th; Harry (#3) = 17th
      Senior: Tom (#3) = 7th; Dick (#15) = 17th; Harry (#11) = 5th

      In each race the runner numbers are #3, #11, #15 and the positions are 5th, 7th, 17th.

      And the sum of the nine different products of these numbers is:

      (3 + 11 + 15) × (5 + 7 + 17) = 29 × 29 = 841

      In fact the only viable factorisation of 841 is 29 × 29.

      Like

  • Unknown's avatar

    Jim Randell 10:03 am on 24 March 2024 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 351: Birthday party 

    From The Sunday Times, 28th January 1968 [link]

    Some years ago the Bell family were holding their usual annual special birthday party. Four members of the family, of four different generations, had birthdays on the same day of the year. They were old Adam, his son Enoch, Enoch’s son Joseph and Joseph’s son David. On this occasion David remarked that the sum of any three of their four ages was a perfect square.

    Some years later old Adam died on his birthday, but it so happened that on the very same day David’s son Samuel was born, and the annual party was continued in subsequent years.

    In 1967 at the usual party Samuel made exactly the same remark that David had made, on the previous occasion.

    In what year did Adam die and how old was he then?

    (Perhaps I should mention that no one survived to be 100!).

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

    [teaser351]

     
    • Jim Randell's avatar

      Jim Randell 10:03 am on 24 March 2024 Permalink | Reply

      This Python program considers possible potential gaps between generations, and then looks for ages that makes any subset of size 3 sum to a square. Once we have found potential candidates we look for a pair with suitable overlapping gaps.

      It runs in 221ms. (Internal runtime is 144ms).

      Run: [ @replit ]

      # generate possible (<gaps>, <ages>)
      def generate():
        # suppose the gaps between generations are in the range [15, 40]
        # which means the sum of the 3 gaps lies between 45 and 97
        for g in irange(45, 97):
          for (p, q, r) in decompose(g, 3, increasing=0, sep=0, min_v=15, max_v=40):
            # consider lowest age
            for a in irange(1, 98 - g):
              b = a + p
              c = b + q
              d = c + r
              t = a + b + c + d
              if not all(is_square(t - x) for x in (a, b, c, d)): continue
              printf("[({r}, {q}, {p}) -> ({d}, {c}, {b}, {a})]")
              yield ((r, q, p), (d, c, b, a))
      
      # look for gaps (p, q, r) -> (q, r, s)
      for (((p, q, r), (A1, E1, J1, D1)), ((q_, r_, s), (E2, J2, D2, S2))) in subsets(generate(), size=2, select='P'):
        if not (q_ == q and r_ == r and E2 > E1): continue
        # event 2 is in 1967
        y2 = 1967
        y1 = y2 - (E2 - E1)
        printf("{y1} (A={A1} E={E1} J={J1} D={D1}) -> {y2} (E={E2} J={J2} D={D2} S={S2})")
        b = y1 - A1  # A's birth year
        d = y2 - S2  # A's death year = S's birth year
        a = d - b # A's age at death
        printf("-> A born {b}; died {d}, aged {a}")
      

      Solution: Adam died in 1953, on his 96th birthday.


      There are only three viable candidate lists:

      (58, 41, 22, 1) with gaps of (17, 19, 21)
      (78, 57, 34, 9) with gaps of (21, 23, 25)
      (89, 66, 41, 14) with gaps of (23, 25, 27)

      An age of 1 might be a little young to be making observations, but in any case only the final two candidates have suitable overlapping gaps.

      So, at the first event we have:

      Adam = 78; Enoch = 57; Joseph = 34; David = 9

      And at the second event:

      Enoch = 89; Joseph = 66; David = 41; Samuel = 14

      This is 32 years later than the first event, and as it happened in 1967 the first event was in 1935.

      So Adam was born in 1857, and Samuel was born in 1953, the same year Adam died. So Adam died on his 96th birthday.

      Like

    • Frits's avatar

      Frits 12:40 pm on 24 March 2024 Permalink | Reply

      Faster and probably easier to understand.
      I have also not put in an age restriction for the youngest family member to make observations.

       
      from enigma import SubstitutedExpression, subsets
      
      # invalid digit / symbol assignments
      d2i = dict()
      for d in range(0, 100):
        vs = set() 
        if d > 69: vs.update('A')
        # not excluding young fathers (with such names)
        for i in range(4):
          if not (10 * i <= d < 10 * (i + 7)): vs.update('ABCD'[i])
        d2i[d] = vs  
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # increasing ages A, B, C and D
          "B > (A + 10)",
          "C > (B + 10)", 
          "is_square(A + B + C)",
          "D > (C + 10)", 
          # the sum of any three of their four ages was a perfect square
          "all(is_square(x + y + z) for x, y, z in subsets(@ages, 3))"
        ],
        base=100,
        macro=dict([("ages", "[D, C, B, A]")]),
        answer="@ages",
        d2i=d2i,
        distinct="",
        verbose=0,    # use 256 to see the generated code
      )
      
      # a,     e,     j,     d
      # e + n, j + n, d + n, s 
      for (a, e, j, d), (e2, j2, d2, s)  in subsets(p.answers(), 2, select="P"):
        # same age increase for Enoch, Joseph and David
        if len(set([e2 - e, j2 - j, d2 - d])) != 1: continue
        n = e2 - e
        # some years later old Adam died on his birthday
        if s >= n - 2: continue
        print(f"answer: Adam died in {1967 - s} at the age of {a + n - s}")    
      

      Like

      • Jim Randell's avatar

        Jim Randell 1:34 pm on 24 March 2024 Permalink | Reply

        @Frits: Yes, it a good idea to check three of the values sum to a square before moving on to the fourth.

        This Python program runs in 70ms. (Internal runtime is 11ms).

        Run: [ @replit ]

        from enigma import (irange, powers, decompose, subsets, is_square, singleton, printf)
        
        # generate possible (<gaps>, <ages>)
        def generate():
          # choose possible squares for b + c + d
          for ta in powers(10, 15):
            for (b, c, d) in decompose(ta, 3, increasing=1, sep=15, min_v=16, max_v=99):
              # find values for a
              for a in irange(1, b - 15):
                t = ta + a
                if not all(is_square(t - x) for x in (b, c, d)): continue
                printf("[ages = ({d}, {c}, {b}, {a})]")
                yield (d, c, b, a)
        
        # look for gaps (p, q, r) -> (q, r, s)
        for ((A1, E1, J1, D1), (E2, J2, D2, S2)) in subsets(generate(), size=2, select='P'):
          g = singleton({E2 - E1, J2 - J1, D2 - D1})
          if g is None or not (g > 0): continue
          # event 2 is in 1967
          y2 = 1967
          y1 = y2 - g
          printf("{y1} (A={A1} E={E1} J={J1} D={D1}) -> {y2} (E={E2} J={J2} D={D2} S={S2})")
          b = y1 - A1  # A's birth year
          d = y2 - S2  # A's death year = S's birth year
          a = d - b # A's age at death
          if not (d > y1 and a < 100): continue
          printf("-> A born {b}; died {d}, aged {a}")
        

        Like

      • Frits's avatar

        Frits 2:21 pm on 24 March 2024 Permalink | Reply

        It is getting more and more annoying to have to do an import or specify a function for a normal operation like ceil.

        This program performs the same as Jim’s 2nd program and seems to have more requirements checks than Jim.

        @Jim, it is not immediately clear to me from your code that Adam didn’t die as a centenarian or that Samuels’ age isn’t too high.

        from itertools import permutations
        from math import ceil
        
        # minimal difference between generations
        diff = 15
        mxA = 98   # if Adam dies one year after the party he still is only 99
        
        d6 = 6 * diff
        sqs = set(i * i for i in range(ceil(d6**.5), int((4 * mxA - d6)**.5) + 1))
        
        sols = []
        for A in range(1, mxA - 3 * diff + 1):
          for B in range(A + diff, mxA - 2 * diff + 1):
            for C in range(B + diff, mxA - diff + 1):
              if (A + B + C) not in sqs: continue
              for D in range(C + diff, mxA + 1):
                # borrowed from Jim
                if any((A + B + C + D - x) not in sqs for x in [D, C, B, A]): 
                  continue
                sols.append([D, C, B, A])
        
        # a,     e,     j,     d
        # e + n, j + n, d + n, s 
        for (a, e, j, d), (e2, j2, d2, s) in permutations(sols, 2):
          # same age increase for Enoch, Joseph and David
          if len(set([e2 - e, j2 - j, d2 - d])) != 1: continue
          n = e2 - e
          # some (1 or more) years later Adam died on his birthday and 
          # no one (read Adam) survived to be 100
          if s >= n or a + n - s > 99: continue
          print(f"answer: Adam died in {1967 - s} at the age of {a + n - s}")     
        

        Like

        • Jim Randell's avatar

          Jim Randell 2:59 pm on 24 March 2024 Permalink | Reply

          @Frits: Yes, for completeness we can add a check to ensure a < 100 and d > y1.

          Fortunately it doesn’t remove the single candidate solution.

          Like

      • Frits's avatar

        Frits 8:29 pm on 24 March 2024 Permalink | Reply

        Inspired by Brian. Fastest approach sofar (5ms with PyPy).

             
        from itertools import combinations
        
        # minimal difference between generations
        diff = 15
        
        sqs = [sq for n in range(1, 20) 
               if 3 * (diff + 1) < (sq := n * n) <= 3 * (99 - diff)]
        
        # find sets of four different values, all less
        # than 100, for which any three add to a square
        quads = []
        # pick four squares (a + b + c, a + b + d, a + c + d, b + c + d)
        for sq1, sq2, sq3, sq4 in combinations(sqs, 4):
          a, r = divmod(sq1 + sq2 + sq3 - 2 * sq4, 3)
          if r or a < 1: continue
          
          if (d := sq4 - sq1 + a) > 99: continue
          b, c = sq2 - a - d, sq3 - a - d
        
          # check difference between generations
          if any(y - x < diff for x, y in zip((a, b, c), (b, c, d))): continue
           
          quads.append((a, b, c, d))
        
        # consider the two events mentioned
        for p in quads:
          for q in quads:
            if p == q:
              continue
            #               ages in 1967 
            (d, j, e, a), (s, d_, j_, e_) = p, q
            # the age gap between the two events must 
            # be the same for David, Joseph and Enoch
            if len({d_ - d, j_ - j, e_ - e}) == 1:
              gap = e_ - e
              if s < gap and (ad := a + gap - s) < 100:
                print(f"Adam died in {1967 - s} at age {ad}.")   
        

        Like

        • Jim Randell's avatar

          Jim Randell 10:01 pm on 24 March 2024 Permalink | Reply

          Starting from the squares is an even better idea.

          If we have four ages (a, b, c, d) (in ascending order), and each 3-subset sums to a square number, we have:

          a + b + c = u²
          a + b + d = v²
          a + c + d = w²
          b + c + d = x²

          The squares also appear in ascending order, and the difference between consecutive squares is the difference between consecutive ages.

          Then by summing the equations we get:

          t = a + b + c + d = (u² + v² + w² + x²) / 3

          And given the squares we can recover the ages:

          a = t − x²
          b = t − w²
          c = t − v²
          d = t − u²

          Here’s my version. It has an internal runtime of 349µs.

          Run: [ @replit ]

          from enigma import (powers, sqrtc, sqrtf, subsets, div, singleton, printf)
          
          # generate possible ages
          def generate():
            g = 15  # min generation gap
            # find possible sets of 4 squares
            m = 3 + 3*g
            for (u2, v2, w2, x2) in subsets(powers(sqrtc(m), sqrtf(300 - m)), size=4):
              # check generation gaps
              if v2 - u2 < g or w2 - v2 < g or x2 - w2 < g: continue
              # 3(a + b + c + d) = u^2 + v^2 + w^2 + x^2
              t = div(u2 + v2 + w2 + x2, 3)
              if t is None: continue
              # calculate (a, b, c, d)
              (a, b, c, d) = (t - x2, t - w2, t - v2, t - u2)
              if not (0 < a < b < c < d < 100): continue
              printf("[ages = ({d}, {c}, {b}, {a})]")
              yield (d, c, b, a)
          
          # look for ages at the 2 events
          for ((A1, E1, J1, D1), (E2, J2, D2, S2)) in subsets(generate(), size=2, select='P'):
            # gap between events
            g = singleton({E2 - E1, J2 - J1, D2 - D1})
            if g is None or not (g > 0): continue
            # event 2 is in 1967
            y2 = 1967
            y1 = y2 - g
            printf("{y1} (A={A1} E={E1} J={J1} D={D1}) -> {y2} (E={E2} J={J2} D={D2} S={S2})")
            b = y1 - A1  # A's birth year
            d = y2 - S2  # A's death year = S's birth year
            # check timeline
            if not (y1 + 1 < d < 100 + b): continue
            # output solution
            printf("-> A born {b}; died {d}, aged {a}", a=d - b)
          

          Like

  • Unknown's avatar

    Jim Randell 8:57 am on 16 November 2023 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 422: Telephone number 

    From The Sunday Times, 8th June 1969 [link]

    Fred was an exasperating person; he would never give a straight answer to a straight question. The other day I was seeing him off at a bus stop and he said, just as the bus was coming, “Give me a ring some time”.

    “I will”, I said, and asked him for his telephone number.

    “It has five digits”, he replied, and the first two are the same as my wife’s age while the last two are my age”.

    “But I don’t know how old you are”, I said, “except that you are less than 70”.

    “There’s no need to be rude”, he said. “I am older than my wile by as many years as our son’s age”.

    “What has he got to do with your telephone number?”, I asked.

    “Oh”, he replied, “his age is the middle digit”.

    “That doesn’t help much”, I said.

    “It’s divisible by 259, he shouted”, as the bus whisked him away.

    “That’s a great help”, I shouted back, and it really was.

    What was Fred’s telephone number?

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

    [teaser422]

     
    • Jim Randell's avatar

      Jim Randell 8:57 am on 16 November 2023 Permalink | Reply

      This puzzle is straightforward to solve programatically.

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

      It runs in 84ms. (Internal runtime of the generated program is 11ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --invalid="0,ACD"
      --distinct=""
      
      # Fred is less than 70
      "DE < 70"
      
      # Fred is older than his wife by as many years as his son's age
      "AB + C = DE"
      
      # The phone number is divisible by 259
      "ABCDE % 259 = 0"
      
      --answer="ABCDE"
      

      Solution: Fred’s telephone number is 35742.

      And we have:

      35 + 7 = 42
      35742 = 138 × 259

      Like

    • GeoffR's avatar

      GeoffR 2:51 pm on 16 November 2023 Permalink | Reply

      for H in range(15, 70):
        for W in range(15, H):
          if H - W > 9:
            continue
          for S in range(1, 10):
            # Son's age = Husband's age - Wife's age 
            if H - W == S:
              # Son's age is the single middle digit of the telephone number
              tel_num = int(str(W) + str(S) + str(H))
              if tel_num % 259 == 0:
                print(f"Fred's telephone number was {tel_num}.")
      
      # Fred's telephone number was 35742.
      
      

      If the telephone number was not divisible by 259, I found 450 solutions.

      Like

  • Unknown's avatar

    Jim Randell 11:56 am on 31 July 2022 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 161: 100-yard race 

    From The Sunday Times, 10th May 1964 [link]

    Harry, Kenneth, Lionel, Martin, Nicholas and Oliver were, the competitors in the 100-yard race on Sports Day. They wore cards numbered 1, 2, 3, 4, 5, 6 but not in that order. In no case was the position of any of the competitors the same as his card number but two of the competitors had positions equal to each other’s card number.

    Nicholas was 5th and his card number was the same as Kenneth’s position. Harry’s card number was the same as Oliver’s position which was 4th. Martin’s card number was 1.

    It was found that the sum of the products of each competitor’s position and card number was 61.

    Place the competitors in the order in which they finished the race and give their card numbers.

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

    [teaser161]

     
    • Jim Randell's avatar

      Jim Randell 11:57 am on 31 July 2022 Permalink | Reply

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

      Run: [ @replit ]

      from enigma import (irange, subsets, reverse, diff, singleton, printf)
      
      pos = list(irange(1, 6))
      
      # consider the cards in each position (1st, ..., 6th)
      for ps in subsets(pos, size=6, select="D"):
        # card: map pos -> card
        card = dict(enumerate(ps, start=1))
        # the sum of the position/card products is 61
        if sum(i * p for (i, p) in card.items()) != 61: continue
        # there is (at least) one 2-cycle
        if not any(card[p] == i for (i, p) in card.items()): continue
      
        # name: map pos -> name
        # "N was 5th"
        # "O was 4th"
        # "K's position is N's card number (N is 5th)"
        # "H's card number is O's position (O is 4th)"
        # "M's card number is 1"
        # so we have enough information to fill out the map
        rcard = reverse(card)
        ks = [5, 4, card[5], rcard[4], rcard[1]]
        ks.append(singleton(diff(pos, ks)))
        if ks[-1] is None: continue
        name = dict(zip(ks, "NOKHML"))
        # check disallowed order
        if all(name[i] == x for (i, x) in zip(pos, "HKLMNO")): continue
      
        # output solution
        for i in pos:
          printf("{i}: {n} = #{c}", c=card[i], n=name[i])
        printf()
      

      Solution: The finishing positions are:

      1st: Lionel (#6)
      2nd: Harry (#4)
      3rd: Kenneth (#2)
      4th: Oliver (#5)
      5th: Nicholas (#3)
      6th: Martin (#1)

      Like

    • Frits's avatar

      Frits 11:53 am on 3 August 2022 Permalink | Reply

       
      from enigma import SubstitutedExpression
      
      # get position from card number
      pos = lambda n, lst: [i for i, x in enumerate(lst, start=1) if x == n][0]
      
      l1 = "[A, B, C, D, E, F]"          # card numbers
      l2 = "[1, 2, 3, 4, 5, 6], " + l1   # positions and card numbers
      z = "zip(" + l2 + ")"
      
      exprs = []
      # position unequal to card number for all competitors
      exprs.append("all(x != y for x, y in " + z + ")")
      # sum of the products of each competitor's position and card number was 61
      exprs.append("sum(x * y for x, y in " + z + ") == 61")
      # two of the competitors had positions equal to each other's card number
      exprs.append("sum((y, x) in " + z + " for x, y in " + z + ") == 2")
      # M ( , 1)
      # N (5, x)
      # O (4,  )
      # H ( , 4)
      # K (x,  )
      # L ( ,  )
      # check on five different positions
      exprs.append("len({pos(1, " + l1 + "), pos(4, " + l1 + "), E, 4, 5}) == 5")
                    
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        exprs,
        answer="((1, A), (2, B), (3, C), (4, D), (5, E), (6, F))",
        digits=range(1, 7),
        distinct=("ABCDEF"),
        env=dict(pos=pos),
        verbose=0,    # use 256 to see the generated code
      )
      
      names = ["", "", "", "Oliver", "Nicholas", ""]
      for (_, ans) in p.solve():
        # dictionary, card --> postion
        d = {y: x for x, y in ans}
        
        names[d[1] - 1] = "Martin"
        names[d[4] - 1] = "Harry"
        names[ans[4][1] - 1] = "Kenneth"
        
        for i in range(6):
          if names[i] == "":
            names[i] = "Lionel"
         
        # integrity check 
        if len(set(names)) != 6: continue
        
        for i in range(6):
          print(f"{ans[i][0]}: {names[i]} with card number {ans[i][1]}")   
      

      Like

  • Unknown's avatar

    Jim Randell 8:02 am on 10 July 2022 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 150: Paving the way 

    From The Sunday Times, 23rd February 1964 [link]

    I have eight paving stones whose dimensions are an exact number of inches. Their areas are 504, 420, 324, 306, 270, 130, 117 and 112 square inches. Four of these are red and four green. There should be a ninth stone coloured yellow and square so that all nine stones can be fitted together to form a square in such a way that the red stones completely enclose the other five and the green stones completely enclose the yellow one.

    What are the dimensions of the red stones?

    [teaser150]

     
    • Jim Randell's avatar

      Jim Randell 8:03 am on 10 July 2022 Permalink | Reply

      The tiles that are mentioned have a total area of 2183.

      If the missing central tile has an area of x², and the entire collection has an area of y² then we have:

      y² = x² + 2183
      y² − x² = 2183
      (y − x)(y + x) = 2183

      So we can calculate possible values for x and y by examining the divisors of 2183.

      The Python program find values for x and y, and then chooses 4 green tiles, along with the x by x yellow square, which can fit together to form a square. And then checks that this square, along with the the remaining (green) tiles can fit together to form a y by y square.

      We use the rectpack.py module to fit the tiles into squares.

      The program runs in 169ms.

      Run: [ @replit ]

      from enigma import (divisors_pairs, div, subsets, is_square, cproduct, printf)
      import rectpack
      
      # areas of tiles we are given
      areas = {504, 420, 324, 306, 270, 130, 117, 112}
      
      # record possible tiles
      dps = dict((x, list(divisors_pairs(x))) for x in areas)
      
      # select tiles by area <x>, with max dimension not exceeding <m>
      tiles = lambda x, m: ((a, b) for (a, b) in dps[x] if not (b > m))
      
      # pack rectangles <rs> and an <s> by <s> square into a <t> by <t> square
      def pack(rs, s, t):
        # make the list of rectangles
        rs_ = [(s, s)]
        rs_.extend(rs)
        # pack them into a t by t square
        for ps in rectpack.pack(t, t, rs_):
          # check the square is not on the edge
          if not any(
            w == h == s and x > 0 and y > 0 and x + w < t and y + h < t
            for (x, y, w, h) in ps
          ): continue
          return ps  # we only need one packing
      
      # consider divisors of the total area
      for (a, b) in divisors_pairs(sum(areas)):
        # calculate x and y
        (x, y) = (div(b - a, 2), div(a + b, 2))
        if x is None or y is None or y < x + 4: continue
        # choose 4 green tiles to go around a central x by x square
        for green in subsets(areas, size=4):
          z = is_square(x * x + sum(green))
          if z is None: continue
          # consider dimensions of green tiles
          for gs in cproduct(tiles(x, z) for x in green):
            # pack them into a z by z square
            ps1 = pack(gs, x, z)
            if ps1 is None: continue
            # consider dimensions of red tiles
            red = areas.difference(green)
            for rs in cproduct(tiles(x, y) for x in red):
              # pack them into a y by y square
              ps2 = pack(rs, z, y)
              if ps2 is None: continue
              # output packings
              printf("red {red} around {z} x {z} green in {y} x {y}\n-> {ps2}")
              printf("green {green} around {x} x {x} yellow in {z} x {z}\n-> {ps1}")
              printf()
      

      Solution: The dimensions of the red stones (in inches) are: 3×39, 6×45, 9×36, 12×42.

      Here is a diagram of one possible layout:

      Like

      • Frits's avatar

        Frits 1:01 pm on 10 July 2022 Permalink | Reply

        @Jim, it is not stated that the yellow and green tiles form a square.

        Like

        • Jim Randell's avatar

          Jim Randell 1:11 pm on 10 July 2022 Permalink | Reply

          @Frits: Is there a way the red stones can enclose the green ones without forming a square?

          Like

        • Jim Randell's avatar

          Jim Randell 1:14 pm on 10 July 2022 Permalink | Reply

          I suppose it could be a non-square rectangle. But luckily it is possible to do it with a square.

          Like

          • Frits's avatar

            Frits 1:18 pm on 10 July 2022 Permalink | Reply

            So there might be another solution…

            Like

          • Jim Randell's avatar

            Jim Randell 1:26 pm on 10 July 2022 Permalink | Reply

            I made some tweaks to my program, and it didn’t find any solutions with a non-square rectangle. But I’ll have a closer look.

            Like

          • Jim Randell's avatar

            Jim Randell 2:53 pm on 10 July 2022 Permalink | Reply

            This is my program adapted to consider packing the green and yellow tiles into a (possibly non-square) rectangle.

            It doesn’t find any additional solutions.

            Run: [ @replit ]

            from enigma import (divisors_pairs, div, subsets, cproduct, printf)
            import rectpack
            
            # areas of tiles we are given
            areas = {504, 420, 324, 306, 270, 130, 117, 112}
            
            # record possible tiles
            dps = dict((x, list(divisors_pairs(x))) for x in areas)
            
            # select tiles by area <x>, with max dimension not exceeding <m>
            tiles = lambda x, m: ((a, b) for (a, b) in dps[x] if not(b > m))
            
            # pack rectangles <rs> and rectangle <s> into a bounding rectangle <t>
            def pack(rs, s, t):
              (a, b) = t
              # make the list of rectangles
              rs_ = [s]
              rs_.extend(rs)
              # pack them into a the rectangle
              for ps in rectpack.pack(t[0], t[1], rs_):
                # check that s is not on the edge
                if not any(
                  x > 0 and y > 0 and x + w < a and y + h < b and {w, h} == set(s)
                  for (x, y, w, h) in ps
                ): continue
                return ps  # we only need one packing
            
            # consider divisors of the total area
            for (a, b) in divisors_pairs(sum(areas)):
              # calculate x and y
              (x, y) = (div(b - a, 2), div(a + b, 2))
              if x is None or y is None or y < x + 4: continue
              # choose 4 green tiles to go around the central x by x square
              for green in subsets(areas, size=4):
                for (a, b) in divisors_pairs(x * x + sum(green)):
                  if a < x + 2 or y < b + 2: continue
                  # consider dimensions of green tiles
                  for gs in cproduct(tiles(x, b) for x in green):
                    # pack them into an a by b rectangle
                    ps1 = pack(gs, (x, x), (a, b))
                    if ps1 is None: continue
                    # consider dimensions of red tiles
                    red = areas.difference(green)
                    for rs in cproduct(tiles(x, y) for x in red):
                      # pack them into a y by y square
                      ps2 = pack(rs, (a, b), (y, y))
                      if ps2:
                        # output packings
                        printf("red {red} around {a} x {b} green in {y} x {y}\n-> {ps2}")
                        printf("green {green} around {x} x {x} yellow in {a} x {b}\n-> {ps1}")
                        printf()
            

            Like

    • Frits's avatar

      Frits 5:02 pm on 10 July 2022 Permalink | Reply

      One piece of logic is not coded, it turns out it is not needed.

         
      from enigma import divisors_pairs
      from itertools import product
       
      # find three other red pieces which can form the outer ring
      def check(a, b, d):
        # combinations of length and width of fourth red piece
        lst = [[[big_square_side - x, (big_square_side - y) * x] 
                for x in d[big_square_side - y] if (big_square_side - x) in d] 
               for y in [a, b]]
        
        # check every combination of possible length and width
        for c, d in product(lst[0], lst[1]):
          # has the fourth piece a known area
          if c[0] * d[0] not in d_red: continue
      
          # do all four pieces have a different area
          if len(set([c[0] * d[0], a * b, c[1], d[1]])) != 4: continue
          # return all four pieces
          return tuple(sorted([tuple(sorted([a, b])), 
                 tuple(sorted([big_square_side - a, big_square_side - c[0]])), 
                 tuple(sorted([big_square_side - b, big_square_side - d[0]])), 
                 tuple(sorted([c[0], d[0]]))]))
         
        return tuple()          
            
      # areas of tiles we are given
      areas = sorted([504, 420, 324, 306, 270, 130, 117, 112])
       
      # record possible tiles
      d_tiles = dict((x, list(divisors_pairs(x))) for x in areas)
      
      # possible areas for the big square
      sqs = {n * n for n in range(1, areas[-4] + 1)}
      
      area_greenred = sum(areas)
      
      # candidates for yellow area
      area_yellow_cands = [sq for sq in sqs if (sq + area_greenred) in sqs]
      
      # check all candidates for yellow areas 
      for area in area_yellow_cands:
        big_square_side = int((area + area_greenred)**.5)
        yellow = int(area**.5)
        area_yellow = area
        
        # adjust possible red tiles to drop tiles with a big length
        d_red = {k: [x for x in vs if x[0] <= big_square_side and 
                                      x[1] <= big_square_side] 
                    for k, vs in d_tiles.items()}
        
        d_side = dict()     # dictionary per side
        for k, vs in d_red.items():
          for x in vs:
            d_side[x[0]] = d_side.get(x[0], set()) | {x[1]}
            d_side[x[1]] = d_side.get(x[1], set()) | {x[0]}
      
        if big_square_side in d_side:
          # valid situation but not coded!
          continue
        
        # as each piece now is shorter than the side of the big square
        # we need to have a form so that 2 sides of different pieces are equal to
        # the side of the big square, forms should be similar to this one:
        #
        #   +----------+--+
        #   |          |  |
        #   +----+-----+  |
        #   |    |     |  |
        #   |    +-----+--+
        #   |    |        |
        #   +----+--------+
        
        # adjust dictionary to keep only sides <x> for which complementary side 
        # <big_square_side> - <k> also exists
        d_side = {k: vs for k, vs in d_side.items() 
                        if big_square_side - k in d_side}
        # also adjust possible red tiles 
        d_red = {k: [x for x in vs if big_square_side - x[0] in d_side and 
                                      big_square_side - x[1] in d_side] 
                    for k, vs in d_red.items()}
       
        sol = set()
        for k, vs in d_red.items():
          # pick a first red piece
          for (le, wi) in vs:
            # find three other red pieces which can form the outer ring
            chk = check(le, wi, d_side)
            if chk:
              if chk not in sol:
                print("answer:", *chk)
              sol.add(chk)
      

      Like

  • Unknown's avatar

    Jim Randell 9:53 am on 29 May 2022 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 110: Rectangular blocks 

    From The Sunday Times, 5th May 1963 [link]

    Two rectangular blocks have all their dimensions an exact number of inches, all different.

    The sum of the edges of one block is equal to the sum of the edges of the other.

    The sum of the areas of the faces of one block is equal to the sum of the areas of the faces of the other.

    The volumes of the two blocks, however, differ by 140 cubic inches.

    If the smallest of the 6 dimensions of the two blocks is 5 inches, what are the dimensions of the blocks?

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

    [teaser110]

     
    • Jim Randell's avatar

      Jim Randell 9:54 am on 29 May 2022 Permalink | Reply

      For blocks with dimensions (x, y, z) this Python program consider blocks that be made with the same (x + y + z) value, and then looks for two with the same surface area, but with areas that differ by 140.

      It runs in 62ms. (Internal runtime is 4.7ms).

      Run: [ @replit ]

      from enigma import (irange, inf, decompose, subsets, multiply, printf)
      
      # for comparing surface areas
      area = lambda ds: 2 * sum(x * y for (x, y) in subsets(ds, size=2))
      
      # for calculating volumes
      vol = multiply
      
      # find the first solution
      def main():
        # consider increasing x + y + z values
        for t in irange(18, inf):
          # consider possible dimensions for the blocks
          for (b1, b2) in subsets(decompose(t, 3, increasing=1, sep=1, min_v=5), size=2):
            ds = set(b1 + b2)
            if not (len(ds) == 6 and 5 in ds): continue
            if not (area(b1) == area(b2)): continue
            if not (abs(vol(b1) - vol(b2)) == 140): continue
            printf("t={t}: {b1} {b2}")
            return  # we only need the first value
      
      main()
      

      Solution: The dimensions of the blocks are: (5 × 14 × 17) inches, (7 × 10 × 19) inches.

      Like

    • Jim Randell's avatar

      Jim Randell 11:38 am on 29 May 2022 Permalink | Reply

      Or, using some analysis:

      If the blocks have dimensions (a, b, c) and (x, y, z), then we have (assuming (a, b, c) has the larger volume):

      a + b + c = x + y + z
      ab + ac + bc = xy + xz + yz
      abc − xyz = 140

      Now, if we consider the expression:

      (a − 5)(b − 5)(c − 5) − (x − 5)(y − 5)(z − 5)
      = (abc − 5(ab + ac + bc) + 25(a + b + c) − 125) − (xyz − 5(xy + xz + yz) + 25(x + y + z) − 125)
      = 140

      We note that one side of the subtraction must equal zero, so the other is ±140.

      And as all the numbers multiplied together in each side of the subtraction are non-negative, it must be the right-hand term that is zero. Hence x = 5.

      So we can look at divisors of 140 to determine the dimensions (a, b, c), and then choose (x, y, z) to give the same sum, and check the surface area constraint.

      Here is a Python program that fully explores the solution space. It runs in 57ms. (Internal run time is 236µs).

      Run: [ @replit ]

      from enigma import (subsets, divisors_tuples, is_pairwise_distinct, div, printf)
      
      # surface area
      area = lambda *ds: 2 * sum(x * y for (x, y) in subsets(ds, size=2))
      
      # start with x = 5
      x = 5
      # calculate dimensions for (a, b, c)
      for (a, b, c) in divisors_tuples(140, 3):
        (a, b, c) = (a + x, b + x, c + x)
        if not is_pairwise_distinct(a, b, c, x): continue
        # find possible y, z values, given xyz = abc - 140
        n = div(a * b * c - 140, x)
        if n is None: continue
        for (y, z) in divisors_tuples(n, 2):
          if not (x < y < z and x + y + z == a + b + c): continue
          # all dimensions are distinct
          if not is_pairwise_distinct(x, y, z, a, b, c): continue
          # check the areas
          if area(a, b, c) != area(x, y, z): continue
          # output solution
          printf("{b1} {b2}", b1=(x, y, z), b2=(a, b, c))
      

      Like

      • Mark Valentine's avatar

        Mark Valentine 12:42 pm on 30 May 2022 Permalink | Reply

        Nice trick to factorise with x-5 to obtain the zero. I was factorising with x+1 not getting anywhere.

        Like

      • Frits's avatar

        Frits 1:44 pm on 31 May 2022 Permalink | Reply

        @Jim,

        I you disable the area check in the first program you get answer (5, 6, 14) (7, 8, 10) and if you disable in the second program you get anwer (5, 14, 17) (7, 10, 19).

        Like

      • Frits's avatar

        Frits 2:16 pm on 31 May 2022 Permalink | Reply

        @GeoffR, the problem with MiniZinc most of the times that you have to set boundaries.
        You have chosen 25 as upper limit.

        Like

        • GeoffR's avatar

          GeoffR 6:59 pm on 31 May 2022 Permalink | Reply

          @Frits:
          The teaser description gives us a means to fix the LB.

          With no prior knowledge of the solution and no logical means to set the UB, I guess the UB could be set fairly high to try and find a solution in MiniZinc. It could then reduced by trial and error if an improvement in total run-time was needed.

          I did an experiment for this teaser, varying the upper bound, to see the variation in run-time to find a solution and the total run-time:

          Upper bound = 25 (Soln = 170ms, Total time = 170ms)
          Upper bound = 50 (Soln = 170ms, Total time = 180ms)
          Upper bound = 100 (Soln = 170ms, Total time = 230ms)
          Upper bound = 200 (Soln = 170ms, Total time = 530ms)

          Absolute boundaries do not seem that important practically in this case.

          Like

      • Frits's avatar

        Frits 8:41 pm on 31 May 2022 Permalink | Reply

           
        from enigma import is_square_p
        
        # dimensions are (a + 5), (b + 5), (c + 5) and 5, y, z
        
        # as (4, 5, 7) is the 140-factorization with the highest lowest factor
        # a is less equal to 4 (and not divisble by 3)
        for a in [1, 2, 4]:
          # as (1, 10, 14) is the 140-factorization with the highest middle factor
          # b is less equal to 10 (and not divisble by 3 or 8)
          for b in [2, 4, 5, 7, 10]:
            if b <= a: continue
            
            # a * b * c = 140
            (c, r) = divmod(140, a * b)
            if r or c <= b: continue
            
            # sum of edges is the same
            # a + b + c + 15 - 5 - y = z
            # volume difference is equal to 140
            # ((a + 5) * (b + 5) * (c + 5) - 140) / 5y = z
            
            # (5 * y) * (a + b + c + 10 - y) = ((a + 5) * (b + 5) * (c + 5) - 140) 
            # 5y^2 - 5 * (a + b + c + 10) * y + (a + 5) * (b + 5) * (c + 5) - 140 = 0
            
            # calculate discriminant
            D = (5 * (a + b + c + 10))**2 - 20 * ((a + 5) * (b + 5) * (c + 5) - 140)
            if D < 0 or not is_square_p(D): continue
            
            # choose lowest solution as y < z
            y = (5 * (a + b + c + 10) - D**.5) // 10 
            z = a + b + c + 15 - 5 - y
            print((a + 5, b + 5, c + 5), (5, y, z))
        

        Like

    • GeoffR's avatar

      GeoffR 12:45 pm on 29 May 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 5..25:a; var 5..25:b; var 5..25:c; % 1st block
      
      var 6..25:x; var 6..25:y; var 6..25:z; % 2nd block (bigger)
      
      constraint all_different([a, b, c, x, y, z]);
      
      constraint a == 5;
      constraint a < b /\ b < c;
      constraint x < y /\ y < z;
      
      % The sum of the edges of one block is equal
      % to the sum of the edges of the other.
      constraint 4 * (a + b + c) == 4 * (x + y + z);
      
      % The sum of the areas of the faces of one block is equal 
      % to the sum of the areas of the faces of the other.
      constraint 2 * (a * b + a * c + b * c) == 2 *(x * y + y * z + x * z);
      
      % The volumes of the two blocks differ by 140 cubic inches.
      constraint x * y * z == a * b * c + 140;
      
      solve satisfy;
      
      output[ "First block dimensions(inches) : " ++ show(a) ++ 
      " by " ++ show(b) ++ " by " ++ show(c) 
      ++ "\n" ++ "Second block dimensions(inches) : " ++ show(x) 
      ++ " by " ++ show(y) ++ " by " ++ show(z)];
      
      % First block dimensions(inches) : 5 by 14 by 17
      % Second block dimensions(inches) : 7 by 10 by 19
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:24 am on 25 March 2021 Permalink | Reply
    Tags: by: J S Rowley   

    Brain-Teaser 717: Wrong hands 

    From The Sunday Times, 13th April 1975 [link]

    Waking in the night I glanced at my bed-side clock and thought it indicated just after 2:20. Putting on my spectacles and looking more carefully I saw that it was actually just after 4:10. I had, of course, interchanged the hands at my first glance.

    I began to wonder what time around then that my observation would have occurred, to the exact fraction of a minute, if the hands could have been exactly interchanged.

    What do you think?

    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.

    [teaser717]

     
    • Jim Randell's avatar

      Jim Randell 8:26 am on 25 March 2021 Permalink | Reply

      I we divide the circle of the clock face into 60 sectors, then the minute has travels at 1 sector/minute, and the hour hand at 1/12 sector/minute.

      If we suppose the difference (in minutes) between the times is d, then the hour hand has moved a distance d / 12 sectors (which is roughly 1/6 of the circle), and the minute hand has moved distance d sectors (and roughly twice – 1/6 times around the circle):

      d = 120 − (d / 12)
      d = 1440 / 13
      d = 110 + 10/13

      So the difference between the times is nearly 111 minutes, about what we would expect.

      If we suppose the actual time is m minutes after 4 o’clock (i.e. 240 + m minutes), then the minute hand will be showing m minutes, which is m sectors around the circle.

      And at the mistaken time = (240 + m − d) the hour hand would be showing the same point:

      (240 + m − d) / 12 = m
      240 − d = 11m
      m = (240 − d) / 11
      m = 1680 / 143
      m = 11 + 107/143

      Solution: The exact time would be 11 + 107/143 minutes after 4 o’clock.

      Like

    • Hugh Casement's avatar

      Hugh Casement 1:45 pm on 25 March 2021 Permalink | Reply

      I make that 11 minutes, 44 and 128/143 seconds,
      mistaken for 20 minutes, 58 and 106/143 seconds (20 and 140/143 minutes) after 2.

      There’s a lot to be said for digital clocks, especially in the middle of the night!

      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