Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 8:18 am on 10 May 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 695: Sitting on a gate 

    From The Sunday Times, 10th November 1974 [link]

    Tom’s mother fetches him by car from school every day, starting out from home so that she arrives at school at 4:15 pm. He is waiting for her and gets into the car at once, and is driven home without delay. This is the normal routine.

    One day Tom expects to be delayed at school for an hour and his mother delays her time of leaving home accordingly. The expected delay does not take place so instead of telephoning home Tom sets out at 4:15 to walk towards home. After walking for half an hour he sits on a convenient roadside gate and waits for the car.

    When it comes he gets in at once and is driven straight home, where they arrive 48 minutes later than usual.

    For how many minutes was Tom sitting on the gate?

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

    [teaser695]

     
    • Jim Randell's avatar

      Jim Randell 8:19 am on 10 May 2022 Permalink | Reply

      We assume that the car is driven at a constant speed, which does not depend on the time of day.

      Tom sets off walking from the school at 4:15 pm and arrives at the gate at 4:45 pm.

      Mum sets off 60 minutes later than usual, and arrives back home 48 minutes later than usual.

      So Tom’s walking has cut 12 minutes off the car journey. Hence the gate is 6 minutes drive from the school.

      So, on a normal day, Mum would pass the gate 6 minutes before arriving at the school, i.e. at 4:09 pm.

      On the abnormal day, she left an hour later, and so arrives at the gate at 5:09 pm, by which time Tom has been waiting 24 minutes.

      Solution: Tom has been waiting at the gate for 24 minutes.

      Like

  • Unknown's avatar

    Jim Randell 3:58 pm on 6 May 2022 Permalink | Reply
    Tags:   

    Teaser 3111: Don’t miss a second 

    From The Sunday Times, 8th May 2022 [link] [link]

    I have an analogue wall clock with a second hand, and I also have a separate 24-hour hh:mm:ss digital clock. The wall clock loses a whole number of seconds over a two-digit period of seconds. The digital clock gains at a rate 2.5% greater than the wall clock loses. After resetting both clocks to the correct time, I noticed that later in the week, at a time one hour earlier than the time of setting, both clocks were displaying the same wrong time.

    I can reset one of the clocks at an exact hour so that it will show the correct time when the televised rugby kicks off at 19:15:00 on the 31st.

    What is the latest time (hour and date) when I can do this?

    The puzzle text has been reworded to (hopefully) remove an ambiguity.

    [teaser3111]

     
    • Jim Randell's avatar

      Jim Randell 5:39 pm on 6 May 2022 Permalink | Reply

      I assumed that the wall clock is a standard 12 hour clock.

      This Python program looks for possible fractions for the wall clock to run slow, and corresponding values for the digital clock to run fast, until we find a pair of values that allows the clocks to apparently show the same time, on a day one hour ahead of the time they were set.

      We can then look backwards for times when a clock could be set on the hour and report the correct time at 19:15:00 on the 31st. The first value we find is the latest possible time.

      The program runs in 112ms.

      Run: [ @replit ]

      from enigma import (Rational, irange, uniq, sprintf, printf)
      
      Q = Rational()
      
      # digital clock gains (41/40)k seconds for every k seconds lost by the
      # wall clock
      m = Q(41, 40)
      
      # gain for wall clock and digital clock after <t> seconds
      W = lambda f, t: int(t * (1 - f))
      D = lambda f, t: int(t * (1 + f * m))
      
      # convert (days, hours, minutes, seconds) to seconds
      hms2t = lambda d=0, h=0, m=0, s=0: s + (60 * (m + 60 * (h + 24 * d)))
      
      H12 = hms2t(h=12) # 12 hours
      H24 = hms2t(h=24) # 24 hours
      
      # format seconds as: [<days>+]<hh>:<mm>:<ss>
      def t2hms(t):
        (m, s) = divmod(t, 60)
        (h, m) = divmod(m, 60)
        (d, h) = divmod(h, 24)
        x = ('' if d == 0 else sprintf("{d}+"))
        return sprintf("[{x}{h:02d}:{m:02d}:{s:02d}]")
      
      # n = the 2-digit period of seconds
      # k = number of seconds the 12 hour clock loses every n seconds
      for f in uniq(Q(k, n) for n in irange(10, 99) for k in irange(1, n - 1)):
      
        # consider times that are 1 hour earlier than the setting time
        for h in irange(23, 168, step=24):
          t = hms2t(h=h)
          w = W(f, t)
          d = D(f, t)
          # are they separated by a multiple of 12 hours?
          (p, r) = divmod(d - w, H12)
          if r != 0: continue
          printf("f={f}; h={h} w={w} d={d} p={p}", w=t2hms(w), d=t2hms(d))
      
          tgt = hms2t(d=31, h=19, m=15)
          # consider clocks set (x hours + 15 minutes) ago
          for x in irange(0, 1000):
            t = hms2t(h=x, m=15)
            # wall clock (has it lost 12 hours?)
            w = t - W(f, t)
            if w % H12 == 0:
              printf("-> wall clock: set @ {s}", s=t2hms(tgt - t))
              break
            # digital clock (has it gained 24 hours?)
            d = D(f, t) - t
            if d % H24 == 0:
              printf("-> digital clock: set @ {s}", s=t2hms(tgt - t))
              break
          printf()
      

      Solution: The latest time is 11:00:00 (i.e. 11:00 am) on the 26th.


      The wall clock loses 32 seconds every 57 seconds (i.e. it goes forward at a rate of 25 seconds every 57 seconds).

      And the digital clock gains (41/40)×32 seconds every 57 seconds = 164/5 seconds every 57 seconds = 164 seconds every 285 seconds. So it goes forwards at a rate of 449 seconds every 285 seconds.

      These are very inaccurate clocks.

      If we suppose the clocks are put right at midnight (= 00:00:00), then in 95 hours time, it will be 23:00:00, almost 4 days later.

      And the wall clock will have advanced by:

      95 × 3600 × (25/57) = 150000 seconds
      = 1 day, 17 hours, 40 minutes

      So it will be showing a time of 5:40 (pm).

      The digital clock will have advanced by:

      95 × 3600 × (449/285) = 538800 seconds
      = 6 days, 5 hours, 40 minutes

      So it will be showing a time of 05:40:00.

      i.e. both clocks will be showing a time of exactly “twenty to six”.


      Now let us suppose the wall clock is set to the right time at 11:00:00 on the 26th.

      At 19:15:00 on the 31st the elapsed time is 5 days, 8 hours, 15 minutes = 461700 seconds.

      And the wall clock will be showing an elapsed time of:

      461700 × (25/57) = 202500 seconds
      = 2 days, 8 hours, 15 minutes

      i.e. the time showing will be: 7:15 (pm), as required.

      Like

  • Unknown's avatar

    Jim Randell 9:53 am on 5 May 2022 Permalink | Reply
    Tags:   

    Teaser 2856: Solid jellometry 

    From The Sunday Times, 18th June 2017 [link] [link]

    As a wedding gift, Granny Lucci gave us a set of five jelly moulds, which was odd in many ways. The base of each mould was a different regular polygon (including a triangle) with each side an odd number of centimetres in length. Each polygon had the same length perimeter, an odd two figure number of centimetres.

    The sides of the moulds were vertical and the heights of the moulds were all the same odd number of centimetres. The volumes of the moulds were all between one and two litres.

    What was the height of the moulds?

    [teaser2856]

     
    • Jim Randell's avatar

      Jim Randell 9:55 am on 5 May 2022 Permalink | Reply

      This Python program run in 56ms (internal run time is 476µs).

      Run: [ @replit ]

      from math import (pi, tan)
      from enigma import (fdiv, irange, divisors, divf, divc, subsets, diff, intersect, printf)
      
      # area of a regular polygon with <n> sides and perimeter <p>
      def area(n, p):
        a = fdiv(pi, n)
        return fdiv(p * p, 4 * n * tan(a))
      
      # consider odd values for p = perimeter
      for p in irange(11, 99, step=2):
      
        # look for divisors of p (excluding 1)
        ds = list(d for d in divisors(p) if d > 1)
      
        # it must have at least 5 divisors, one of which is 3
        if not (len(ds) > 4 and 3 in ds): continue
      
        # consider possible odd heights for each divisor
        ps = dict()
        for n in ds:
          a = area(n, p)
          hs = list(h for h in irange(divc(1000, a), divf(2000, a)) if h % 2 == 1)
          if hs:
            ps[n] = hs
        # the smallest prism is triangular
        if 3 not in ps: continue
      
        # choose the 4 prisms with more than 3 sides
        for ns in subsets(diff(sorted(ps.keys()), [3]), size=4, fn=list):
          # add in the triangular prism
          ns.insert(0, 3)
          # find common heights
          for h in intersect(ps[n] for n in ns):
            # output solution
            printf("perimeter = {p}, height = {h}")
            for n in ns:
              printf("  {n} sides -> side = {x}; vol = {v:.2f}", x=(p // n), v=area(n, p) * h)
            printf()
      

      Solution: The moulds are 11 cm high.

      Each of the five moulds has a base with a perimeter of 45 cm.

      3 sides of 15 cm each; volume = 1071.71 cm³
      5 sides of 9 cm each; volume = 1532.95 cm³
      9 sides of 5 cm each; volume = 1700.00 cm³
      15 sides of 3 cm each; volume = 1746.59 cm³
      45 sides of 1 cm each; volume = 1769.71 cm³

      Like

    • Hugh+Casement's avatar

      Hugh+Casement 3:28 pm on 5 May 2022 Permalink | Reply

      There are only four values for the perimeter with enough divisors to give five different polygons.
      Of those, only 45 cm (and height 11 cm) results in all the volumes being between 1 and 2 litres.
      The polygons have sides 3, 5, 9, 15, and 45 sides.

      Like

  • Unknown's avatar

    Jim Randell 7:58 am on 3 May 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 693: Ready … 

    From The Sunday Times, 27th October 1974 [link]

    The Phytteness School marathon attracted 16 entrants this year. Each of the five houses entered a team of three runners, and the field was made up by the maths master, Des Chappelle. The school houses were competing for the trophy. The number of points by each entrant would be equal to his finishing position.

    The five houses tied for the cup, their totals being equal, although no two competitors tied for the same position. In order to determine the order in which the houses would hold the cup (they had agreed to hold it for 73 days each), they multiplied the finishers’ positions together in each house. The house with the smallest product, Black, would hold the cup first and so on to the house with the largest product, Blue, who hold it last. Unfortunately, Green and White houses still tied, and had to be separated by the toss of a coin.

    Des noted later that no house had had two finishers in consecutive positions, although Green house would have achieved this had he not managed to get between two of their runners right on the line.

    In what positions did the Red house runners finish?

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

    [teaser693]

     
    • Jim Randell's avatar

      Jim Randell 8:00 am on 3 May 2022 Permalink | Reply

      There are T(16) = 136 points available, but Des finished between two of the other runners, so he must have finished with 2 – 15 points. And the remaining positions must be shared between the 5 houses (3 positions each) to give the same sum.

      This Python program chooses a position for Des that give viable possible totals for each house, and then partitions the remaining positions among the houses. The groupings are then sorted by product, and the 3 groups in the middle of the field must have 2 products between them. The group with the unique product are Red.

      The program runs in 59ms (internal run time is 312µs).

      Run: [ @replit ]

      from enigma import (tri, tuples, irange, div, diff, multiply, group, filter2, singleton, printf)
      
      # total number of points
      T = tri(16)
      
      # check increasing integer sequence <s> has no consecutive elements
      check = lambda s: all(y - x > 1 for (x, y) in tuples(s, 2))
      
      # partition the values into <k>-length groups, each sums to <t>
      def partition(vs, k, t, ss=[]):
        if len(vs) == k:
          if sum(vs) == t and check(vs):
            yield ss + [tuple(vs)]
        else:
          v0 = vs[0]
          for (i, v1) in enumerate(vs):
            if i == 0: continue
            v2 = t - (v0 + v1)
            if not (v2 > v1): break
            if v2 in vs:
              s = (v0, v1, v2)
              if check(s):
                yield from partition(diff(vs, s), k, t, ss + [s])
      
      # consider position for D (between 2 others)
      for D in irange(2, 15):
        # calculate points for each house
        H = div(T - D, 5)
        if H is None: continue
      
        # partition the remaining positions into groups of 3
        for ss in partition(diff(irange(1, 16), [D]), 3, H):
          # sort the groups by product (largest first)
          ss.sort(key=multiply, reverse=1)
          # find the products of the middle three (G, W, R)
          p = group(ss[1:-1], by=multiply)
          if len(p.keys()) != 2: continue
          Rs = Ws = Gs = None
          for (k, vs) in p.items():
            if len(vs) == 1:
              # red has a unique product
              Rs = vs[0]
            else:
              # green/white have shared products; green has D - 1 and D + 1
              (Gs, Ws) = filter2((lambda s: D - 1 in s and D + 1 in s), vs, fn=singleton)
          if Rs and Ws and Gs:
            # output solutions
            printf("D={D}; H={H}")
            printf("houses = {ss}")
            printf("-> blue = {ss[0]}")
            printf("-> green = {Gs}")
            printf("-> white = {Ws}")
            printf("-> red = {Rs}")
            printf("-> black = {ss[-1]}")
            printf()
      

      Solution: Red house finished 2nd, 9th, 14th.

      The points awarded were:

      Des = 11
      Blue = (5, 7, 13); sum = 25; product = 455
      Green = (3, 10, 12); sum = 25; product = 360
      White = (4, 6, 15); sum = 25; product = 360
      Red = (2, 9, 14); sum = 25; product = 252
      Black = (1, 8, 16); sum = 25; product = 128

      Like

    • GeoffR's avatar

      GeoffR 10:32 am on 3 May 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 2..15: des;  % Maths master - Des Chappelle
      
      var 1..16: a; var 1..16: b;var 1..16: c;  % Blue's points
      constraint b - a != 1 /\ c - b != 1 /\ b > a /\ c > b;
      
      var 1..16: d; var 1..16: e;var 1..16: f;  % Green's points
      constraint e - d != 1 /\ f - e != 1 /\ e > d /\ f > e;
      
      var 1..16: g; var 1..16: h;var 1..16: i;  % White's points 
      constraint h - g != 1 /\ i - h != 1 /\ h > g /\ i > h;
      
      var 1..16: j; var 1..16: k;var 1..16: l;  % Red's points 
      constraint k - j != 1 /\ l - k != 1 /\ k > j /\ l > k;
      
      var 1..16: m; var 1..16: n;var 1..16: o;  % Black's points
      constraint n - m != 1 /\ o - n != 1 /\ n > m /\ o > n;
      
      % No two competitors tied for the same position
      constraint all_different ([des,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o]);
      
      % The five houses tied for the cup, their totals being equal
      constraint a + b + c == d + e + f /\ a + b + c == g + h + i
      /\ a + b + c == j + k + l /\ a + b + c == m + n + o;
      
      % Black's points have the smallest product
      constraint m * n * o < j * k * l /\  m * n * o < g * h * i
      /\ m * n * o < d * e * f /\ m * n * o < a * b * c;
      
      % Blue's points have the largest product
      constraint a * b * c > d * e * f /\ a * b * c > g * h * i
      /\ a * b * c > j * k * l /\ a * b * c > m * n * o;
      
      % The product of Green's points equals the product of White's points
      constraint d * e * f == g * h * i;
      
      % Location of Des' points in relation to the Green house points
      constraint (des - d == 1 /\ e - des == 1 ) 
      \/ (des - e == 1 /\ f - des == 1);
      
      solve satisfy;
      
      output ["Des = " ++ show(des) ++ "\n" ++
      "Blue:   [a,b,c] = " ++ show([a,b,c]) ++ "\n" ++
      "Green:  [d,e,f] = " ++ show([d,e,f]) ++ "\n" ++
      "White:  [g,h,i] = " ++ show([g,h,i]) ++ "\n" ++
      "Red:    [j,k,l] = " ++ show([j,k,l]) ++ "\n" ++
      "Black:  [m,n,o] = " ++ show([m,n,o]) ];
      
      % Des = 11
      % Blue:   [a,b,c] = [5, 7, 13]
      % Green:  [d,e,f] = [3, 10, 12]
      % White:  [g,h,i] = [4, 6, 15]
      % Red:    [j,k,l] = [2, 9, 14]  <<< Answer
      % Black:  [m,n,o] = [1, 8, 16]
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:33 pm on 29 April 2022 Permalink | Reply
    Tags:   

    Teaser 3110: Many a slip 

    From The Sunday Times, 1st May 2022 [link] [link]

    I have written down three 3-figure numbers in decreasing order and added them up to give their 4-figure sum, which is a perfect square: the digit 0 occurred nowhere in my sum.

    Now I have attempted to replace digits consistently by letters and I have written the sum as:

    CUP + AND + LIP = SLIP

    However, there’s “many a slip twixt cup and lip” and unfortunately one of those thirteen letters is incorrect. If you knew which letter was incorrect then you should be able to work out the three 3-figure numbers.

    What are they?

    [teaser3110]

     
    • Jim Randell's avatar

      Jim Randell 5:15 pm on 29 April 2022 Permalink | Reply

      I used a variation on the [[ bungled_sum() ]] function that I have used before in other puzzles (see: Puzzle 56, Teaser 2952).

      When the setter transcribed the sum to an alphametic there are two ways a mistake could be made. Firstly a symbol could be assigned to more than one digit. And secondly a digit could be assigned to more than one symbol (assuming the setter is aiming for each letter standing for a different digit – although this is not explicitly stated in the puzzle text)).

      We consider each symbol in the alphametic sum in turn, and replace it by a “wild card” symbol to create a modified sum. If the puzzle created with this modified sum has a single solution then we have found the answer to the original puzzle.

      The following Python program runs in 276ms.

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, irange, union, update, singleton, join, printf)
      
      # the terms in the alphametic sum
      terms = ['CUP', 'AND', 'LIP', 'SLIP']
      
      # we use X to denote the bungled letter
      assert 'X' not in union(terms)
      
      # solve the alphametic sum and return the values of the terms
      def solve(terms, i, j):
        # create terms for the modified sum
        ts = update(terms, [(i, update(t, [(j, 'X')]))])
        # split the terms into the summands and the result
        ss = list("{" + x + "}" for x in ts)
        r = ss.pop(-1)
        # make the expressions
        exprs = [
          # the sum
          join(ss, sep=" + ") + " = " + r,
          # summands are in descending order
          join(ss, sep=" > "),
          # result is a square
          "is_square_p(" + r + ")",
        ]
        # distinct symbols
        syms = union(ts).difference("X")
        distinct = [ join(syms) ]
        # is the replaced symbol still present in the modified sum?
        x = terms[i][j] # replaced symbol
        if x in syms:
          # X must be different from the replaced symbol
          distinct.append('X' + x)
        else:
          # X must be the same as one of the other symbols
          exprs.append("{X} in {{" + join(syms, sep="}, {") + "}}")
        # create the modified puzzle (digits used are 1-9)
        p = SubstitutedExpression(exprs, digits=irange(1, 9), distinct=distinct, verbose=0)
        # find solutions
        for s in p.solve():
          # return the values of the terms
          yield tuple(int(p.substitute(s, t)) for t in ts)
      
      # consider the suspect term
      for (i, t) in enumerate(terms):
        # and the suspect symbol in the term
        for (j, x) in enumerate(t):
          # is there a unique solution to a modified puzzle?
          vs = singleton(solve(terms, i, j))
          if vs is None: continue
          # return: bungled symbol (term, index) and the values of the terms
          printf("term {i} [{t}], index {j} -> {vs}")
      

      Solution: The numbers are: 826, 574, 536.

      So we have:

      826 + 574 + 536 = 1936 (= 44²)

      Which when substituted as:

      CUP + AND + LIP = SLIP

      Makes the A and the first L map to 5, and the second L maps to 9.

      This corresponds to a single mistake in transcribing the 5 in 536 to L, when it should be mapped to A. (i.e. a correct transcription is: CUP + AND + AIP = SLIP).

      And this is the only sum that correspond to a mistake at the 1st digit of the 3rd summand (i.e. the L of LIP).


      The other potential sums, which are discounted as there is more than one sum corresponding to a mistake at the same position, are:

      522 + 478 + 369 = 1369 (= 37²)
      572 + 428 + 369 = 1369 (= 37²)

      These two sums correspond to mistakes in transcribing the 3rd digit of the 1st summand (i.e. the P of CUP), or the 3rd digit of the 3rd summand (i.e. the P of LIP).

      And (possibly):

      529 + 471 + 369 = 1369 (= 37²)
      579 + 421 + 369 = 1369 (= 37²)

      These two sums correspond to mistakes in transcribing the 3rd digit if the second summand (i.e. the D in AND), or the 1st digit of the result (i.e. the S in SLIP).

      Although if the setter is only attempting to “replace digits consistently by letters”, but not have “each letter standing for a different digit”, then this last scenario would not count as having a mistake in it.

      Like

    • GeoffR's avatar

      GeoffR 8:19 pm on 29 April 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Four digit squares list
      set of int: sq4 = {x*x | x in 32..99}; 
      
      % Alternative substituted sum - to compare digits.
      % abc + efg + hij = kmnp
      var 1..9:a; var 1..9:b; var 1..9:c;
      var 1..9:e; var 1..9:f; var 1..9:g;
      var 1..9:h; var 1..9:i; var 1..9:j;
      var 1..9:k; var 1..9:m; var 1..9:n; var 1..9:p;
      
      % letters in original sum [C,U,P,A,N,D,L,I,S]
      var 1..9:C; var 1..9:U; var 1..9:P;
      var 1..9:A; var 1..9:N; var 1..9:D;
      var 1..9:L; var 1..9:I; var 1..9:S;
      
      % Components of the substituted sum
      var 100..999:abc == 100*a + 10*b + c;
      var 100..999:efg == 100*e + 10*f + g;
      var 100..999:hij == 100*h + 10*i + j;
      var 1000..9999:kmnp == 1000*k + 100*m + 10*n + p;
      
      % The substituted sum
      constraint abc + efg + hij = kmnp;
      
      % Three 3-digit numbers are in descending order
      constraint abc > efg /\ efg > hij;
      
      % 4-figure sum is a perfect square:
      constraint kmnp in sq4;
      
      % Substituted sum has 9 correct digits (1..9) from 13 digits
      constraint card({a,b,c,e,f,g,k,m,n,p}) == 9;
      
      % Teaser sum has 8 correct digits (1..9) from 13 digits
      %... since 1 digit is repeated
      constraint card( {C,U,P,A,N,D,L,I,S} ) == 8;
      
      % Teaser Sum: CUP + AND + LIP = SLIP
      %
      % Only 1 letter is repeated in capital letters sum
      % ... so compare letters in original and substituted sums
      constraint sum ([ a != C, b != U, c != P,
                        e != A, f != N, g != D,
                        h != L, i != I, j != P,
                        k != S, m != L, n != I,
                        p != P]) == 1;   
      
      solve satisfy;
      
      output [ show(abc) ++ " + " ++ show(efg) ++ " + " ++ 
      show(hij) ++ " = " ++ show(kmnp) ];
      
      % There was one unique sum from 11 total multiple outputs
      % This unique sum gives the values of CUP, AND and LIP
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:33 am on 28 April 2022 Permalink | Reply
    Tags:   

    Teaser 2853: Marble marvel 

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

    I had a cuboid (or rectangular block) of marble whose volume in cubic centimetres was divisible by each of the numbers 1, 2, 3, 4, 5, 6, 7, 8 and 9. Unfortunately I dropped the block and it broke into two cuboids, and surprisingly the two new blocks were of similar shape to each other (i.e. one was a magnification of the other). The lengths of the sides of the original block and the two new blocks were all two-figure numbers of centimetres.

    What were the lengths of the sides of the original block?

    [teaser2854]

     
    • Jim Randell's avatar

      Jim Randell 10:34 am on 28 April 2022 Permalink | Reply

      The block splits along a plane parallel to the face with the smallest area, to give two similar blocks with dimensions (a, b, c) and (b, c, d) (with dimensions in increasing order).

      The blocks are similar, so we have:

      b / a = c / b = d / c

      From which we can calculate c and d given values for a and b:

      c = b² / a
      d = c² / b = b³ / a²

      The original block is (b, c, a + d) and the volume of this is divisible by 2520 (= lcm(1, …, 9)).

      And each of (a, b, c, d, a + d) must be 2-digit integers.

      This Python program runs in 53ms (internal run time is 507µs).

      Run: [ @replit ]

      from enigma import (irange, mlcm, call, printf)
      
      # total volume must be divisible by m
      m = call(mlcm, irange(1, 9))
      
      # choose values for a, b
      for a in irange(10, 99):
        a2 = a * a
        for b in irange(a, 99):
          b2 = b * b
          # calculate d
          (d, r) = divmod(b2 * b, a2)
          if a + d > 99: break
          if r != 0: continue
          # calculate c
          (c, r) = divmod(b2, a)
          if r != 0: continue
          # check total volume is divisible by m
          if (b * c * (a + d)) % m != 0: continue
          # output solution
          printf("({a}, {b}, {c}) + ({b}, {c}, {d}) = ({b}, {c}, {ad})", ad=a + d)
      

      Solution: The original block measured 24 cm × 36 cm × 70 cm.

      Like

    • GeoffR's avatar

      GeoffR 6:28 pm on 28 April 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % All dimensions must be 2-digit integers
      % The original block has dimensions (B, C, A + D)
      var 10..99:A; var 10..99:B; var 10..99:C; var 10..99:D;
      
      constraint all_different ([A, B, C, D]) /\ A + D >= 20 /\ A + D <=99;
      constraint B > A /\ C > B /\ D > C;
      
      % Volume of block was divisible by 1, 2, 3, 4, 5, 6, 7, 8 and 9.
      constraint B * C * (A + D) mod 2520  == 0;
       
      % Reusing Jim's equations
      constraint A * C == B * B;
      constraint D * B == C * C /\ D * A * A == B * B * B;
      
      solve satisfy;
      
      output ["Original Block Dimensions were " ++ show(B) ++ " by " 
      ++ show(C) ++ " by " ++ show(A + D) ++ " cm." ];
      
      % Original Block Dimensions were 24 by 36 by 70 cm.
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:39 am on 26 April 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 690: … And two poles 

    From The Sunday Times, 6th October 1974 [link]

    In my garden, which is level, there are two vertical flagpoles and although there is little apparent difference in their respective heights I know that their actual heights are each an exact but different number of inches.

    As part of their anchoring taut wires stretch from the very top of each to the foot of the other. The crossing-point of these two wires is exactly eleven feet from the ground.

    What are the heights of the flagpoles?

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

    [teaser690]

     
    • Jim Randell's avatar

      Jim Randell 9:42 am on 26 April 2022 Permalink | Reply

      When resistors with values r1 and r2 are connected in parallel, the value of the combined resistance R is given by:

      1/R = 1/r1 + 1/r2

      (See: Teaser 3058).

      The value of R can also be calculated using the following diagram:

      Where the height of the crossing point gives the combined resistance.

      The flagpoles in the question also correspond to this diagram. Their heights being r1 and r2 and the crossing point being 11ft = 132in above the ground corresponds to R.

      So we need to find two different values whose reciprocals sum to 1/132. And the [[ reciprocals() ]] function in the enigma.py library can do this for us.

      The following Python program runs in 59ms.

      Run: [ @replit ]

      from enigma import (Accumulator, reciprocals, sprintf, printf)
      
      # find (a, b) such that 1/a + 1/b = 1/132
      # record the smallest difference (i.e. the final candidate)
      r = Accumulator(fn=min)
      for (a, b) in reciprocals(2, 132):
        # a, b must be different
        if a == b: continue
        r.accumulate_data(b - a, (a, b))
        printf("[a={a} b={b}]")
      
      # format <x> inches as "<f>ft <i>in"
      def fmt(x):
        (d, r) = divmod(x, 12)
        return sprintf("{d}ft {r}in")
      
      # output solution
      (a, b) = r.data
      printf("A = {a}; B = {b}", a=fmt(a), b=fmt(b))
      

      Solution: The heights of the flagpoles are: 21ft 1in, 23ft 0in.

      This is the solution where the flagpoles are as close as possible in height (we have: 1/253 + 1/276 = 1/132).

      But there are other candidate solutions where the difference in height is greater, e.g. the next solution would be 1/231 + 1/308 = 1/132 to give values of 19ft 3in and 25ft 8in.

      So to narrow the solution down to a single candidate we could say the flagpoles had a difference in height of less than 6ft.

      Like

    • GeoffR's avatar

      GeoffR 11:12 am on 26 April 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Assume Max flagpole height = 30 ft.
      var 1..360:a;
      var 1..360:b;
      
      % 1/a + 1/b = 1/132
      constraint 132 * (b + a) == a * b;
      
      % extra constraint for a single solution
      % ...the flagpoles had a difference in height of less than 6ft
      constraint a > b /\ a - b  < 72;
      
      solve minimize(a - b);
      
      output ["Heights of flagpoles = " ++ show(a) ++ 
      " and " ++ show(b) ++  " inches." ];
      
      % Heights of flagpoles = 276 and 253 inches.
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:48 am on 24 April 2022 Permalink | Reply
    Tags: by: S. T. Crump   

    Brain Teaser: Crossing the desert 

    From The Sunday Times, 29th March 1959 [link]

    David and Henry live on opposite sides of a desert which is 2,020 miles across. David has a lorry which can carry sufficient petrol for 1,008 miles, and Henry has a lorry which can carry petrol for 504 miles. They wish to meet in the desert and then return to their original starting-points. They can, of course, dump or exchange petrol.

    Assuming that Henry’s lorry can carry as much petrol as David’s, but consumes petrol at twice the rate, where should Henry and David meet if between the two of them they are to use the least amount of petrol?

    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.

    [teaser-1959-03-29] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:50 am on 24 April 2022 Permalink | Reply

      (See also: Enigma 1732, Teaser 2799 and [ @wikipedia ]).

      I am assuming that each driver fills up their vehicle with 1008 units of fuel at their base, and may establish fuel dumps in the desert using this fuel for later use.

      For the meeting each driver fills up and drives their vehicle to the meeting point, possibly using fuel from dumps they have previously established. At the meeting point fuel may be exchanged between the two, and then they return to their respective bases (again, possibly using fuel from dumps they have previously established).

      And I’m assuming there are no tricks, like abandoning their vehicles and walking some distance to a base, fuel dump or meeting point.

      When they are “exploring the desert” they each end up at their final dump with a full complement of fuel, and there is enough fuel to get them back to their home base if they return to the final base with no fuel remaining.

      So if they do not exchange any fuel, then D could travel 504 miles from his final dump and H could travel 252 miles from his final dump and they would each have enough fuel to get back to their respective final dumps. So, if no fuel is exchanged, the final dumps could be a maximum of 756 miles apart.

      However if they were to exchange fuel this distance can be increased. D’s vehicle is more efficient, so if H stays at his final dump, D can use his full tank to drive 1008 miles to H’s final dump, and arrive empty. D’s vehicle is fully refuelled at the dump, and both vehicles then have enough fuel to return to their respective bases.

      So we need to look for a minimum number of trips for H and D to establish final dumps 1008 miles apart.

      This Python program runs in 61ms.

      Run: [ @replit ]

      from enigma import (fdiv, irange, Accumulator, cache, printf)
      
      Q = fdiv  # floats are good enough
      
      R = 1008  # range (for D; H is half this)
      T = 2020  # distance between bases
      S = R  # max separation of final dumps
      
      # consider number of trips for D and total distance
      # (H can only go half as far in k trips)
      
      # consider distance to dump k (dump 1 = base)
      @cache
      def D(k):
        return (Q(R, 2 * k) + D(k - 1) if k > 1 else 0)
      
      # find minimum fuel use
      r = Accumulator(fn=min)
      
      # consider number of trips for H
      # (one trip is 0 length, as the fuel is given to D)
      for h in irange(1, 10):
        # position of H's final dump
        dh = Q(D(h), 2)
        # consider trips for D to reach H's final dump
        for d in irange(1, 20):
          # position of D's final dump
          dd = D(d)
          # separation between final dumps
          s = T - dh - dd
          if s > S: continue
          printf("D makes {d} trips; H makes {h} trips; total = {t}", t=d + h)
          printf("-> D's final dump is {dd:.2f} miles from base")
          printf("-> H's final dump is {dh:.2f} miles from base")
          printf("-> separation is {s:.2f} miles")
          # accumulate by minimum units of fuel
          r.accumulate_data(R * (d + h) - (S - s), (d, h, s))
          break
      
      (u, (d, h, s)) = (r.value, r.data)
      printf()
      printf("min = {t} trips, {u:.2f} units; final separation = {s:.2f} miles", t=d + h)
      

      Solution: They should meet 210 miles from Henry’s base.

      David should set up 6 fuel dumps at (72, 156, 256.8, 382.8, 550.8, 802.8) miles, and can then proceed to the final dump. This requires filling up at base 7 times.

      Henry should set up 2 fuel dumps at (84, 210) miles, and then proceed to the final dump. This requires filling up at base 3 times.

      Both David and Henry are at the final dumps with 1008 units of fuel each, and enough fuel at their respective dumps to get them back home if they arrive at the final dump with no fuel.

      The distance between the final dumps is 1007.2 miles, so David can drive between them with 0.8 units of fuel to spare. He can then replenish his supply, using Henry’s fuel, so he has enough to return to his final dump (and then there is enough fuel in his dumps to get him back to his base). And Henry has enough fuel in his dumps to return to his base.

      Altogether there are 10 fillings at the bases (7 at David’s and 3 at Henry’s), accounting for 10080 units of fuel. And when the two return to their bases there is 0.8 units of fuel remaining.

      Like

  • Unknown's avatar

    Jim Randell 4:38 pm on 22 April 2022 Permalink | Reply
    Tags:   

    Teaser 3109: Hole numbers 

    From The Sunday Times, 24th April 2022 [link]

    In theoretical golf, you have a set of “clubs” each of which hits a ball an exact number of yards forwards or backwards. You own the following set: 3, 8, 17, 19 and 35. For example, if a hole is 31 yards long, you can reach it in three strokes, with two forward hits of the 17 and a backward hit of the 3. In the next competition, you are only allowed to use three clubs, and the course consists of three holes whose lengths are 101, 151 and 197 yards. In order to get round the course in the fewest possible strokes, you must make a wise choice of clubs.

    Which three clubs (in ascending order) should you choose, and what will the individual hole scores be (in order)?

    [teaser3109]

     
    • Jim Randell's avatar

      Jim Randell 4:59 pm on 22 April 2022 Permalink | Reply

      This Python program runs in 59ms. (Internal runtime is 3.7ms).

      Run: [ @replit ]

      from enigma import ordered, subsets, Accumulator, printf
      
      clubs = [3, 8, 17, 19, 35]
      holes = [101, 151, 197]
      
      # generate hole lengths and strokes using the specified clubs
      # m = max number of strokes
      def generate(clubs, m):
        # record total distance and individual strokes
        d = { 0: () }
        # distances to extend
        ts = list(d.keys())
        while ts:
          ts_ = list()
          for t in ts:
            # choose a club
            for n in clubs:
              # forwards or backwards?
              for x in (n, -n):
                t_ = t + x
                # is this a new distance?
                if t_ > 0 and t_ not in d:
                  ns_ = ordered(x, *d[t], reverse=1)
                  yield (t_, ns_)
                  d[t_] = ns_
                  if len(ns_) < m:
                    ts_.append(t_)
          # process the next batch
          ts = ts_
      
      # find lowest total number of strokes
      r = Accumulator(fn=min, collect=1)
      
      # choose 3 clubs
      for cs in subsets(clubs, size=3):
        # attempt to make the required holes
        rs = list()
        for (t, ns) in generate(cs, 30):
          if t in holes:
            rs.append((t, ns))
            if len(rs) == 3:
              # find total number of strokes
              ts = sum(len(ns) for (t, ns) in rs)
              printf("[cs={cs} -> {ts} strokes]")
              r.accumulate_data(ts, (cs, rs))
              break
      
      # output solution
      printf()
      printf("min = {r.value} strokes")
      for (cs, rs) in r.data:
        printf("-> clubs = {cs}")
        for (t, ns) in sorted(rs):
          printf("--> hole {t} = {ns}; {n} strokes", n=len(ns))
        printf()
      

      Solution: The optimal set of clubs is (3, 17, 35). Which achieves the 101 hole in 5 strokes, the 151 hole in 7 strokes, the 197 hole in 9 strokes, for a total of 21 strokes.

      hole 101 = (35, 35, 17, 17, −3)
      hole 151 = (35, 35, 35, 35, 17, −3, −3)
      hole 197 = (35, 35, 35, 35, 17, 17, 17, 3, 3)

      If the selection of clubs is not restricted the holes can be achieved in 18 strokes (although only 3 clubs are used per hole):

      hole 101 = (35, 35, 17, 17, −3)
      hole 151 = (35, 35, 35, 35, 8, 3)
      hole 197 = (35, 35, 35, 35, 35, 19, 3)

      Like

    • GeoffR's avatar

      GeoffR 4:39 pm on 24 April 2022 Permalink | Reply

      This solution uses MiniZinc’s minimize constraint to minimise the total strokes for the clubs used for the three holes.

      It finds five sets of minimum total strokes in decreasing magnitude,
      The first four sets use clubs(3,8,35)

      The last (5th) print out is the answer to this teaser and uses a different set of three clubs to the first four solutions.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      % Fixed golf hole lengths (yd), specified in teaser
      int: H1 = 101;  
      int: H2 = 151; 
      int: H3 = 197; 
      
      % a set of “clubs”
      set of int: clubs = {3, 8, 17, 19, 35};
      
      % Three clubs used from a set of six clubs
      var clubs: c1; var clubs: c2; var clubs: c3;
      constraint all_different([c1, c2, c3]);
      
      % Multiples of club strokes for each hole for three clubs
      % .. allowing for 3rd multiple as possible backward strokes
      var 1..5: m1; var 1..5: m2; var -5..5: m3;  % Hole 101 yd
      var 1..5: m4; var 1..5: m5; var -5..5: m6;  % Hole 151 yd
      var 1..5: m7; var 1..5: m8; var -5..5: m9;  % Hole 197 yd
      
      % 1st hole(101 yd)
      constraint H1 == m1*c1 + m2*c2 + m3*c3;
      % 2nd hole(151 yd)
      constraint H2 == m4*c1 + m5*c2 + m6*c3;
      % 3rd hole(197 yd)
      constraint H3 == m7*c1 + m8*c2 + m9*c3;
      
      % use abs function for 3rd club possible negative value
      solve minimize(m1 + m2 + abs(m3) + m4 + m5 + abs(m6) + m7 + m8 + abs(m9));
      
      output ["Total strokes = " ++ 
      show (m1 + m2 + abs(m3) + m4 + m5 + abs(m6) + m7 + m8 + abs(m9))
      ++ ", Clubs used = " ++ show([c1, c2, c3]) 
      ++ "\n" ++ "Hole 101 yd = " ++ show([m1, c1, m2, c2, m3, c3])
      ++ "\n" ++ "Hole 151 yd = " ++ show([m4, c1, m5, c2, m6, c3])
      ++ "\n" ++ "Hole 197 yd = " ++ show([m7, c1, m8, c2, m9, c3])
      ];
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:46 am on 21 April 2022 Permalink | Reply
    Tags:   

    Teaser 2857: Significant errors 

    From The Sunday Times, 25th June 2017 [link] [link]

    Last year George and Martha were going to visit one of their daughters. Her house number was a two-figure number but unfortunately Martha wrote the number incorrectly by making the first digit less than it should be. When George discovered the error he commented that the incorrect number was a whole number percentage of the correct one. If you knew that percentage then you should be able to work out their daughter’s house number.

    Then the daughter moved to a different two-figure house number, Martha again wrote the number incorrectly by making the first digit less than it should be, and again the incorrect number was a whole number percentage of the correct one. If you knew that percentage then you should be able to work out their daughter’s new house number.

    What was the daughter’s original house number, and what is the new one?

    [teaser2857]

     
    • Jim Randell's avatar

      Jim Randell 8:46 am on 21 April 2022 Permalink | Reply

      This Python program collects possible (house-number, wrong-number, percentage-reduction) values, and then uses the [[ filter_unique() ]] function from the enigma.py library to find the required values for the first and second house numbers.

      It runs in 55ms. (Internal runtime is 333µs).

      Run: [ @replit ]

      from enigma import (irange, div, filter_unique, unpack, printf)
      
      # record (n, w, p) results
      rs = list()
      
      # consider two digit house numbers
      for n in irange(20, 99):
        # consider different numbers with the first digit less than the
        # correct digit, but the second digit is correct
        for w in irange(10 + (n % 10), n - 1, step=10):
          # calculate the percentage w / n
          p = div(100 * w, n)
          if p is None: continue
          rs.append((n, w, p))
      
      # if you knew the percentage you would know the correct house number
      fn_n = unpack(lambda n, w, p: n)
      fn_p = unpack(lambda n, w, p: p)
      for (n1, w1, p1) in filter_unique(rs, fn_p, fn_n).unique:
        printf("p1={p1}% -> n1={n1} w1={w1}")
      
        # look for results with a different house number
        rs1 = filter(unpack(lambda n2, w2, p2: n2 != n1), rs)
        for (n2, w2, p2) in filter_unique(rs1, fn_p, fn_n).unique:
          printf("  p2={p2}% -> n2={n2} w2={w2}")
        printf()
      

      Solution: The first house number was 50. The second house number was 75.

      There are two possible scenarios:

      Daughter’s first house was 50, but she wrote 20 (= 40% of 50).
      Daughter’s second house was 75, but she wrote 15 (= 20% of 75).

      Daughter’s first house was 50, but she wrote 40 (= 80% of 50).
      Daughter’s second house was 75, but she wrote 15 (= 20% of 75).

      But in both cases the first house number is 50 and the second house number is 75.


      Here are the 15 possible (house number, wrong number) pairs, grouped by percentage:

      20%: (50 → 10) (75 → 15)
      25%: (40 → 10) (80 → 20)
      40%: (50 → 20)
      50%: (20 → 10) (40 → 20) (60 → 30) (80 → 40)
      60%: (25 → 15) (50 → 30) (75 → 45)
      75%: (40 → 30) (80 → 60)
      80%: (50 → 40)

      From which we see the only values with a unique percentage are:

      40%: (50 → 20)
      80%: (50 → 40)

      Both cases give a first house number of 50.

      If we remove pairs with a first house number of 50 from those listed above we get:

      20%: (75 → 15)
      25%: (40 → 10) (80 → 20)
      50%: (20 → 10) (40 → 20) (60 → 30) (80 → 40)
      60%: (25 → 15) (75 → 45)
      75%: (40 → 30) (80 → 60)

      And we see there is only one value with a unique percentage:

      20%: (75 → 15)

      And so the second house number is 75.

      Like

  • Unknown's avatar

    Jim Randell 9:38 am on 19 April 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 668: Sweepstakes sixfold 

    From The Sunday Times, 28th April 1974 [link]

    A party of six racegoers arrived on the course with precisely £6 each with which to speculate. There were six races on the card and six runners in each race, so they decided to hold sweepstakes among themselves on each of the six races, the stake being £1 per race per person.

    The runners in each race were numbered 1, 2, 3, 4, 5 and 6, and each of the racegoers drew one of these numbers out of a hat. Each player’s number remained the same throughout the six races. There were thus, in effect, six separate sweepstakes, the holder of the winning number drawing £6 on each race. To add a little interest to the proceedings it was arranged that the winner on any one of the races would be permitted to buy (at cost price of £1) one additional chance in one or more of the races subsequent to his win, from one of the other players. Only a player who had not yet had a win could sell his chance.

    At the conclusion of the events it transpired that three players had made a net profit; the holder of No. 1 who won £4, the holder of No. 5 who won £1, and the holder of No. 4. The holder of No. 2 lost £3, and the holder of No. 6 lost £6.

    Three winning chances were sold, but none of these by the holders of Nos 1, 4 and 5. The holder of No. 1 did not have any transaction with the holders of Nos 2 and 3. There were no dead-heats and no number appeared in the winner’s frame in consecutive races.

    What, in order, were the numbers of the six winning horses?

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

    [teaser668]

     
    • Jim Randell's avatar

      Jim Randell 9:40 am on 19 April 2022 Permalink | Reply

      At the start of the races each participant has a profit of −6 (having bought the 6 tickets with their own number on).

      And in each race, the outcome for each participant is:

      +6 = no transaction, win (with own ticket)
      +5 = buy ticket, win (with own or bought ticket)
      +1 = sell ticket
      0 = no transaction, don’t win
      −1 = buy ticket, don’t win (with either ticket)

      And there are some additional restrictions: a +5 or −1 can only come after a +6, and a +1 cannot come after a +6.

      And we can use these facts to check that there is a sequence of viable outcomes for each participant (without this the following code takes a lot longer to run, but you can change valid() to always return True to try the slower version, but it will take a lot longer).

      This Python program looks for a viable sequence of winners and ticket transactions for each race. It runs in 4.28s.

      Run: [ @replit ]

      from enigma import (irange, subsets, product, diff, first, printf)
      
      # update a dictionary with given deltas
      def update(d, *kvs):
        d = d.copy()
        for (ks, v) in kvs:
          for k in ks:
            d[k] += v
        return d
      
      # find possible length k sequences that give total t
      def complete(k, t, f=0, ss=[]):
        xs = [[0, 1, 6], [-1, 0, 5, 6]][f]
        if k == 0:
          if t == 0:
            yield ss
        elif k == 1:
          if t in xs:
            yield ss + [t]
        else:
          # choose an outcome
          for x in xs:
            yield from complete(k - 1, t - x, f or (x == 6), ss + [x])
      
      # is there a valid completion?
      valid = lambda k, t, f: bool(first(complete(k, t, f)))
      
      # totals we are given (also #3 not >0; #4 >0)
      totals = { 1: 4, 5: 1,  2: -3, 6: -6 }
      
      # solve the puzzle
      # n = remaining races
      # vs = ticket values
      # m = money for each participant
      # ws = winning tickets for each race
      # ps = winning players for each race
      # bs = potential buyers
      def solve(n, vs, m, ws=list(), ps=list(), bs=set()):
        if n == 0:
          # check totals
          if not (m[4] > 0 and not (m[3] > 0) and all(m[k] == v for (k, v) in totals.items())): return
          # check three winning tickets were sold
          if not (sum(x != y for (x, y) in zip(ws, ps)) == 3): return
          # viable solution
          yield (m, ws, ps)
        else:
          # choose some buyers and sellers
          b = len(bs)
          for k in irange(0, min(b, 6 - b)):
            for (bs1, ss1) in product(subsets(bs, size=k), subsets(diff(vs, bs), size=k, select="P")):
              # determine who gets the winnings
              d = dict(zip(ss1, bs1))
              # but 1 did not have any transactions with 2 or 3
              if any(d.get(k, 0) == v for (k, v) in [(1, 2), (1, 3), (2, 1), (3, 1)]): continue
              # perform the transactions
              m1 = update(m, (bs1, -1), (ss1, +1))
              # at this point we can check the targets of any sellers are reachable
              if any(not valid(n - 1, totals[k] - m1[k], k in bs) for k in ss1 if k in totals): continue
      
              # choose a winner for the race
              for w in vs:
                # no consecutive winners
                if ws and ws[-1] == w: continue
                # has the winning ticket been sold?
                p = d.get(w, w)
                if p != w:
                  # winning ticket was sold, but not by 1, 4, 5
                  if w in {1, 4, 5}: continue
                  # and only 3 winning tickets were solve
                  if sum(x != y for (x, y) in zip(ws, ps)) > 2: continue
                # allocate the winnings
                m_ = update(m1, ([p], +6))
                bs_ = bs.union([p])
                # check all (non-seller) targets are reachable
                if any(not valid(n - 1, v - m_[k], k in bs_) for (k, v) in totals.items() if k not in ss1): continue
                # solve for the remaining races
                yield from solve(n - 1, vs, m_, ws + [w], ps + [p], bs_)
      
      # initial values
      vs = list(irange(1, 6)) # available tickets
      m = dict((k, -6) for k in vs) # initial amounts
      for (m, ws, ps) in solve(6, vs, m):
        printf("winning tickets = {ws}; profits = {m}", m=list(m[i] for i in vs))
      

      Solution: The numbers of the winning horses were: 5, 4, 2, 3, 2, 1.


      One scenario is:

      Initially: All on −6
      Race 1: 5 wins; #5 → 0
      Race 2: #5 buys 1; 4 wins; #1 → −5, #4 → 0, #5 → −1
      Race 3: #4 buys 1, #5 buys 2; 2 wins; #1 → −4, #2 → −5, #4 → −1, #5 → +4
      Race 4: #4 buys 3, #5 buys 1; 3 wins; #1 → −3, #3 → −5, #4 → +4, #5 → +3
      Race 5: #4 buys 2, #5 buys 1; 2 wins; #1 → −2, #2 → −4, #4 → +9, #5 → +2
      Race 6: #4 buys 3, #5 buys 2; 1 wins; #1 → +4, #2 → −3, #3 → −4, #4 → +8, #5 → +1

      There are alternative scenarios, in each we have (#3, #4) → (−4, +8) or (−5, +9).

      But in each scenario the winning tickets are (5, 4, 2, 3, 2, 1).

      Like

  • Unknown's avatar

    Jim Randell 9:42 am on 17 April 2022 Permalink | Reply
    Tags:   

    An Easter Brain-Teaser: Indoor sports 

    From The Sunday Times, 6th April 1958 [link]

    We know them familiarly as Jennifer, Judith and Jean, but in age order they are the doctor’s wife, the dentist’s widow and the draper’s daughter, and they form the new Committee of our Indoor Sports and Pastimes Club.

    These are the election results, 200 members having each voted for three [different] names:

    Elected:
    Mrs. Battledaw, 148 votes
    Mrs. Shuttlecox, 137 votes
    Mrs. Bowles, 126 votes

    Not elected:
    Mr. D’Artz, 125 votes
    Mr. Snewker, 64 votes

    Total votes cast:
    600 votes

    Those who voted for both Jean and Judith were the same in number as those who didn’t vote for the doctor’s wife and if even one of Snewker’s supporters had voted for D’Artz he might have gained the third place. Jean is more interested in Mr. Bowles than in her own husband, who is sorry the draper’s daughter didn’t come top.

    Who are Jennifer, Judith and Jean?

    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.

    [teaser-1958-04-06] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:43 am on 17 April 2022 Permalink | Reply

      There are 5 candidates (let’s number them 1, 2, 3, 4, 5), and each member votes for three different candidates, so possible voting patterns are:

      a = (1, 2, 3)
      b = (1, 2, 4)
      c = (1, 2, 5)
      d = (1, 3, 4)
      e = (1, 3, 5)
      f = (1, 4, 5)
      g = (2, 3, 4)
      h = (2, 3, 5)
      i = (2, 4, 5)
      j = (3, 4, 5)

      If we suppose a, …, j are the numbers who voted for their respective patterns, then their sum is 200.

      a + b + c + d + e + f + g + h + i + j = 200

      And from the votes cast (assuming the numbers are the order that the candidates finished in), we have:

      a + b + c + d + e + f = 148
      a + b + c + g + h + i = 137
      a + d + e + g + h + j = 126
      b + d + f + g + i + j = 125
      c + e + f + h + i + j = 64

      We are told “if even one of Snewker’s [5] supporters had voted for D’Artz [4] he might have gained the third place”, which implies that no-one voted for both 4 and 5, so:

      f = 0
      i = 0
      j = 0

      Hence:

      a + b + c + d + e = 148
      a + b + c + g + h = 137
      a + d + e + g + h = 126
      b + d + g = 125
      c + e + h = 64

      We can use one of these equations to select seed values (I used: c + e + h = 64), and then determine the remaining values using the remaining equations, and verify that they are non-negative integers, and that the additional conditions hold.

      The following Python program runs in 241ms.

      Run: [ @replit ]

      from enigma import (Matrix, decompose, as_int, subsets, multiset, printf)
      
      # record solutions, as: ((jen, jud, jea), (doc, den, dra))
      ss = multiset()
      
      # initial values
      f = i = j = 0
      
      # consider values for c, e, h
      for (c, e, h) in decompose(64, 3, increasing=0, sep=0, min_v=0):
      
        # solve for the remaining values
        eqs = [
          # a  b  d  g = k
          ((1, 1, 1, 0), 148 - c - e),
          ((1, 1, 0, 1), 137 - c - h),
          ((1, 0, 1, 1), 126 - e - h),
          ((0, 1, 1, 1), 125),
        ]
      
        # look for non-negative integers
        try:
          (a, b, d, g) = Matrix.linear(eqs, valid=(lambda x: as_int(x, "0+")))
        except ValueError:
          continue
      
        # consider positions by names
        for (jen, jud, jea) in subsets((1, 2, 3), size=len, select="P"):
          # Jean is not Mrs Bowles
          if jea == 3: continue
          # v1 = votes for both Jean and Judith
          v1 = { 1: a + g + h, 2: a + d + e, 3: a + b + c }[jen]
      
          # consider related occupations by position
          for (doc, den, dra) in subsets((1, 2, 3), size=len, select="P"):
            # Jean's husband was not the dentist
            if den == jea: continue
            # Draper's daughter didn't come top
            if dra == 1: continue
            # v2 = didn't vote for doctor's wife
            v2 = { 1: g + h + i + j, 2: d + e + f + j, 3: b + c + f + i }[doc]
            if v1 != v2: continue
            # record this solution
            ss.add(((jen, jud, jea), (doc, den, dra)))
      
      # output solutions
      for (((jen, jud, jea), (doc, den, dra)), n) in ss.most_common():
        name = { 1: "Battledaw", 2: "Shuttlecox", 3: "Bowles" }
        rel = { doc: "doctor", den: "dentist", dra: "draper" }
        printf("[{n} solutions]")
        printf("-> Jennifer = Mrs {n} [{r}]", n=name[jen], r=rel[jen])
        printf("-> Judith = Mrs {n} [{r}]", n=name[jud], r=rel[jud])
        printf("-> Jean = Mrs {n} [{r}]", n=name[jea], r=rel[jea])
        printf()
      

      Solution: Jennifer is Mrs Battledaw, the dentist’s widow (1st); Judith is Mrs Bowles, the draper’s daughter (3rd); Jean is Mrs Shuttlecox, the doctor’s wife (2nd).

      There are many distributions of votes, but they all satisfy:

      a = 11
      b = 10..74
      c = 74 − b
      d = 0..63
      e = 63 − e
      f = 0
      g = 125 − (b + d)
      h = (b + d) − 73
      i = 0
      j = 0

      And lead to the solution given above.

      Like

    • John+Crabtree's avatar

      John+Crabtree 6:44 pm on 18 April 2022 Permalink | Reply

      Nobody votes for both Dr. A and Mr. S, meaning that 11 people vote for all three women,
      It can then be shown that 84 people vote for both Mrs. Ba. and Mrs. Sh, 74 people for both Mrs. Ba. and Mrs. Bo, and 63 people for both Mrs. Sh. and Mrs. Bo.

      The Doctor’s wife must get 137 or 126 votes, and so must the Draper’s daughter, who is not in first place. And so the Dentist’s widow must get 148 votes, and cannot be Jean, who is married.
      Jean cannot be Mrs. Bowles and so cannot get 126 votes, and so must get 137 votes.
      Then Judith must get 126 votes, and so Jean is the Doctor’s wife, Judith is the Draper’s daughter, and Jennifer is the Dentist’s widow.

      Like

  • Unknown's avatar

    Jim Randell 6:22 pm on 14 April 2022 Permalink | Reply
    Tags:   

    Teaser 3108: My Grandfather’s coins 

    From The Sunday Times, 17th April 2022 [link] [link]

    When my grandfather died, he left his fine collection of coins, not more than 2500 of them, to his children, a different number to each of them, and in decreasing amounts according to their ages.

    To the eldest of his children, he left one fifth of the coins, while the youngest inherited just one eleventh of them. Gaby, my mother, and third oldest of the children, received one tenth of the collection. All the other children [each] received a prime number of coins.

    How many coins did my aunt Isabel, second oldest in the family, inherit?

    [teaser3108]

     
    • Jim Randell's avatar

      Jim Randell 10:24 pm on 14 April 2022 Permalink | Reply

      This Python program runs in 59ms.

      Run: [ @replit ]

      from enigma import (primes, mlcm, irange, rev, subsets, printf)
      
      # total must be divisible by m
      m = mlcm(5, 10, 11)
      
      # consider possible t = total
      for t in irange(m, 2500, step=m):
        # calculate a = 1st, c = 3rd, z = last
        a = t // 5
        c = t // 10
        z = t // 11
        # find primes between [z + 1, c - 1]
        ps = rev(primes.between(z + 1, c - 1))
        # choose a prime for b = 2nd
        for b in primes.between(c + 1, a - 1):
          # decompose remaining amount into primes between [z + 1, c - 1]
          r = t - (a + b + c + z)
          for rest in subsets(ps, fn=list):
            if sum(rest) == r:
              # output solution
              printf("t={t} -> {xs}", xs=[a, b, c] + rest + [z])
      

      Solution: Isabel inherited 311 coins.

      There were 2420 coins in total, and the inheritance of the 9 children (eldest to youngest) was:

      484 coins = 1/5 of the total
      311 coins = prime (Isabel)
      242 coins = 1/10 of the total (Gaby)
      241 coins = prime
      239 coins = prime
      233 coins = prime
      227 coins = prime
      223 coins = prime
      220 coins = 1/11 of the total

      Like

  • Unknown's avatar

    Jim Randell 9:19 am on 14 April 2022 Permalink | Reply
    Tags:   

    Teaser 2847: Easter parade 

    From The Sunday Times, 16th April 2017 [link] [link]

    Following the success of last year’s Easter parade our village is going to make it an annual event. To celebrate today’s parade I have taken three numbers and I have consistently replaced digits by letters to give:

    ANNUAL
    EASTER
    PARADE

    In fact the third number is the sum of the other two.

    What is the number PARADE?

    [teaser2847]

     
    • Jim Randell's avatar

      Jim Randell 9:20 am on 14 April 2022 Permalink | Reply

      This puzzle can be solved using the [[ SubstitutedSum ]] solver from the enigma.py library.

      The following command executes in 85ms.

      Run: [ @replit ]

      % python3 -m enigma SubstitutedSum "ANNUAL + EASTER = PARADE"
      (ANNUAL + EASTER = PARADE)
      (300632 + 138719 = 439351) / A=3 D=5 E=1 L=2 N=0 P=4 R=9 S=8 T=7 U=6
      (300732 + 138619 = 439351) / A=3 D=5 E=1 L=2 N=0 P=4 R=9 S=8 T=6 U=7
      

      Solution: PARADE = 439351.

      T and U are interchangeable, taking on the values 6 and 7.

      Like

    • GeoffR's avatar

      GeoffR 11:24 am on 14 April 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:A;  var 0..9:D;  var 1..9:E;  var 0..9:L; var 0..9:N;  
      var 1..9:P;  var 0..9:R;  var 0..9:S;  var 0..9:T; var 0..9:U;
      
      constraint all_different([A, D, E, L, N, P, R, S, T, U]);
      
      function var int: num6 (var int: u, var int: v, var int: w, var int: x, 
      var int: y, var int: z) = 100000*u + 10000*v + 1000*w + 100*x + 10*y + z;
      
      var 100000..999999: ANNUAL = num6(A,N,N,U,A,L);
      var 100000..999999: EASTER = num6(E,A,S,T,E,R);
      var 100000..999999: PARADE = num6(P,A,R,A,D,E);
      
      constraint ANNUAL + EASTER == PARADE;
      
      solve satisfy;
      output ["PARADE" ++ " = " ++ show(PARADE) ];
      
      % PARADE = 439351
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:44 am on 12 April 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 687: Fair deal 

    From The Sunday Times, 15th September 1974 [link]

    From a normal pack of playing cards, Bill, George, Harry and Joe were each dealt an Ace, a King, a Queen, a Jack and a ten.

    Harry’s five cards were in three different suits and consisted of three red and two black cards.

    Joe’s five cards were also in three different suits, his Ace being in the same suit as his Queen, and his King in the same suit as his Jack.

    George held more than one black card.

    Bill’s five cards were all in the same suit.

    Harry held the King of spades, and Joe the ten of diamonds.

    What were the cards in George’s hand?

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

    [teaser687]

     
    • Jim Randell's avatar

      Jim Randell 9:45 am on 12 April 2022 Permalink | Reply

      I used the [[ SubstitutedExpression ]] solver from the enigma.py library to fill out the suits of the cards.

      Here is the run file:

      #! python3 -m enigma -rr
      
      # assign values 0-3 (S=0; C=1; H=2; D=3) to:
      #
      #     A K Q J X
      #  B: a b c d e
      #  G: f g h i j
      #  H: k m n p q
      #  J: r s t u v
      
      SubstitutedExpression
      
      --base=4
      --distinct="afkr,bgms,chnt,dipu,ejqv"
      
      # red cards and black cards
      --code="red = lambda x: x > 1"
      --code="black = lambda x: x < 2"
      
      # H's cards were in 3 different suits
      "len({{k}, {m}, {n}, {p}, {q}}) == 3"
      # 3 red [and 2 black]
      "icount([{k}, {m}, {n}, {p}, {q}], red) == 3"
      
      # J's cards were in 3 different suits
      "len({{r}, {s}, {t}, {u}, {v}}) == 3"
      # suit of A is the same as Q
      "{r} = {t}"
      # suit of K is the same as J
      "{s} = {u}"
      
      # G has more than 1 black card
      "icount([{f}, {g}, {h}, {i}, {j}], black) > 1"
      
      # B's cards are in the same suit
      "{a} = {b}" "{b} = {c}" "{c} = {d}" "{d} = {e}"
      
      # H held KS (i.e. m = S = 0)
      --assign="m,0"
      
      # J held XD (i.e. v = D = 3)
      --assign="v,3"
      

      And then I wrote a short Python program to output the hands dealt.

      The whole thing runs in 64ms.

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, join, printf)
      
      # load the run-file
      p = SubstitutedExpression.from_file("teaser687.run")
      
      # solve the puzzle
      for s in p.solve(verbose=0):
        # output hands
        printf("   A K Q J X")
        hand = lambda xs: join(("SCHD"[s[x]] for x in xs), sep=" ")
        printf("B: {h}", h=hand("abcde"))
        printf("G: {h}", h=hand("fghij"))
        printf("H: {h}", h=hand("kmnpq"))
        printf("J: {h}", h=hand("rstuv"))
        printf()
      

      Solution: George held: A♣, K♦, Q♣, J♠, 10♠.

      The complete set of hands is fully determined:

      B: A♥, K♥, Q♥, J♥, 10♥.
      G: A♣, K♦, Q♣, J♠, 10♠.
      H: A♦, K♠, Q♦, J♦, 10♣.
      J: A♠, K♣, Q♠, J♣, 10♦.

      Like

  • Unknown's avatar

    Jim Randell 3:02 pm on 8 April 2022 Permalink | Reply
    Tags:   

    Teaser 3107: Room 101 

    From The Sunday Times, 10th April 2022 [link] [link]

    In the Ministry of Love, O’Brien told Winston Smith to calculate five-digit perfect squares, none sharing a prime factor with 1984.

    “And Winston, no two of them may differ by a multiple of 101 or you know what will happen. Now find as many as you can.”

    When Winston said he had found more than fifty, O’Brien replied:

    “Well done, Winston. Although there were a quadrillion solutions, I know some of your squares.”

    “Does the party control my mind?”

    “We do Winston. Did you choose 10201, for example?”

    “Of course not! It’s the worst square in the world!”

    “How many fingers am I holding up Winston?”

    “Three? Four? I don’t know!”

    “That’s how many of your squares we know.”

    What four squares did O’Brien know?

    [teaser3107]

     
    • Jim Randell's avatar

      Jim Randell 3:19 pm on 8 April 2022 Permalink | Reply

      This Python program collects groups of 5-digit squares (excluding 10201) by their residue modulo 101. Any two squares with the same residue will differ by a multiple of 101.

      So Smith can choose at most one square from each group.

      It turns out there are 51 groups, so if Smith has correctly found more than 50 squares he must have chosen 1 from each group. And the only choices we (and O’Brien) can know for sure are from groups that have exactly 1 member.

      The program runs in 55ms.

      Run: [ @replit ]

      from enigma import (powers, is_coprime, group, mod, join, printf)
      
      # group 5-digit squares, except 10201, that are coprime to 1984 by their residue mod 101
      d = group(
        powers(100, 316, 2),
        st=(lambda x: x != 10201 and is_coprime(x, 1984)),
        by=mod(101),
      )
      printf("[{n} groups]", n=len(d.keys()))
      
      # look for keys with only one candidate
      for k in sorted(d.keys()):
        vs = d[k]
        if len(vs) == 1:
          printf("{k} -> {vs}", vs=join(vs, sep=", "))
      

      Solution: The four squares O’Brien knew are: 15625, 34969, 62001, 91809.

      Like

    • GeoffR's avatar

      GeoffR 8:13 am on 14 April 2022 Permalink | Reply

      
      from enigma import is_coprime
      from collections import defaultdict
      SQList = defaultdict(list)
      
      # 5-digit squares must be co-prime with 1984
      # omit 101 as 101 ^ 2 = 10201, which is not needed
      squares = [x * x for x in range(102, 317) if is_coprime(x * x, 1984)]
      
      # index squares on remainder after dividing square by 101.
      for n in squares:
          SQList[n % 101].append(n)
      
      print("O'Brien knew the following squares:" )
      for k, v in SQList.items():
          if len(v) == 1:
              print( v[0], end = ' ')
      
      

      Like

    • Hugh+Casement's avatar

      Hugh+Casement 10:58 am on 17 April 2022 Permalink | Reply

      I think it can be done without a program.
      1984 = 2^6 × 31, so all even integers are excluded, as are the squares of multiples of 31, which are 155, 217, and 279 in the range under consideration (101 to 315). We are also told that 101² is absent.

      If i² – j² is a multiple of 101, then so is either (i – j) or (i + j).
      That means that nearly every odd integer in the range is paired with at least one other. The exceptions are 125 = 404 – 279, 187 = 404 – 217, 249 = 404 – 155, and 303 = 404 – 101 = 101 + 202.

      And there certainly weren’t a quadrillion: in English we would say “Though there be a quadrillion …”.

      Like

  • Unknown's avatar

    Jim Randell 9:30 am on 7 April 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 910: The Teaser Club 

    From The Sunday Times, 30th December 1979 [link]

    “The Teaser Club was formed in the summer of 1969”, the secretary of the Chub told me. “We held our Inaugural meeting then. A president and a secretary were appointed, and a constitution was drawn up. One rule of the Club is that each member must send a Christmas card to every other member. Each card, as you may guess, carries a Teaser as well as Christmas greetings”.

    The membership increased every year”, he continued, “indeed, in 1970, we sent 330 more cards than we did in 1969”.

    “How many were sent in 1978?”, I asked.

    “We sent 330 more than in 1977”, the secretary replied with a smile.

    “And how many did you send in 1977?”, I persisted.

    “By a strange coincidence”, he replied, “it was 330 more than in 1976”.

    “Thank you for your information”, I said. “I see now why you call it the Teaser Club”.

    How many Christmas cards were sent in 1975?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994). The puzzle text above is taken from the book.

    [teaser910]

     
    • Jim Randell's avatar

      Jim Randell 9:31 am on 7 April 2022 Permalink | Reply

      If there are n members of the club, then the number of cards send is s(n) = n(n − 1).

      This Python program looks for pairs of possible s(n) values that differ by 330. And then looks for two pairs which chain together (for the 1976, 1977, 1978 membership.

      We then look for possible lower values (the membership must grow by at least 1 member a year) for the 1969, 1970 membership.

      Together these give us upper and lower bounds on the 1975 membership, from which we can determine the number of cards sent.

      It runs in 55ms.

      Run: [ @replit ]

      from enigma import (irange, inf, subsets, printf)
      
      # record s[n] = number of cards sent by n members
      s = [0]
      for n in irange(1, inf):
        t = n * (n - 1)
        if t - s[-1] > 330: break
        s.append(t)
      
      # find terms that differ by 330, record by index
      d = dict()
      for ((i, x), (j, y)) in subsets(enumerate(s), size=2):
        if y - x == 330:
          d[i] = j
          printf("[s[{i}] = {x} -> s[{j}] = {y}]")
      
      # look for indices i, j, k such that d[i] = j and d[j] = k
      for (i, j) in d.items():
        k = d.get(j)
        if k is None: continue
        printf("1976 = {i}; 1977 = {j}; 1978 = {k}")
      
        # and look for 1969, 1970 valules
        for (a, b) in d.items():
          if b + 6 > i: continue
          printf("-> 1969 = {a}; 1970 = {b}")
      
          # calculate 1975 values
          for g in irange(b + 5, i - 1):
            printf("--> 1975 = {g}; cards = {x}", x=s[g])
      

      Solution: 552 Christmas cards were sent in 1975.

      Like

  • Unknown's avatar

    Jim Randell 8:55 am on 5 April 2022 Permalink | Reply
    Tags:   

    Teaser 2866: On the rack 

    From The Sunday Times, 27th August 2017 [link] [link]

    I have wine in a rectangular rack consisting of five rows and eight columns. The bottles are made up of five different vintages with each vintage (four reds and four whites) forming a rectangle in the rack. Within each vintage-rectangle the places occupied by the whites form a connected set, and the same is true of the reds. No two vintage-rectangles have the same red/white pattern, no matter what angle they are viewed at from the front.

    In the rack the first row is a mixture of reds and whites, with a red on the extreme right. Another row contains just reds, and the first column contains more reds than than any other column.

    What (in order) are the colours of the wines in the second row?

    [teaser2866]

     
    • Jim Randell's avatar

      Jim Randell 8:56 am on 5 April 2022 Permalink | Reply

      There are 5 possible rectangular patterns of 8 bottles:

      And 5×8 = 40 so each pattern must appear exactly once. All we need to do is work out how to fit them into an 8×5 grid meeting the specified conditions.

      It is fairly straightforward to sketch out a solution manually on the back of an envelope in a few minutes. But making a programmed solution is a bit more involved, and my first program only just managed a run time less than a second.

      However, we can restructure the program using a few techniques to make it more efficient:

      Firstly we take the pieces and precompute the data for the 4 possible rotations. The rotations are then stored with the values furthest from the origin listed first, so when we try to fit them into the grid, pieces that fall outside the bounds of the grid will fail sooner.

      The grid is stored as a linear array to make copying it easy, and the values are stored in columns in the grid, so we fill out the columns in order as we go along.

      As the pieces are placed we check that (row 0, col 7) must be red, and as the columns are filled out we ensure that column 0 (which is filled out first) has more reds than any of the other columns.

      So when the grid is complete we only need to check that row 0 also has whites and one of the other rows is entirely red.

      Together, these measures bring the run time down to just 64ms. (Internal runtime is 12ms).

      Run: [ @replit ]

      from enigma import (irange, unpack, chunk, join, printf)
      
      # red and white
      (R, W) = (0, 1)
      
      # pieces: [ (r, c, v) ... ]
      pieces = [
        # R R R R W W W W
        [ (0, 0, R), (0, 1, R), (0, 2, R), (0, 3, R), (0, 4, W), (0, 5, W), (0, 6, W), (0, 7, W) ],
        # R R R R
        # W W W W
        [ (0, 0, R), (0, 1, R), (0, 2, R), (0, 3, R), (1, 0, W), (1, 1, W), (1, 2, W), (1, 3, W) ],
        # R R R W
        # R W W W
        [ (0, 0, R), (0, 1, R), (0, 2, R), (0, 3, W), (1, 0, R), (1, 1, W), (1, 2, W), (1, 3, W) ],
        # R W W W
        # R R R W
        [ (0, 0, R), (0, 1, W), (0, 2, W), (0, 3, W), (1, 0, R), (1, 1, R), (1, 2, R), (1, 3, W) ],
        # R R W W
        # R R W W
        [ (0, 0, R), (0, 1, R), (0, 2, W), (0, 3, W), (1, 0, R), (1, 1, R), (1, 2, W), (1, 3, W) ],
      ]
      
      # rotate a piece 90 degrees
      def rotate(p):
        p_ = list((c, -r, v) for (r, c, v) in p)
        r0 = min(r for (r, _, _) in p_)
        c0 = min(c for (_, c, _) in p_)
        return list((r - r0, c - c0, v) for (r, c, v) in p_)
      
      # transform the pieces array into [ <rot 0>, <rot 90>, <rot 180>, <rot 270> ]
      for i in irange(len(pieces)):
        p = pieces[i]
        ps = list()
        for n in irange(0, 3):
          ps.append(sorted(p, key=unpack(lambda r, c, v: r + c), reverse=1))
          if n == 3: break
          p = rotate(p)
        pieces[i] = ps
          
      # place p at position k in g
      def place(g, p, k):
        g = list(g)
        (c0, r0) = divmod(k, 5)
        for (r, c, v) in p:
          r += r0
          if r > 4: return
          c += c0
          if c > 7: return
          # only place R at (r=0, c=7)
          if r == 0 and c == 7 and v != R: return
          i = 5 * c + r
          if g[i] is not None: return
          g[i] = v
        return g
      
      # fit the pieces into the grid
      # return the rows
      def fit(ps, g):
        # are we done?
        if not ps:
          # create the cols and rows
          cs = list(chunk(g, 5))
          rs = list(zip(*cs))
          # row 0 has whites in (as well as red at col 7)
          if W not in rs[0]: return
          # but one of the other rows is all red
          if all(W in r for r in rs): return
          # return the rows
          yield rs
        else:
          # find an empty square
          k = g.index(None)
          # try to place a piece there (in some orientation)
          for (i, rs) in enumerate(ps):
            for p in rs:
              g_ = place(g, p, k)
              if g_ is None: continue
              # make the columns
              cs = list(chunk(g_, 5))
              if None not in cs[0]:
                # count the reds in column 0
                n = cs[0].count(R)
                # all the other columns must have fewer reds
                if not all(j == 0 or col.count(R) < n for (j, col) in enumerate(cs)): continue
              # place the remaining pieces
              yield from fit(ps[:i] + ps[i + 1:], g_)
                
      # initial grid
      g0 = [None] * 40
      
      # fit the pieces into the grid
      for rs in fit(pieces, g0):
        # output rows
        for r in rs:
          printf("[ {r} ]", r=join(('RW'[v] for v in r), sep=' '))
        printf()
      

      Solution: The wines in the second row down are: (red, white, white, white, white, white, white, red).

      There is only one possible layout, as shown:

      Like

  • Unknown's avatar

    Jim Randell 4:15 pm on 1 April 2022 Permalink | Reply
    Tags:   

    Teaser 3106: Underground umbra conundrum 

    From The Sunday Times, 3rd April 2022 [link] [link]

    An underground car park has rectangular walls, each covered with 300 abutting square tiles arranged in three repeats of red, orange, yellow, green and blue columns twenty tiles high. A sharp tapering shadow from a duct played across one wall from top to bottom (with straight lines similar to that shown).

    Each shadow edge crossed every colour and, curiously, hit just two points where four tile corners met. The upper and lower pairs of these were on levels the same distance up and down from the mid-level line. I also noticed that no column was completely free of shadow, but just one column was completely in shadow.

    Give the shadow’s area relative to the wall area as a fraction in simplest form.

    [teaser3106]

     
    • Jim Randell's avatar

      Jim Randell 11:01 pm on 1 April 2022 Permalink | Reply

      For the “shadow edge” (or terminator) to “cross” a coloured column I required that a non-zero length of it intersected with the column. I assumed that the points at which the terminator hits the intersections of the four tile corners are all the same vertical distance from the horizontal centre line of the wall. And that the shadow tapers towards the top and slants towards the right (as in the diagram).

      After drawing a diagram on graph paper to solve the puzzle, I wrote the following Python program to check the solution was unique. And I found there were in fact two ways to reach the answer.

      The Python program considers the possible heights at which the terminator line hits the intersections of four tile corners, and then possible horizontal positions. The areas of the resulting lit regions are grouped by the width in tiles of the bounding box of the area.

      We then look for two lit areas that have exactly 1 column between them (that is in shadow), and the total area of shadow is calculated.

      It runs in 56ms. (Internal runtime is 6.1ms).

      Run: [ @replit ]

      from enigma import (defaultdict, irange, subsets, is_coprime, Polynomial, intc, product, printf)
      
      # consider possible levels for the corner intersection (from the floor/ceiling)
      for y in irange(1, 9):
      
        # record possible lit areas by bounding width
        d = defaultdict(set)
        
        # possible x co-ordinates for upper/lower intersections
        for (x, x_) in subsets(irange(1, 8), size=2):
          if not is_coprime(20 - 2 * y, x_ - x): continue
          
          # interpolate the terminator line
          f = Polynomial.interpolate([(y, x), (20 - y, x_)])
          # calculate intercepts of y=0, y=20 (floor/ceiling)
          (x0, x20) = map(f, [0, 20])
          # must start in first column, and include at least 5 columns
          if x0 < 0 or (not x0 < 1) or (not x20 > 4): continue
          
          # width of bounding region
          w = intc(x20)
          if w < 5 or w > 9: continue
          # lit area
          L = 10 * (x0 + x20)
          d[w].add(L)
          
          printf("[y={y}: x=({x}, {x_}) -> ({x0}, {x20}) w={w} L={L}]")
      
        # look for 2 bounding widths that sum to 14
        for w1 in sorted(d.keys()):
          w2 = 14 - w1
          if not w1 < w2: break
      
          # calculate area in shadow
          for (L1, L2) in product(d[w1], d[w2]):
            T = 300
            S = T - L1 - L2
            q = S / T
            printf("q = {S}/{T} = {q} [w1={w1} L1={L1}; w2={w2} L2={L2}]")
      

      Solution: The shadow covers 8/15 of the wall.


      We note that in the determination of any lit region we have:

      x0 + x20 = w

      And the area of the lit region is therefore:

      L = 20 (x0 + x20) / 2
      L = 10w

      And for a solution we require widths that sum to 14 (to leave exactly one column completely in shadow).

      Hence the area in shadow is:

      S = 300 − L1 − L2
      S = 300 − 10w1 − 10w2
      S = 300 − 10(w1 + w2)
      S = 300 − 140
      S = 160

      So we can see, if there are any solutions, the answer will be:

      q = 160 / 300
      q = 8 / 15

      And the program above shows there are two scenarios which lead to a solution (shown below):

      In the second scenario the leftmost red column is only just in shadow (at its bottom right hand corner), and similarly the second green column is only just lit (at its top left hand corner), so the first scenario is probably a better solution (although the second scenario looks more similar to the diagram in the puzzle statement).


      Another way of looking at the puzzle is that there is exactly one column entirely in shadow = 1/15 of the wall.

      And the remaining area of the wall (totalling 14/15) consists of 2 rectangles, each of which is split exactly half in shadow.

      The total area in shadow is therefore: 1/15 + 7/15 = 8/15 of the wall.

      All that remains is to show that there is a construction that meets the remaining conditions (and, as noted above, there are 2).

      Like

  • Unknown's avatar

    Jim Randell 8:40 am on 31 March 2022 Permalink | Reply
    Tags:   

    Teaser 2867: Clumsy Meg 

    From The Sunday Times, 3rd September 2017 [link] [link]

    I arranged cards labelled ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE and TEN equally-spaced around the perimeter of a circular table. The arrangement was such that any two adjacent cards had exactly one letter in common.

    That evening Meg entered the room and accidentally knocked two adjacent cards onto the floor. In her haste to put things right, she inadvertently put the two cards back the wrong way round. Surprisingly, the one-letter property still applied.

    What were the two numbers directly opposite the two that she knocked on the floor?

    [teaser2867]

     
    • Jim Randell's avatar

      Jim Randell 8:41 am on 31 March 2022 Permalink | Reply

      I took “exactly one letter in common” to mean that there was exactly one letter of the alphabet that appeared in both words, but the letter may be repeated in one (or both) of the words.

      The following Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (subsets, intersect, ordered, update, tuples, join, printf)
      
      # available words
      words = ('ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'TEN')
      
      # find words that share exactly one letter
      S1 = set(ordered(*ws) for ws in subsets(words, size=2) if len(intersect(ws)) == 1)
      
      # check two words share exactly one letter
      check = lambda *ws: ordered(*ws) in S1
      
      # generate a ring of words such that adjacent items share exactly one letter
      def generate(s, ws):
        sz = s[-1]
        # are we done?
        if not ws:
          # check closure and eliminate duplicates
          if check(sz, s[0]) and s[1] < sz:
            yield s
        else:
          # extend the ring
          for (i, w) in enumerate(ws):
            if check(sz, w):
              yield from generate(s + [w], ws[:i] + ws[i + 1:])
      
      # solve the puzzle starting with the first word
      for s in generate([words[0]], words[1:]):
        # find two adjacent words that can be swapped
        for (i, w) in enumerate(s):
          if i == len(s) - 1: break
          s2 = update(s, (i + 1, i), s[i:i + 2])
          if all(check(a, b) for (a, b) in tuples(s2, 2, circular=1)):
            # the numbers opposite the swapped pair
            r = (s2[(i + 5) % 10], s2[(i + 6) % 10])
            fmt = lambda x: join(x, sep=" ", enc="()")
            printf("{r}; {s} -> {s2}", r=fmt(r), s=fmt(s), s2=fmt(s2))
      

      Solution: The two numbers are: SEVEN and EIGHT.

      One scenario is that the cards were initially laid out:

      ONE (O) FOUR (F) FIVE (E) TEN (T) TWO (T) EIGHT (E) SEVEN (S) SIX (I) NINE (E) THREE (E) [ONE]

      And the ONE and FOUR cards were swapped over to give

      FOUR (O) ONE (E) FIVE (E) TEN (T) TWO (T) EIGHT (E) SEVEN (S) SIX (I) NINE (E) THREE (R) [FOUR]

      Or we started with the second set and swapped ONE and FOUR to get the first set.

      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