Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 11:18 am on 11 June 2020 Permalink | Reply
    Tags: by: Ann Kindersley   

    Brain-Teaser 522: [Palindromic phone numbers] 

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

    The other day I noticed some strange coincidences about the telephone numbers of my three friends.

    Each number reads the same from left to right and vice versa. The first and third digit formed the owner’s age, as did the sum of all the digits of his number and, furthermore, his number was divisible by his age.

    What were my friends’ ages?

    This puzzle was originally published with no title.

    [teaser522]

     
    • Jim Randell's avatar

      Jim Randell 11:20 am on 11 June 2020 Permalink | Reply

      Assuming the ages cannot have a leading zero (which means the phone numbers can’t either) gives us a unique solution to the puzzle.

      This Python program uses the [[ SubstitutedExpression() ]] solver from the enigma.py library to try 3-, 4- and 5-digit numbers satisfying the required conditions. It runs in 54ms.

      from enigma import (sprintf as f, join, SubstitutedExpression, subsets, all_different, printf)
      
      # alphametic forms for possible palindromic phone numbers
      numbers = [ 'ABA', 'ABBA', 'ABCBA' ]
      
      # collect possible (number, age) solutions
      ss = list()
      
      # consider possible phone numbers
      for n in numbers:
        # age is 1st and 3rd digit
        a = n[0] + n[2]
        
        exprs = [
          # sum of digits is the same as age
          f("{s} = {a}", s=join(n, sep=" + ")),
          # the number itself is divisible by the age
          f("{n} % {a} = 0"),
        ]
      
        # solve the alphametic expressions
        p = SubstitutedExpression(exprs, distinct="", answer=f("({n}, {a})"))
        ss.extend(p.answers(verbose=0))
      
      # choose three solutions with different ages
      for (A, B, C) in subsets(ss, size=3):
        if not all_different(A[1], B[1], C[1]): continue
        printf("A={A} B={B} C={C}")
      

      Solution: The ages are: 21, 23, 27.

      The corresponding phone numbers are: 28182, 28382, 28782.

      These are the only three solutions for 5-digit phone numbers, and there are no solutions for 3- or 4-digit phone numbers.

      Like

    • GeoffR's avatar

      GeoffR 4:48 pm on 16 July 2021 Permalink | Reply

      % A Solution in MiniZinc 
      % assumes 5-digit tel. no. is ABCBA
      include "globals.mzn";
      
      var 1..9:A; var 0..9:B; var 0..9:C; 
      
      var 10..100: age1;
      
      var 10000..99999: ABCBA = 10001*A + 1010*B + 100*C; 
      
      constraint age1 == 10*A + C /\ age1 == 2*A + 2*B + C
      /\ ABCBA mod age1 == 0;
      
      solve satisfy;
      
      output ["Age = " ++ show(age1) ++ ", Tel. No. = " ++ show(ABCBA) ];
      
      % Age = 21, Tel. No. = 28182
      % ----------
      % Age = 23, Tel. No. = 28382
      % ----------
      % Age = 27, Tel. No. = 28782
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:33 am on 9 June 2020 Permalink | Reply
    Tags: by: Ernest J Luery,   

    Brain-Teaser 521: [Names and occupations] 

    From The Sunday Times, 6th June 1971 [link]

    There were six. Messrs. Butcher, Carpenter, Draper, Farmer, Grocer and Miller,  who shared the fire-watching on Friday nights — three this week, three the next. By occupation they were (not necessarily respectively) a butcher, a carpenter, a draper, a farmer, a grocer and a miller.

    Incidents were few and far between, until that Saturday morning when they found the “log” book signed by the Acting Deputy Assistant something or other, as follows: “All three present and fast asleep”.

    Something had to be done about it: they decided to watch, to play whist, and to keep awake in the future. It was arranged thus: four did duty this week, next week two stood down and two others came in, and so on. Each did two turns in three weeks.

    On the first Friday the carpenter, the draper, the farmer and the miller watched. Next week Mr Carpenter, Mr Draper, Mr Farmer and Mr Grocer played. On the third occasion Mr Butcher played against Mr Grocer, Mr Farmer against the butcher, and the miller against the draper. Each night the four cut for partners and kept them till morning.

    If Mr Carpenter’s occupation is the same as the name of the one whose occupation is the same as the name of the one whose occupation is the same as the one whose occupation is the same as the name of the miller:

    What is Mr Miller’s occupation?

    As presented this puzzle has no solutions. In the comments I give a revised version that does have a unique answer.

    This puzzle was originally published with no title.

    [teaser521]

     
    • Jim Randell's avatar

      Jim Randell 9:33 am on 9 June 2020 Permalink | Reply

      I thought the tricky bit in this puzzle would be detangling the long obfuscated condition at the end (and decide if there was a “the name of” missing from it). But it turns out we can show that there are no solutions before we get that far, so the puzzle is flawed.

      In week 3 we have the following pairs (Mr Butcher + Mr Grocer), (Mr Farmer + the butcher), (the miller + the draper).

      But there are only four people (i.e. two pairs), so one of the pairs is repeated. The middle one doesn’t overlap with either of the outer two, so the outer two must refer to the same pair. i.e. (Mr Butcher + Mr Grocer) = (the miller + the draper).

      In week 2 we had Mr Carpenter, Mr Draper, Mr Farmer and Mr Grocer. And Mr Farmer and Mr Grocer stayed on for week 3, which means they can’t have done week 1.

      The jobs missing from week 1 are the butcher and the grocer, so: (Mr Farmer + Mr Grocer) = (the butcher + the grocer).

      But Mr Grocer cannot be one of (the miller + the draper) and also one of (the butcher + the grocer).

      So the described situation is not possible.


      However if we change the names in the second week to match the jobs from the first week (which I think makes for a more pleasing puzzle), and also insert the missing “the name of” into the final obfuscated condition we get the following revised puzzle:

      On the first Friday the carpenter, the draper, the farmer and the miller watched. Next week Mr Carpenter, Mr Draper, Mr Farmer and Mr Miller played. On the third occasion Mr Butcher played against Mr Grocer, Mr Farmer against the butcher, and the miller against the draper. Each night the four cut for partners and kept them till morning.

      If Mr Carpenter’s occupation is the same as the name of the one whose occupation is the same as the name of the one whose occupation is the same as the name of the one whose occupation is the same as the name of the miller:

      What is Mr Miller’s occupation?

      Then we get a puzzle that does have solutions (although not the same as the published solution).

      The following Python program runs in 54ms.

      Run: [ @repl.it ]

      from enigma import (irange, subsets, multiset, map2str, printf)
      
      # labels for the names and jobs
      labels = (B, C, D, F, G, M) = irange(0, 5)
      
      # check the schedule for the 3 weeks
      def check(w1, w2, w3):
        m = multiset()
        for w in (w1, w2, w3):
          w = set(w)
          # 4 different people each week
          if len(w) != 4: return False
          m.update_from_seq(w)
        # each person does 2 duties over the 3 weeks
        return all(v == 2 for v in m.values())
      
      # map: job -> name
      for n in subsets(labels, size=len, select="P"):
      
        # Week 1 = carpenter, draper, farmer, miller
        w1 = [n[C], n[D], n[F], n[M]]
        # Week 2 = Carpenter, Draper, Farmer, Miller
        w2 = [C, D, F, M] # NOT: [C, D, F, G]
        # Week 3 = Butcher + Grocer, Farmer + butcher, miller + draper
        # so: Butcher + Grocer = miller + draper
        if not(set([B, G]) == set([n[M], n[D]])): continue
        w3 = [B, G, F, n[B]]
      
        if not check(w1, w2, w3): continue
      
        # "Mr Carpenter's occupation is the same as the name of the one
        # whose occupation is the same as the name of the one whose
        # occupation is the same as the [name of] one whose occupation is
        # the same as the name of the miller"
        if not(n[n[n[n[n[M]]]]] == C): continue
      
        # output the map
        names = ("Butcher", "Carpenter", "Draper", "Farmer", "Grocer", "Miller")
        jobs = list(x.lower() for x in names)
        printf("{s}", s=map2str(((names[n], j) for (n, j) in zip(n, jobs)), sep="; ", enc=""))
      

      Solution: Mr Miller is the carpenter.

      There are three ways to assign the jobs to the names:

      Butcher=draper; Carpenter=butcher; Draper=farmer; Farmer=grocer; Grocer=miller; Miller=carpenter
      Butcher=miller; Carpenter=butcher; Draper=farmer; Farmer=grocer; Grocer=draper; Miller=carpenter
      Butcher=miller; Carpenter=farmer; Draper=butcher; Farmer=grocer; Grocer=draper; Miller=carpenter

      Like

  • Unknown's avatar

    Jim Randell 5:14 pm on 5 June 2020 Permalink | Reply
    Tags:   

    Teaser 3011: Optical illusion 

    From The Sunday Times, 7th June 2020 [link] [link]

    George and Martha are studying optics. If you place an object a specific distance from a lens, an image will appear at a distance from that lens according the following formula:

    The reciprocal of the object distance plus the reciprocal of the image distance is equal to the reciprocal of the focal length of the lens.

    The object distance was a two-digit whole number of cm (ab). The image distance and the focal length of the lens were also two-digit whole numbers (cd and ef respectively). The six digits were all different and non-zero. Also, the object distance and the focal length were of the same parity and b was an exact multiple of d. Martha pointed out that the sum of those three two-digit numbers was a prime number.

    What was that prime number?

    [teaser3011]

     
    • Jim Randell's avatar

      Jim Randell 5:27 pm on 5 June 2020 Permalink | Reply

      I used uppercase letters for the alphametic symbols, as that is more usual.

      We can use the [[ SubstitutedExpression() ]] solver from the enigma.py library, to solve the relevant alphametic expressions. The following run file executes in 104ms.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      --digits="1-9"
      
      "fraction(1, AB, 1, CD) == (1, EF)"
      "B % 2 == F % 2"
      "div(B, D) > 1"
      "is_prime(AB + CD + EF)"
      
      --answer="AB + CD + EF"
      

      Solution: The sum of the numbers is 211.

      The object distance is 78 cm, the image distance is 91 cm and the focal length is 42 cm.

      (1 / 78) + (1 / 91) = (1 / 42)

      The optical constraint and multiple constraint (lines 12 and 14) are sufficient to arrive at a unique answer to the puzzle.


      However, if we remove the constraint that the symbols stand for different digits there is a further solution:

      (1 / 28) + (1 / 21) = (1 / 12)

      In this case the numbers sum to 61.

      Like

    • GeoffR's avatar

      GeoffR 7:33 pm on 5 June 2020 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9: a; var 1..9: b; var 1..9: c; 
      var 1..9: d; var 1..9: e; var 1..9: f;
      
      constraint all_different ([a, b, c, d, e, f]);
      
      var 10..99: ab = 10*a + b;  % object distance
      var 10..99: cd = 10*c + d;  % image distance
      var 10..99: ef = 10*e + f;  % focal length
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x)))))
       ((i < x) -> (x mod i > 0));
       
      % optical formula is  1/ab + 1/cd = 1/ef, so... 
      constraint (cd * ef) + (ab * ef) == (ab * cd);
      
      % parity is same for numbers ab and ef
      constraint ab mod 2 == ef mod 2;
      
      % b was an exact multiple of d
      constraint b div d > 1 /\ b mod d == 0;
      
      % sum of three two-digit numbers was a prime number
      constraint is_prime(ab + cd + ef);
      
      solve satisfy;
      
      output ["Prime number is " ++ show(ab + cd + ef) ++
      "\nab = " ++ show(ab) ++ ", cd = " ++ show(cd) ++ ", ef = " ++ show(ef)];
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 12:06 pm on 4 June 2020 Permalink | Reply
    Tags:   

    Teaser 2860: Cricketing greats 

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

    On each of the next seven evenings a different media pundit will advocate the merits of two cricketers. The pundits are Agnew, Blofeld, Dagnall, Mann, Mitchell, Norcross and Smith. The fourteen cricketers to be discussed are Ali, Anderson, Ball, Ballance, Broad, Carberry, Compton, Hales, Kerrigan, Patel, Stokes, Tredwell, Trescothick and Woakes. Each evening, looking at the names of the pundit and the two cricketers, then for any two out of the three names there are just two letters of the alphabet that occur (once or more) in both.

    (a) Which cricketers will Dagnall advocate?
    (b) Which cricketers will Norcross advocate?

    [teaser2860]

     
    • Jim Randell's avatar

      Jim Randell 12:07 pm on 4 June 2020 Permalink | Reply

      See: Teaser 2959.

      I incorporated the [[ gangs() ]] function into the [[ grouping ]] code that is available in the enigma.py library, and we can use it to solve this puzzle.

      The following Python code runs in 53ms.

      Run: [ @repl.it ]

      from enigma import (grouping, diff)
      
      # gang leaders (in solving order)
      pundits = ( 'Dagnall', 'Norcross', 'Agnew', 'Blofeld', 'Mann', 'Mitchell', 'Smith' )
      
      # gang followers
      cricketers = (
        'Ali', 'Anderson', 'Ball', 'Ballance', 'Broad', 'Carberry', 'Compton',
        'Hales', 'Kerrigan', 'Patel', 'Stokes', 'Tredwell', 'Trescothick', 'Woakes'
      )
      
      # selection function
      fn = grouping.share_letters(2)
      
      # find possible 2-gangs for Dagnall and Norcross
      for (g1, g2) in grouping.gangs(2, pundits[0:2], cricketers, fn):
        # check the remaining gangs can be made
        for gs in grouping.gangs(2, pundits[2:], diff(cricketers, g1 + g2), fn):
          grouping.output_gangs(pundits, [g1, g2] + gs)
          # we only need one example
          break
      

      Solution: (a) Dagnall advocates Ball and Broad; (b) Norcross advocates Ballance and Woakes.

      The program checks that the remaining gangs can be formed, and it turns out that there is only one way to do this:

      Dagnall: Ball, Broad
      Norcross: Ballance, Woakes
      Agnew: Carberry, Tredwell
      Blofeld: Patel, Trescothick
      Mann: Anderson, Compton
      Mitchell: Ali, Kerrigan
      Smith: Hales, Stokes

      So we could just use the following even shorter program to find all possible groupings:

      from enigma import grouping
      
      # gang leaders
      pundits = ( 'Dagnall', 'Norcross', 'Agnew', 'Blofeld', 'Mann', 'Mitchell', 'Smith' )
      
      # gang followers
      cricketers = (
        'Ali', 'Anderson', 'Ball', 'Ballance', 'Broad', 'Carberry', 'Compton',
        'Hales', 'Kerrigan', 'Patel', 'Stokes', 'Tredwell', 'Trescothick', 'Woakes'
      )
      
      # find 2-gangs
      for gs in grouping.gangs(2, pundits, cricketers, grouping.share_letters(2)):
        grouping.output_gangs(pundits, gs)
      

      Like

  • Unknown's avatar

    Jim Randell 8:11 am on 2 June 2020 Permalink | Reply
    Tags: , ,   

    Teaser 2566: A fulfilling strategy 

    From The Sunday Times, 27th November 2011 [link] [link]

    I drove down a road with a number of petrol stations whose locations I saw on my map. I decided to check the price at the first station then fill up when I found one offering a lower price (or, failing that, the last one).

    When I got home I noticed that I could arrange the prices (in pence per litre) into an ascending sequence of consecutive whole numbers of pence, plus 0.9p (i.e. 130.9p, 131.9p, 132.2p, etc). I also worked out the average price that I would expect to pay using this strategy, if I were to encounter this set of prices in an unknown order, and I was surprised to find that this value turned out to be a whole number of pence per litre.

    How many petrol stations were there?

    When the puzzle was originally published in The Sunday Times it had been edited into a form that meant there were no solutions. Here I have rephrased the middle paragraph so that it works as the setter originally intended.

    [teaser2566]

     
    • Jim Randell's avatar

      Jim Randell 8:12 am on 2 June 2020 Permalink | Reply

      (See also: Teaser 2988).

      Here is a program that calculates the result directly. It runs in 50ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, inf, factorial, div, printf)
      
      # strategy, take the first amount lower than the first n items
      def strategy(s, n):
        t = min(s[:n])
        for x in s[n:]:
          if x < t: break
        return x
      
      # play the game with k items
      def play(k):
        # collect total amount
        t = 0
        # consider possible orderings of the items
        ps = list(1299 + 10 * p for p in irange(1, k))
        for s in subsets(ps, size=k, select="P"):
          t += strategy(s, 1)
        return t
      
      # consider numbers of items
      for k in irange(2, inf):
        t = play(k)
        # calculate the average expected price (in 10th pence)
        d = factorial(k)
        p = div(t, d)
        if p is not None and p % 10 == 0:
          printf("k={k} -> p={p}")
          break
      

      Solution: There were 5 petrol stations.

      And the expected average fuel price was 132.0 pence.

      It doesn’t actually matter what the prices actually are, all the petrol stations could adjust their prices by the same whole number of pence, and the expected average price would be adjusted by the same amount.


      Analytically:

      As determined in Teaser 2988 we can use the formula:

      S(n, k) = ((2n + 1)k − n(n + 1)) (k + 1) / 2(n + 1)k

      To determine the mean expected value of choosing from k boxes with values from 1..k, using the strategy of picking the next box that is better than any of the first n boxes (or the last box if there are no boxes better than the first).

      In this case n = 1:

      S(1, k) = (3k − 2) (k + 1) / 4k

      If we award ourselves a prize of value k for selecting the cheapest fuel, and a prize of value 1 for selecting the most expensive fuel, then we have the same situation as in Teaser 2988, and if p is expected average prize value, then the corresponding expected average fuel price is (130.9 + k − p).

      And k is an integer, so in order to make the expected average fuel price a whole number of pence we are looking for expected average prize value that is an integer plus 0.9.

      i.e. when the value of 10 S(1, k) is an integer with a residue of 9 modulo 10.

      10 S(1, k) = (15k + 5) / 2 − (5 / k)

      When k is odd, the first part gives us a whole number, so we would also need the (5 / k) part to give a whole number. i.e. k = 1, 5.

      When k is even, the first part gives us an odd number of halves, so we also need (5 / k) to give an odd number of halves. i.e. k = 2, 10.

      And we are only interested in values of k > 1, so there are just 3 values to check:

      k = 5: 10 S(1, k) = 39
      k = 2: 10 S(1, k) = 15
      k = 10: 10 S(1, k) = 77

      The only value for k that gives a residue of 9 modulo 10 is k = 5.

      And in this case our expected average prize is p = 3.9 so our expected average fuel price is 132.0 pence.

      Like

  • Unknown's avatar

    Jim Randell 9:44 pm on 29 May 2020 Permalink | Reply
    Tags:   

    Teaser 3010: Putting game 

    From The Sunday Times, 31st May 2020 [link] [link]

    A putting game has a board with eight rectangular holes, like the example (not to scale), but with the holes in a different order.

    If you hit your ball (diameter 4cm) through a hole without touching the sides, you score the number of points above that hole. The sum of score and width (in cm) for each hole is 15; there are 2cm gaps between holes.

    I know that if I aim at a point on the board, then the ball’s centre will arrive at the board within 12cm of my point of aim, and is equally likely to arrive at any point in that range. Therefore, I aim at the one point that maximises my long-term average score. This point is the centre of a hole and my average score is a whole number.

    (a) Which hole do I aim at?

    (b) Which two holes are either side of it?

    [teaser3010]

     
    • Jim Randell's avatar

      Jim Randell 9:23 am on 30 May 2020 Permalink | Reply

      The 4cm diameter ball is not allowed to touch the sides of the hole, so we can consider the outside 2cm of each hole to be “out of bounds”. Which is the same as if the ball was a point, the gaps between holes are extended 2cm in each direction (so they become 6cm wide), and each hole is reduced by a corresponding 2cm on either edge.

      To be sure I satisfy all the conditions this Python program constructs all possible orderings for the holes, and then for each ordering looks at possible target positions. It looks for orderings that have a unique maximum targeting point that is at the centre of a hole, and that yields an integer average score per shot. Once an ordering passes these tests we record the hole that the target is in, along with the two holes on either side. This gives a unique target + left/right value (although there are many orderings that contain the three holes appropriately positioned).

      It runs in 1.58s, but I think this could be improved with some additional analysis.

      from enigma import (irange, subsets, multiset, ordered, printf)
      
      # available holes (points)
      holes = [ 1, 2, 3, 4, 5, 6, 7, 8 ]
      
      # layout for a sequence of holes
      # return a list of: (region size (in mm), points scored) pairs
      def layout(ps):
        for (i, p) in enumerate(ps):
          if i > 0: yield (60, 0) # gap between holes
          yield (110 - 10 * p, p) # hole size
      
      # generate intervals ((a, b), p) from a layout
      # where a, b are absolute distances, p is number of points scored
      def intervals(ss):
        a = b = 0
        for (d, p) in ss:
          if b == 0:
            (a, b) = (0, d)
          else:
            (a, b) = (b, b + d)
          yield ((a, b), p)
      
      # analyse the layout (working in 5mm steps)
      # return list of (d, (v, r)) values, where:
      # d = absolute target distance
      # v + r/240 = max average score
      def analyse(ss):
        # determine total length
        t = sum(d for (d, _) in ss)
        rs = list()
        # consider target positions (in 5mm steps)
        for x in irange(120, t - 120, step=5):
          # consider each zone
          r = 0
          for ((a, b), p) in intervals(ss):
            # overlap?
            if b < x - 120: continue
            if a > x + 120: break
            d = min(x + 120, b) - max(x - 120, a)
            r += p * d
          rs.append((x, r))
        # find maximum average value
        m = max(r for (_, r) in rs)
        return list((x, divmod(r, 240)) for (x, r) in rs if r == m)
      
      # collect results
      m = multiset()
      
      # choose an order for the holes
      for ps in subsets(holes, size=len, select="P"):
        # but ignore the order in the diagram
        if ps == (6, 3, 8, 7, 2, 5, 1, 4): continue
        # construct the sequence of (regions, points)
        ss = list(layout(ps))
        # analyse it
        rs = analyse(ss)
        # there should only be one max position
        if len(rs) != 1: continue
        (x, (v, r)) = rs[0]
        # and the average score should be an integer
        if r != 0: continue
        # find the interval x is in
        for ((a, b), p) in intervals(ss):
          # and check it is centred
          if p > 0 and a < x < b and b - x == x - a:
            # find the holes on either side
            i = ps.index(p)
            z = ps[i - 1:i + 2]
            printf("[{ps} -> target = {x} mm, avg pts = {v}; segment = {z}]")
            m.add((p, ordered(ps[i - 1], ps[i + 1])))
      
      # output solution
      for ((x, (l, r)), n) in m.most_common():
        printf("centre = {x}, left/right = {l}/{r} [{n} solutions]")
      

      Solution: (a) You should aim at the centre of the 5 point hole. (b) The 6 point and 8 point holes are either side of the 5 point hole.

      Here is a diagram of the arrangement:

      The “out of bounds” areas are indicated in red, and we score zero points if the centre of the ball lands in these.

      In the 24cm section centred on the 5 point hole we see that there is a 3cm zone where we can score 6 points, a 6cm zone where we can score 5 points, and a 3cm zone where we can score 8 points.

      The average expected score is thus: (6×3 + 5×6 + 8×3) / 24 = 72 / 24 = 3.

      Like

      • Jim Randell's avatar

        Jim Randell 6:28 pm on 30 May 2020 Permalink | Reply

        Here is the program adapted to just consider the centre + left/right values. It finds there is only one segment that must appear in any solution (or its reverse), and this gives the required answer. It runs in 54ms.

        from enigma import (irange, subsets, peek, printf)
        
        # available holes (points)
        holes = [ 1, 2, 3, 4, 5, 6, 7, 8 ]
        
        # layout for a sequence of holes (in mm)
        # return a list of: (region size, points scored) pairs
        def layout(ps):
          for (i, p) in enumerate(ps):
            if i > 0: yield (60, 0) # gap between holes
            yield (110 - 10 * p, p) # hole
        
        # generate intervals ((a, b), p) from a layout
        # where a, b are absolute distances, p is the number of points scored
        def intervals(ss):
          a = b = 0
          for (d, p) in ss:
            if b == 0:
              (a, b) = (0, d)
            else:
              (a, b) = (b, b + d)
            yield ((a, b), p)
        
        # analyse the layout (working in 5mm steps)
        # return list of (d, (v, r)) values, where:
        # d = absolute target distance
        # v + r/240 = max average score
        def analyse(ss):
          # determine total length
          t = sum(d for (d, _) in ss)
          rs = list()
          # consider target positions (in 5mm steps)
          for x in irange(120, t - 120, step=5):
            # consider each zone
            r = 0
            for ((a, b), p) in intervals(ss):
              # overlap?
              if b < x - 120: continue
              if a > x + 120: break
              d = min(x + 120, b) - max(x - 120, a)
              r += p * d
            rs.append((x, r))
          # find maximum average value
          m = max(r for (_, r) in rs)
          return list((x, divmod(r, 240)) for (x, r) in rs if r == m)
        
        # find triples with integer scores max average scores at the centre of the centre hole
        
        # choose the centre hole and left/right holes
        for (X, L, R) in subsets(holes, size=3, select="P"):
          if not (L < R): continue
          # create the layout
          ss = list(layout((L, X, R)))
          # analyse it
          rs = analyse(ss)
          # there should be only one max position
          if len(rs) != 1: continue
          (x, (v, r)) = rs[0]
          # and the max average score should be an integer
          if r != 0: continue
          # and it should be centred on X
          ((a, b), _) = peek(intervals(ss), 2)
          if a < x < b and b - x == x - a:
            printf("segment = ({L}, {X}, {R}) -> target = {x} mm, avg pts = {v}")
        

        I suppose really I should demonstrate that we can construct an ordering containing the required segment that satisfies all the requirements, but as my first program finds lots I don’t think it is necessary.

        Like

    • Robert Brown's avatar

      Robert Brown 10:02 am on 30 May 2020 Permalink | Reply

      If the middle & adjacent slots are too small, the ±12 cm range goes into the next slot, which is undefined. This reduces the solution space and quickly leads to what appears to be a unique result. But if you reduce the smaller adjacent slot by 1 and increase the other one, it still works. This would be valid if the smaller slot was the first one on the board, so a possible duplicate result?

      Like

      • Jim Randell's avatar

        Jim Randell 1:11 pm on 30 May 2020 Permalink | Reply

        @Robert: I don’t think it is possible for the target area to extend beyond the centre hole and the holes on either side. The smallest holes are 7cm and 8cm and the distance from the centre of one to the edge of the other is always greater than 12cm, so I think we only need to consider centre + left/right configurations. (Which I’m looking at to give a more efficient program).

        Like

    • Robert Brown's avatar

      Robert Brown 10:52 am on 30 May 2020 Permalink | Reply

      Further to above. The ‘not to scale’ drawing masks the fact that low value slots are quite wide. I assume there are 8 values as per the drawing, with corresponding slot widths.
      So there is a solution where the mid value is low, and the adjacent values can take one of two different pairs (they have the same sum), and where the total length measured from the centre of the mid slot is > 12 in each case. Definitely two sets of results, doesn’t matter where they are on the board. Maybe I’m missing something?

      Like

    • Robert Brown's avatar

      Robert Brown 12:17 pm on 30 May 2020 Permalink | Reply

      This is not a good place to have a conversation. You have my email address, but I don’t have yours!
      Both of the above sets of results work, but each post has typos which I cannot correct. An in depth exploration reveals 5 different solutions, all with 3 different L M R values between 1 & 8, and with average values of 3, 4 or 5. In each case the minimum distance from the centre of the middle value (where he aims for) is at least 15.5 cm before going into the next (unknown) slot, plenty of room for the 2cm radius ball to have it’s centre up to 12 cm from that centre. So no need for any of the slots to be the first one on the board.

      Like

      • Jim Randell's avatar

        Jim Randell 6:55 pm on 1 June 2020 Permalink | Reply

        @Robert: It’s a limitation of WordPress.com I’m afraid, but if there are any corrections you would like to make, just post a followup comment, and I will use it to correct errors in the original.

        Like

    • Frits's avatar

      Frits 1:14 pm on 15 August 2025 Permalink | Reply

      Based on Jim’s diagram of the arrangement.

      # number of holes, sum score+width, gap, ball diameter, aim deviation  
      N, T, G, B, Z = 8, 15, 2, 4, 12
      
      cm = lambda pts: T - pts - 2 * G
      
      # length of deviation range
      dev_cm = 2 * Z
      rng = set(range(1, N + 1))
      # look for a solution L, M, R
      for M in rng:
        mid_cm = cm(M)
        mid_total = mid_cm * M
        # remaining cm to use for left and right holes
        rem_cm = dev_cm - mid_cm - 2 * G - 2 * B
        rem_rng = sorted(rng - {M})
        for R in rem_rng:
          right_cm = cm(R)
          # aim as far right as possible in order to maximise the score
          right = min(rem_cm, right_cm)
          # but aim must also be at the centre of a hole
          if 2 * right != rem_cm: continue
          mid_right_total = mid_total + right * R
          _, r = divmod(mid_right_total, dev_cm)
          left_total_needed  = dev_cm - r
          
          while True:
            L, r = divmod(left_total_needed, right)
            if L >= R: break
            if not r and L != M:
              print(f"answer: (a) {M}, (b) {L} and {R}")
            left_total_needed += dev_cm
      

      Like

  • Unknown's avatar

    Jim Randell 12:53 pm on 28 May 2020 Permalink | Reply
    Tags:   

    Teaser 2863: Little time 

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

    Do you have a little time to try this Teaser? I have taken a four-figure number and a six-figure number and I have consistently replaced digits by letters to give the words LITTLE and TIME.

    If you take the digits of TIME and write down all the four-figure numbers which use exactly those digits in some order and then add up all those numbers, then your total will be LITTLE.

    What number is TIME?

    [teaser2863]

     
    • Jim Randell's avatar

      Jim Randell 12:54 pm on 28 May 2020 Permalink | Reply

      The puzzle text doesn’t include the usual alphametic caveat that different letters stand for different digits, only that the digits are replaced consistently by letters (i.e. the same letter is replaced by the same digit). And if there are zero digits involved the 24 rearrangements of the 4 digits of TIME will include some sequences which don’t give 4 valid digit numbers.

      So this Python program considers all 4 digit values for TIME, and totals only those rearrangements corresponding to 4 digit numbers. It runs in 135ms.

      Run: [ @repl.it ]

      from enigma import (irange, nsplit, nconcat, subsets, ordered, cached, printf)
      
      # update a map m consistently with (k, v) pairs in ps
      def update(m, ps):
        m = m.copy()
        for (k, v) in ps:
          x = m.get(k)
          if x is None:
            m[k] = v
          elif x != v:
            return None
        return m
      
      # sum the rearrangements that give 4 digit numbers
      @cached
      def total(ds):
        t = 0
        for s in subsets(ds, size=len, select="mP"):
          if s[0] != 0:
            t += nconcat(s)
        return t
      
      # consider 4 digit numbers for TIME
      for TIME in irange(1000, 9999):
        # this maps the letters T, I, M, E to digits
        ds = nsplit(TIME, 4)
      
        # sum the rearrangements of TIME that give 4 digit numbers
        t = total(ordered(*ds))
      
        # the total is a 6 digit number
        ts = nsplit(t)
        if len(ts) != 6: continue
      
        # map letters to digits
        m = update(dict(zip("TIME", ds)), zip("LITTLE", ts))
        if m is None: continue
      
        # output solution
        printf("TIME={TIME} LITTLE={t}")
      

      Solution: TIME = 3578.

      And so LITTLE = 153318.


      It turns out that using a standard alphametic interpretation, and not worrying about whether the rearrangements always give 4 digit numbers will also give this answer. So:

      If we write down the sum of the 24 permutations of TIME, each letter appears in each column 6 times, the result of the sum is thus:

      6666 × (T + I + M + E) = LITTLE

      Which we can solve as an alphametic expression using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file gives the same solution in 92ms.

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "6666 * (T + I + M + E) = LITTLE"
      
      --answer="(TIME, LITTLE)"
      

      Like

    • GeoffR's avatar

      GeoffR 8:47 am on 29 May 2020 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9: T; var 1..9: I; var 1..9: M;
      var 1..9: E; var 1..9: L; 
      
      var 1000..9999: TIME = 1000*T + 100*I + 10*M + E;
      
      var 100000..999999: LITTLE = 100000*L + 10000*I
      + 1000*T + 100*T + 10*L + E;
      
      % All letters T, I, M, E appear 6 times in 24 permutations
      constraint  6666 * (T + I + M + E) == LITTLE;
      
      solve satisfy;
      
      output ["TIME = " ++ show(TIME) 
      ++ "\nLITTLE = " ++ show(LITTLE)];
      
      % TIME = 3578
      % LITTLE = 153318
      % ----------
      % ==========
      
      
      

      Easy to check the digits in TIME give the digits in LITTLE

      little, cnt = 0, 0
      
      # check value of LITTLE using TIME'S digits of (3,5,7,8)
      from itertools import permutations
      for p in permutations((3,5,7,8)):
          t, i, m, e = p
          time = 1000*t + 100*i + 10*m + e
          # Add current <time> total
          little += time
          # increase permutation count
          cnt += 1
      
      print(f"LITTLE = {little} in {cnt} permutations")
      # LITTLE = 153318 in 24 permutations
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 12:10 pm on 26 May 2020 Permalink | Reply
    Tags:   

    Brain-Teaser 520: [Picnic allocations] 

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

    “What’s inside it?” asked the Mole wriggling with curiosity.

    “There’s cold chicken inside it”, replied the Rat briefly: “cold-tongue-cold-ham-cold-beef-pickled-gherkins-salad-french-rolls-cress-sandwiches-potted-meat-ginger-beer-lemonade-soda-water…”

    “Oh, stop”, cried the Mole in ecstasies. “This is too much for one picnic. We can have another tomorrow on what’s left”.

    “Do you really think so?” inquired the Rat seriously. “Let’s see. There’s only salad-pickled-gherkins-french-rolls-and-soda-water enough for two days: so if we have ham today we’ll have beef tomorrow; if we have potted meat today we’ll have cress sandwiches tomorrow; and if we have tongue today we’ll have lemonade tomorrow”.

    “If we save the cress sandwiches for tomorrow we’ll have the beef today; if we keep the potted meat for tomorrow we’ll have the ginger beer today; and if we keep the lemonade for tomorrow we’ll have the ham today”. The Mole was entering into the spirit of the thing.

    “In any event we’ll have the lemonade and ginger beer on different days, and likewise the beef and the chicken”, Rat shrieked excitedly.

    “And if we have the chicken and cress sandwiches together, we’ll have the potted meat the day after we have the tongue”. The Mole rolled on his back at the prospect. “And we’ll eat every scrap”.

    Which of the eight items did they save for the second day?

    This puzzle was originally published with no title.

    [teaser520]

     
    • Jim Randell's avatar

      Jim Randell 12:10 pm on 26 May 2020 Permalink | Reply

      The following Python program checks all possible assignments of days to the 8 items in question, and then checks to find which assignments satisfy all the conditions.

      It runs in 71ms (on my new laptop). (Internal runtime is 192µs).

      from enigma import (subsets, implies, printf)
      
      # choose days for the 8 items in question
      for (CC, CT, CH, CB, CS, PM, GB, LE) in subsets((1, 2), size=8, select="M"):
      
        # check the statements:
      
        # R: "if we have ham today we'll have beef tomorrow"
        if not implies(CH == 1, CB == 2): continue
      
        # R: "if we have potted meat today we'll have cress sandwiches tomorrow"
        if not implies(PM == 1, CS == 2): continue
      
        # R: "if we have tongue today we'll have lemonade tomorrow"
        if not implies(CT == 1, LE == 2): continue
      
        # M: "if we save the cress sandwiches for tomorrow we'll have the beef today"
        if not implies(CS == 2, CB == 1): continue
      
        # M: "if we keep the potted meat for tomorrow we'll have the ginger beer today"
        if not implies(PM == 2, GB == 1): continue
      
        # M: "if we keep the lemonade for tomorrow we'll have the ham today"
        if not implies(LE == 2, CH == 1): continue
      
        # R: "we'll have the lemonade and ginger beer on different days ..."
        if not (LE != GB): continue
      
        # R: "... and likewise the beef and the chicken"
        if not (CB != CC): continue
      
        # M: "if we have the chicken and cress sandwiches together, we'll
        # have the potted meat the day after we have the tongue"
        if not implies(CC == CS, PM == CT + 1): continue
      
        # output solution
        printf("CC={CC} CT={CT} CH={CH} CB={CB} CS={CS} PM={PM} GB={GB} LE={LE}")
      

      Solution: The cold beef, potted meat and lemonade was saved for the second day.

      Like

      • John Crabtree's avatar

        John Crabtree 8:10 pm on 25 June 2020 Permalink | Reply

        There are 9 statements for 8 unknowns. If Ch is true for chicken today, and ~Ch is true for chicken tomorrow,then the statements may be written as boolean equations:
        1. H.~B + ~H = 1
        2. P.~Cr + ~P = 1
        3. T.~L + ~T = 1
        4. ~Cr.B + Cr = 1
        5. ~P.G + P = 1
        6. ~L.H + L = 1
        7. L XOR G
        8. B XOR Ch
        9. [(Ch XNOR Cr).~P.T] + (Ch XOR Cr) = 1

        Combining equations 5, 7, 6, 1, 4, 2 and 8 gives
        1 = (~P.G + P).( L XOR G).(~L.H + L).(H.~B + ~H).(~Cr.B + Cr).(P.~Cr + ~P).(B XOR Ch)
        = ~P.G.( L XOR G).(~L.H + L).(H.~B + ~H).(~Cr.B + Cr).(P.~Cr + ~P).(B XOR Ch) +
        ….P.(P.~Cr + ~P).(~Cr.B + Cr).(H.~B + ~H).(~L.H + L).( L XOR G).(B XOR Ch)
        = ~P.G.~L.H.~B.Cr.Ch + P.~Cr.B.~H.L.~G.~Ch
        Combing this with equation 9 gives ~P.G.~L.H.~B.Cr.Ch.T = 1
        Equation 3 is satisfied, but is not required to solve the teaser by this method

        The items to be saved for tomorrow are given by ~B,~L and ~P, ie cold Beef, Lemonade and Potted meat.

        Starting with any assumption other than T or ~T and working through the equations 1-2, and 4-8 in some order and finally 9 gives either a consistent solution,or a contradiction (reductio ad absurdum). If one starts by assuming T, then equation 3 is used before the others. If ~T is assumed, then equation 9 gives Ch.~Cr + ~Ch.Cr = 1, and both conditions have to be checked to show that ~T is not true

        Like

      • Jim Randell's avatar

        Jim Randell 3:12 pm on 15 November 2025 Permalink | Reply

        Or using the [[ SubstitutedExpression ]] solver from the enigma.py library.

        The code generated from the following run file has an internal runtime of 75µs.

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        --distinct=""
        --digits="1,2"
        
        --macro="@CC = {A}"  # cold chicken
        --macro="@CT = {B}"  # cold tongue
        --macro="@CH = {C}"  # cold ham
        --macro="@CB = {D}"  # cold beef
        --macro="@CS = {E}"  # cress sandwiches
        --macro="@PM = {F}"  # potted meat
        --macro="@GB = {G}"  # ginger beer
        --macro="@LE = {H}"  # lemonade
        
        # R: "if we have ham today we'll have beef tomorrow"
        "implies(@CH == 1, @CB == 2)"
        
        # R: "if we have potted meat today we'll have cress sandwiches tomorrow"
        "implies(@PM == 1, @CS == 2)"
        
        # R: "if we have tongue today we'll have lemonade tomorrow"
        "implies(@CT == 1, @LE == 2)"
        
        # M: "if we save the cress sandwiches for tomorrow we'll have the beef today"
        "implies(@CS == 2, @CB == 1)"
        
        # M: "if we keep the potted meat for tomorrow we'll have the ginger beer today"
        "implies(@PM == 2, @GB == 1)"
        
        # M: "if we keep the lemonade for tomorrow we'll have the ham today"
        "implies(@LE == 2, @CH == 1)"
        
        # R: "we'll have the lemonade and ginger beer on different days ..."
        "@LE != @GB"
        
        # R: "... and likewise the beef and the chicken"
        "@CB != @CC"
        
        # M: "if we have the chicken and cress sandwiches together, we'll
        # have the potted meat the day after we have the tongue"
        "implies(@CC == @CS, @PM == @CT + 1)"
        
        # output day for each dish
        --template="CC=@CC CT=@CT CH=@CH CB=@CB CS=@CS PM=@PM GB=@GB LE=@LE"
        --solution=""
        --verbose="1-H"
        

        Like

  • Unknown's avatar

    Jim Randell 5:08 pm on 22 May 2020 Permalink | Reply
    Tags:   

    Teaser 3009: Head count 

    From The Sunday Times, 24th May 2020 [link] [link]

    My grandson and I play a simple coin game. In the first round we toss seven coins and I predict how many “heads” there will be whilst my grandson predicts the number of “tails”. After the tossing I score a point for each head plus a bonus of ten if my prediction was correct — my grandson scores likewise for the tails. We then repeat this with six coins, then five, and so on down to a single coin. After each round we keep cumulative totals of our scores. In one game, for over half of the pairs of cumulative scores, my grandson’s total was like mine but with the digits in reverse order. In fact he was ahead throughout and at one stage his cumulative total was double mine — and he had an even bigger numerical lead than that on just one earlier occasion and one later occasion.

    List the number of heads in each of the seven rounds.

    [teaser3009]

     
    • Jim Randell's avatar

      Jim Randell 11:03 pm on 22 May 2020 Permalink | Reply

      Originally I missed the significance of the word “numerical”, and got no solutions. But careful interpretation of the puzzle text does lead to a single solution.

      I wrote a recursive function that keeps track of all the conditions as it goes along.

      The following Python 3 program runs in 320ms.

      Run: [ @repl.it ]

      from itertools import product
      from enigma import irange, nsplit, join, cached, printf
      
      # does A mirror B?
      mirror = cached(lambda A, B: nsplit(A) == nsplit(B)[::-1])
      
      # play the game starting with a round of k coins,
      # where A plays heads, B plays tails, and B is always ahead
      # ms = number of points B is ahead of A
      # s = index in ms when B = 2A
      # rv = count number of scores that are reverse of each other
      def play(k, tA=0, tB=0, ms=[], s=None, rv=0, ss=[]):
        # are we done?
        if k == 0:
          if s is not None and rv > 3:
            # there must be exactly 1 round after s where B is further ahead
            if sum(x > ms[s] for x in ms[s + 1:]) == 1:
              yield ss
        else:
          # consider the number of heads, and predictions for heads and tails
          for (n, h, t) in product(irange(0, k), (0, 1), (0, 1)):
            # calculate the points for each player
            a = n + h * 10
            b = k - n + t * 10
            # and the total points
            A = tA + a
            B = tB + b
            m = B - A
            # B is always ahead
            if not(m > 0): continue
            # look for cases where B = 2A
            s_ = s
            if B == 2 * A:
              # it only happens once
              if s is not None: continue
              # there must be exactly 1 previous round where B is further ahead
              if not(sum(x > m for x in ms) == 1): continue
              s_ = len(ms)
            # are A, B mirrored scores?
            rv_ = rv + mirror(A, B)
            # in the final 4 rounds we must have encountered some mirrored scores
            if k < 5 and rv_ < 5 - k: continue
            # play the remaining rounds
            yield from play(k - 1, A, B, ms + [m], s_, rv_, ss + [(n, h, t, A, B)])
      
      # play the game, starting with 7 coins
      for ss in play(7):
        # output the rounds
        (pA, pB) = (0, 0)
        s = list(i for (i, (n, h, t, A, B)) in enumerate(ss) if B == 2 * A)[0]
        for (i, (n, h, t, A, B)) in enumerate(ss):
          k = 7 - i
          fs = [ f"lead = {B - A}" ]
          if i == s: fs.append("double")
          if mirror(A, B): fs.append("mirror")
          printf(
            "{k} coins: heads={n}; predictions = ({h}, {t}); points = ({a}, {b}); totals = ({A},  {B}); {fs}",
            a=A - pA, b=B - pB, fs=join(fs, sep="; "),
          )
          (pA, pB) = (A, B)
        printf()
      

      Solution: The number of heads in each of the rounds was: 0, 2, 4, 4, 3, 1, 1.

      The full description of the rounds is:

      k   h  t  |  H   T  |  a   b  |  A   B
      7:  0  7  |  -  10  |  0  17  |  0  17  [lead >16]
      6:  2  4  | 10   -  | 12   4  | 12  21  [mirror]
      5:  4  1  |  -  10  |  4  11  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  |  4   0  | 20  32
      3:  3  0  |  -   -  |  3   0  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  |  1  10  | 35  53  [mirror, lead >16]
      

      (k = number of coins; h, t = number of heads/tails; H, T = bonus points; a, b = points in round; A, B = cumulative totals)


      However, keeping the cumulative totals always using 2 digits gives us three further solutions where the totals 03 and 30 are used as mirrored pairs:

      k   h  t  |  H   T  |  a   b  |  A   B
      7:  1  6  |  -  10  | 01  16  | 01  16
      6:  2  4  |  -  10  | 02  14  | 03  30  [mirror, lead >16]
      5:  3  2  | 10   -  | 13  02  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  | 04  00  | 20  32
      3:  3  0  |  -   -  | 03  00  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  | 01  10  | 35  53  [mirror, lead >16]
      
      
      k   h  t  |  H   T  |  a   b  |  A   B
      7:  2  5  |  -  10  | 02  15  | 02  15
      6:  1  5  |  -  10  | 01  15  | 03  30  [mirror, lead >16]
      5:  3  2  | 10   -  | 13  02  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  | 04  00  | 20  32
      3:  3  0  |  -   -  | 03  00  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  | 01  10  | 35  53  [mirror, lead >16]
      
      
      k   h  t  |  H   T  |  a   b  |  A   B
      7:  3  4  |  -  10  | 03  14  | 03  14
      6:  0  6  |  -  10  | 00  16  | 03  30  [mirror, lead >16]
      5:  3  2  | 10   -  | 13  02  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  | 04  00  | 20  32
      3:  3  0  |  -   -  | 03  00  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  | 01  10  | 35  53  [mirror, lead >16]
      

      The program will find all 4 solutions if we replace the calls to [[ nsplit(x) ]] with [[ nsplit(x, 2) ]] in the function [[ mirror() ]] (line 5).

      Like

    • Robert Brown's avatar

      Robert Brown 11:57 am on 24 May 2020 Permalink | Reply

      Turns out there’s a simple manual solution. After each section (7,6,5 etc) there’s a total sum for all heads & tails. Last digit is different in each case. Adding bonuses makes no difference.
      Need to find 4 sections that end in reversible numbers, so just look for reversible pairs where the sum of the pair has the same last digit. Only works for 4 sections, QED.

      Like

  • Unknown's avatar

    Jim Randell 12:59 pm on 21 May 2020 Permalink | Reply
    Tags:   

    Teaser 2864: Sequence of squares 

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

    I started with a rectangle of paper. With one straight cut I divided it into a square and a rectangle. I put the square to one side and started again with the remaining rectangle. With one straight cut I divided it into a square and a rectangle. I put the square (which was smaller than the previous one) to one side and started again with the remaining rectangle. I kept repeating this process (discarding a smaller square each time) until eventually the remaining rectangle was itself a square and it had sides of length one centimetre. So overall I had divided the original piece of paper into squares. The average area of the squares was a two-figure number of square centimetres.

    What were the dimensions of the original rectangle?

    [teaser2864]

     
    • Jim Randell's avatar

      Jim Randell 1:00 pm on 21 May 2020 Permalink | Reply

      If we start with the 1×1 cm square and replace the removed squares, we find the sequence of sizes of squares is:

      1, 1, 2, 3, 5, 8, 13, 21, …

      i.e. a Fibonacci sequence. [ @Wikipedia ]

      So we can start with the two 1×1 cm squares and build up the original rectangle square by square, until find one where the mean area of the squares is a two digit integer as required.

      This Python program runs in 82ms.

      Run: [ @repl.it ]

      from enigma import printf
      
      # initial a x b rectangle, n = number of squares, A = total Area
      (a, b, n, A) = (1, 1, 2, 2)
      while True:
        # calculate the mean area of the squares
        (m, r) = divmod(A, n)
        if m > 99: break
        # construct the rectangle
        (a, b) = (b, a + b)
        if r == 0 and m > 9:
          # output solution, when m is a 2 digit integer
          printf("[n={n}] rectangle = {a} x {b}, mean area = {m}")
        # move on to the next square
        A += b * b
        n += 1
      

      Solution: The original rectangle was 13 cm × 21 cm.

      So in total 6 cuts were made, producing 7 squares.

      The total area of the 7 squares is 273 sq cm, so the mean area is 39 sq cm.


      Manually:

      If:

      F(n) = nth Fibonacci number
      S(n) = sum of squares of F(n) = F(n) F(n + 1)
      M(n) = mean of squares of F(n) = F(n) F(n + 1) / n

      Then we can calculate:

       n  F(n)  S(n)  M(n)
       1    1     1    1
       2    1     2    1
       3    2     6    2
       4    3    15    3.75  
       5    5    40    8
       6    8   104   17.3...
       7   13   273   39
       8   21   714   89.25
       9   34  1870  207.7...
      10   55  ...
      

      And the answer is apparent.

      If we draw a quarter circle through each square we can make a Fibonacci Spiral:

      Liked by 1 person

  • Unknown's avatar

    Jim Randell 5:13 pm on 15 May 2020 Permalink | Reply
    Tags:   

    Teaser 3008: Three-fruit fractions 

    From The Sunday Times, 17th May 2020 [link] [link]

    The owner of the old curiosity shop repaired an antique mechanical fruit machine having three wheels of identical size and format. Afterwards each wheel was independently fair, just as when new. Each wheel’s rim had several equal-sized zones, each making a two-figure whole number of degrees angle around the rim. Each wheel had just one zone showing a cherry, with other fruits displayed having each a different single-figure number (other than one) of zone repetitions.

    Inside the machine were printed all the fair chances (as fractions) of getting three of the same fruit symbol in one go. Each of these fractions had a top number equal to 1 and, of their bottom numbers, more than one was odd.

    What was the bottom number of the chance for three cherries?

    [teaser3008]

     
    • Jim Randell's avatar

      Jim Randell 7:26 am on 16 May 2020 Permalink | Reply

      I assumed the wheels are identical duplicates of each other, which gives a unique solution.

      This Python 3 program runs in 49ms.

      Run: [ @repl.it ]

      from enigma import Rational, divisors, div, irange, join, printf
      
      F = Rational()
      
      # decompose t into numbers between 2 and 9
      def decompose(t, m=2, M=9, s=[]):
        if not(t < m or t > M):
          yield s + [t]
        for x in irange(m, min(t - m, M)):
          yield from decompose(t - x, x + 1, M, s + [x])
      
      # each wheel is divided into n equal sized divisions
      # each spanning d degrees
      for n in divisors(360):
        d = div(360, n)
        if d < 10: break
        if d > 99: continue
      
        # probability of 3 cherries
        p = F(1, n) ** 3
      
        # consider the number of other fruits (all different and between 2 and 9)
        for ns in decompose(n - 1):
          # calculate the probabilities of getting 3 of each of the other fruits
          ps = list(F(x, n) ** 3 for x in ns)
          # probabilities are all 1/N
          if not all(x.numerator == 1 for x in ps): continue
          # and more than one N is odd
          if sum(x.denominator % 2 == 1 for x in ps) < 2: continue
      
          # output solution
          printf("{n} divisions, {d} degrees -> 1 + {ns}; p={p}, ps=[{ps}]", ps=join(ps, sep=", "))
      

      Solution: There is a 1 / 5832 chance of getting 3 cherries.

      Each wheel is divided into 18 sectors. Each sector subtends 20°.

      There are 4 fruits, each is allocated 1, 2, 6, 9 sectors on each wheel.

      The probabilities for each of the 4 fruits are: 1/5832 (= 1/18³), 1/729 (= 1/9³), 1/27 (= 1/3³), 1/8 (= 1/2³).

      Like

  • Unknown's avatar

    Jim Randell 10:42 am on 12 May 2020 Permalink | Reply
    Tags:   

    Teaser 2865: Seventh heaven? 

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

    I have a modern painting by the surrealist artist Doolali. It is called “Seventh Heaven” and it consists of a triangle with green sides and a red spot on each of its sides. The red spots are one seventh of the way along each side as you pass clockwise around the triangle. Then each of the red spots is joined by a straight blue line to the opposite corner of the triangle. These three blue lines create a new triangle within the original one and the new triangle has area 100 sq cm.

    What is the area of the green triangle?

    [teaser2865]

     
    • Jim Randell's avatar

      Jim Randell 10:43 am on 12 May 2020 Permalink | Reply

      We have encountered similar problems to this before (see: Enigma 1313, Enigma 320, Enigma 1076).

      The generalisation of this puzzle is known as Routh’s Theorem [ @wikipedia ], which states that the ratio of the area of the central triangle to the original triangle, where the sides are divided in the ratios 1:x, 1:y, 1:z is given by the formula:

      R = (xyz − 1)² / ((xz + x + 1)(xy + y + 1)(yz + z + 1))

      I made a note giving the areas of the various subdivisions here [ rouths-theorem.pdf ].

      For this puzzle we have: x = y = z = 6, which gives the ratio as:

      R = (x − 1)² / (x² + x + 1) = 25/43

      The smaller triangle has an area of 100, so the larger triangle has an area of: 100 × 43/25 = 172.

      Solution: The area of the green triangle is 172 sq cm.


      Or using a program:

      from enigma import (rational, printf)
      
      # ratio ABC/XYZ in Routh's theorem
      def routh(x, y, z):
        a = x * y * z - 1
        b = (x * y + y + 1) * (y * z + z + 1) * (z * x + x + 1)
        return rational(a * a, b)
      
      # compute the ratio of the area of the inner and outer triangles
      R = routh(6, 6, 6)
      
      # output the solution, given the inner triangle has area 100
      printf("outer triangle = {x} [R={R}]", x=100 / R)
      

      Like

  • Unknown's avatar

    Jim Randell 3:59 pm on 7 May 2020 Permalink | Reply
    Tags:   

    Teaser 3007: Paving stones 

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

    James has decided to lay square block paving stones on his rectangular patio. He has calculated that starting from the outside and working towards the middle that he can lay a recurring concentric pattern of four bands of red stones, then three bands of grey stones, followed by a single yellow stone band. By repeating this pattern and working towards the centre he is able to finish in the middle with a single line of yellow stones to complete the patio.

    He requires 402 stones to complete the first outermost band. He also calculates that he will require exactly 5 times the number of red stones as he does yellow stones.

    How many red stones does he require?

    [teaser3007]

     
    • Jim Randell's avatar

      Jim Randell 4:33 pm on 7 May 2020 Permalink | Reply

      Assuming an x by y rectangle, with x > y.

      If there are k repeats of the (red, grey yellow) bands, then y = 16k − 1.

      And the first band of red stones requires 2x + 2y − 4 = 402 stones, so x + y = 203.

      Here is a constructive program that counts the number of stones in each band. It runs in 78ms.

      from enigma import irange, inf, printf
      
      # calculate the number of stones with <k> repeats
      # and the initial band being <x> by <y>
      def stones(k, y, x):
        d = dict(R=0, G=0, Y=0)
        for i in ("RRRRGGGY" * k):
          if y < 2:
            d[i] += x * y
            break
          d[i] += 2 * (x + y - 2)
          x -= 2
          y -= 2
        return (d['R'], d['G'], d['Y'])
      
      # consider the number of repeats of the bands
      for k in irange(1, inf):
        y = 16 * k - 1
        x = 203 - y
        if not(x > y): break
       
        # calculate the number of stones needed
        (R, G, Y) = stones(k, y, x)
       
        # output solution
        if 5 * Y == R:
          printf("k={k}, y={y} x={x}; R={R} G={G} Y={Y}")
      

      Solution: James requires 5520 red stones.

      The patio consists of 6 red/grey/yellow layers, finishing with a single row of 14 yellow stones:

      The number of stones required is: red = 5520, grey = 3636, yellow = 1104; total = 10260.

      The outer band measures 108×95 stones.

      Like

    • Jim Randell's avatar

      Jim Randell 9:33 am on 10 May 2020 Permalink | Reply

      Each band requires 8 fewer stones than the previous band, so we can just start with the outermost band (402 stones) and work our way in. When we get to the number blocks for the yellow band we can adjust the number to account for a single line to see if we have reached the middle of the design, and if not carry on. We don’t need to do any analysis to work out the dimensions of the patio, or consider the number of repeats.

      This gives us a simpler, and slightly shorter, program.

      Run: [ @repl.it ]

      from enigma import irange, chunk, printf
      
      # number of blocks in outer band
      n = 402
      
      # record Red, Grey, Yellow blocks required
      R = G = Y = 0
      
      # work inwards in chunks of 8 bands
      # each band has 8 less blocks than the previous band
      for s in chunk(irange(n, 1, step=-8), 8):
        if len(s) < 8: break
        (r1, r2, r3, r4, g1, g2, g3, y) = s
        R += r1 + r2 + r3 + r4
        G += g1 + g2 + g3
        # the middle is a single row
        m = 1 + y // 2
        if 5 * (Y + m) == R:
          printf("R={R} G={G} Y={Y}", Y=Y + m)
        # otherwise, carry on
        Y += y
      

      Like

  • Unknown's avatar

    Jim Randell 10:54 am on 7 May 2020 Permalink | Reply
    Tags: ,   

    Teaser 2869: Cubic savings 

    From The Sunday Times, 17th September 2017 [link] [link]

    In 2009 George and Martha had a four-figure number of pounds in a special savings account (interest being paid into a separate current account). At the end of the year they decided to give some of it away, the gift being shared equally among their seven grandchildren, with each grandchild getting a whole number of pounds. At the end of the following year they did a similar thing with a different-sized gift, but again each grandchild received an equal whole number of pounds. They have repeated this procedure at the end of every year since.

    The surprising thing is that, at all times, the number of pounds in the savings account has been a perfect cube.

    What is the largest single gift received by any grandchild?

    [teaser2869]

     
    • Jim Randell's avatar

      Jim Randell 10:54 am on 7 May 2020 Permalink | Reply

      This puzzle is marked as flawed, as there are two possible solutions.

      I assumed the number of grandchildren remained constant (at 7) during the eight years in question (2009 – 2016).

      The amounts in the savings account are perfect cubes, that differ by multiples of 7, so we can collect cubes by their residue modulo 7, and consider the sets for each residue to look for 9 amounts that satisfy the remaining conditions.

      This Python program runs in 76ms.

      Run: [ @replit ]

      from enigma import (group, powers, mod, subsets, tuples, printf)
      
      # group the cubes (up to 4 digits) in descending order, by their residue mod 7
      cubes = group(powers(21, 1, 3, step=-1), by=mod(7))
      
      # choose values for the eight gifts made (2009 - 2016)
      for s in cubes.values():
        for vs in subsets(s, size=9):
          if s[0] < 1000: continue
          # and calculate the sequence of gifts
          gs = tuple((a - b) // 7 for (a, b) in tuples(vs, 2))
          if len(set(gs)) != len(gs): continue # gift amounts are all different
          # output solutions
          m = max(gs)
          printf("{vs} -> {gs}, max = {m}")
      

      Solution: There are two solutions. The largest amount is received by a grandchild is £ 292, or £ 388.

      If we start with 5832 (= 18³) in the savings account, and then give presents of (248, 103, 292, 86, 31, 64, 8, 1) to each grandchild then the amounts remaining in the savings account are:

      5832 (= 18³)
      4096 (= 16³)
      3375 (= 15³)
      1331 (= 11³)
      729 (= 9³)
      512 (= 8³)
      64 (= 4³)
      8 (= 2³)
      1 (= 1³)

      However, starting with 8000 (= 20³) and giving (163, 278, 388, 67, 104, 112, 13, 14), leaves amounts of:

      8000 (= 20³)
      6859 (= 19³)
      4913 (= 17³)
      2197 (= 13³)
      1728 (= 12³)
      1000 (= 10³)
      216 (= 6³)
      125 (= 5³)
      27 (= 3³)

      One way to rescue the puzzle is to exclude 1 as an amount given to the grandchildren (or remaining in the account).

      Another way is to require that none of the amounts given to any grandchild is itself a perfect cube.

      Either of these restrictions eliminate the first solution, leaving a unique answer of £ 388.

      Like

    • GeoffR's avatar

      GeoffR 8:28 am on 10 May 2020 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Yearly account balances
      var 1000..9999: S1; var 1..9999: S2; var 1..9999: S3;
      var 1..9999: S4; var 1..9999: S5; var 1..9999: S6;
      var 1..9999: S7; var 1..9999: S8; var 1..9999: S9;
      
      % yearly gifts 2009 - 2016
      var 1..1400: G1; var 1..999: G2; var 1..999: G3;
      var 1..999: G4; var 1..999: G5; var 1..999: G6;
      var 1..999: G7; var 1..999: G8; 
      
      % All yearly gifts are different values
      constraint all_different ([G1, G2, G3, G4, G5, G6, G7, G8]);
      
      % Set of cubes up to a maximum of four digits
      set of int: cube = {n * n * n | n in 1..21};
      
      % S1 is largest yearly account balance
      constraint increasing([S9,S8,S7,S6,S5,S4,S3,S2,S1]);
      
      % All yearly account balances are cubes
      constraint S1 in cube /\  S2 in cube /\  S3 in cube 
      /\ S4 in cube /\  S5 in cube /\  S6 in cube
      /\ S7 in cube /\  S8 in cube /\ S9 in cube;
      
      % Yearly amounts to distribute amongst seven grandchildren
      constraint (S1 - S2) mod 7 == 0 /\ (S2 - S3) mod 7 == 0 /\ 
      (S3 - S4) mod 7 == 0 /\ (S4 - S5) mod 7 == 0
      /\ (S5 - S6) mod 7 == 0 /\ (S6 - S7) mod 7 == 0 
      /\ (S7 - S8) mod 7 == 0 /\ (S8 - S9) mod 7 == 0;
      
      % Yearly total gifts to each of seven grandchildren
      constraint G1 == (S1 - S2) div 7 /\ G2 == (S2 - S3) div 7
      /\ G3 == (S3 - S4) div 7 /\ G4 == (S4 - S5) div 7
      /\ G5 == (S5 - S6) div 7 /\ G6 == (S6 - S7) div 7
      /\ G7 == (S7 - S8) div 7 /\ G8 == (S8 - S9) div 7;
      
      % Maximum yearly gift
      var 10..1000: maxv;
      maxv = max([G1, G2, G3, G4, G5, G6, G7, G8]);
       
      solve satisfy;
      
      output ["Yearly account balances: " ++ 
      show([S1, S2, S3, S4, S5, S6, S7, S8, S9])] ++ 
      ["\nYearly gifts(£): " ++ show ([G1, G2, G3, G4, G5, G6, G7, G8])]
      ++ ["\nMax gift = " ++ show(maxv)] ;
      
      % Yearly account balances: [5832, 4096, 3375, 1331, 729, 512, 64, 8, 1]
      % Yearly gifts(£): [248, 103, 292, 86, 31, 64, 8, 1]
      % Max gift = 292
      % ----------
      % Yearly account balances: [8000, 6859, 4913, 2197, 1728, 1000, 216, 125, 27]
      % Yearly gifts(£): [163, 278, 388, 67, 104, 112, 13, 14]
      % Max gift = 388
      % ----------
      % ==========
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:07 am on 5 May 2020 Permalink | Reply
    Tags:   

    Teaser 2870: Lucky dip 

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

    My bank PIN consists of four different digits in decreasing order. I used this PIN to help me choose my six lottery numbers. I wrote down all the two-figure numbers that used two different digits from the PIN. Just six of those numbers were in the range from 10 to 49 and so they were my lottery choices. In fact the sum of the six is a perfect square. If you knew that square it would now be possible to work out my PIN.

    What is my PIN?

    [teaser2870]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 5 May 2020 Permalink | Reply

      This Python program runs in 87ms.

      Run: [ @repl.it ]

      from enigma import (subsets, irange, is_square, filter_unique, unpack, nconcat, printf)
      
      # record (<PIN digits>, <2 digit numbers>, <square root of the sum of 2 digit numbers>)
      rs = list()
      
      # choose the 4 digits (in decreasing order)
      for ds in subsets(irange(9, 0, step=-1), size=4, select="C"):
        # collect 2-digt numbers between 10 and 49
        ns = tuple(n for n in map(nconcat, subsets(ds, size=2, select="P")) if 9 < n < 50)
        # there are exactly 6 of them
        if len(ns) != 6: continue
        # and their sum is a perfect square
        s = is_square(sum(ns))
        if s is None: continue
        printf("[ds = {ds}, ns = {ns}, s = {s}]")
        rs.append((ds, ns, s))
      
      # find PIN digits unique by s
      (rs, _) = filter_unique(rs, unpack(lambda ds, ns, s: s), unpack(lambda ds, ns, s: ds))
      
      # output solutions
      for (ds, ns, s) in rs:
        printf("PIN = {pin}, numbers = {ns}, sum = {s}^2", pin=nconcat(ds), ns=sorted(ns))
      

      Solution: The PIN is 5420.

      There are 5 candidate PINs:

      5420, numbers = (45, 42, 40, 25, 24, 20), sum = 14²
      7320, numbers = (37, 32, 30, 27, 23, 20), sum = 13²
      7410, numbers = (47, 41, 40, 17, 14, 10), sum = 13²
      8621, numbers = (28, 26, 21, 18, 16, 12), sum = 11²
      9521, numbers = (29, 25, 21, 19, 15, 12), sum = 11²

      Only the first of these has a unique sum.

      Like

    • GeoffR's avatar

      GeoffR 5:04 pm on 5 May 2020 Permalink | Reply

      from itertools import permutations
      
      from collections import defaultdict
      D = defaultdict(list)
      
      def is_sq(n):
        c = int(n**(1/2) + 0.5)
        return (c**2 == n) 
      
      for P1 in permutations (range(10),4):
        a, b, c, d = P1
        # choose four different digits in decreasing order
        if a > b > c > d:
          t = 1000*a + 100*b + 10*c + d   # PIN
          # find numbers in range 10...49 from 4 digits
          for P2 in permutations((a, b, c, d), 2):
            x, y = P2
            n = 10 * x + y
            if 10 <= n <= 49:
              D[t] += [n]
      
      for k,v in D.items():
        # find 6 lottery numbers
        if len(v) == 6:
          # check sum of 6 numbers is a square
          t2 = v[0] + v[1] + v[2] + v[3] + v[4] + v[5]
          if is_sq(t2):
            print(k, v, t2)
      
      # 5420 [45, 42, 40, 25, 24, 20] 196 << PIN = 5420
      # 7320 [37, 32, 30, 27, 23, 20] 169
      # 7410 [47, 41, 40, 17, 14, 10] 169
      # 8621 [28, 26, 21, 18, 16, 12] 121
      # 9521 [29, 25, 21, 19, 15, 12] 121
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:47 am on 3 May 2020 Permalink | Reply
    Tags:   

    Brain-Teaser 519: Badberg clock 

    From The Sunday Times, 23rd May 1971 [link]

    At the village of Badberg, hidden away in the Alps, there is a town clock of a certain antiquity. The maker was a wealthy but inexpert amateur. After the ornate instrument had been installed it was found that the great hands stood still during the equal intervals between each stroke of the hour on the massive bell. Naturally, the clock was always slow.

    The placid villagers became accustomed to the erratic behaviour of their timepiece; only after the death of its donor did his nephew dare to tackle the problem. Finding it impossible to alter the striking mechanism, he ingeniously modified the movement to run at a higher constant speed so that the hands showed the correct time at least when the clock struck certain hours.

    Unfortunately, the hands still stop for the same period between successive strokes of the bell, but the villages can now see and hear the correct time every six hours.

    At what hours does the clock make its first stroke correctly?

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

    [teaser519]

     
    • Jim Randell's avatar

      Jim Randell 10:47 am on 3 May 2020 Permalink | Reply

      The clock only pauses between the strokes, so at 1 o’clock it doesn’t pause. At 2 o’clock it pauses for 1 interval. At 3 o’clock, 2 intervals. And so on until 12 o’clock when it pauses for 11 intervals. So the total time lost in a 12 hour period is 66 intervals.

      And the speed of the clock is adjusted so that the time lost in these 66 intervals is made up during the 12 hours. So we need to find adjacent 6 hour periods each containing 33 intervals. And there are only 6 pairs of periods to consider.

      Run: [ @repl.it ]

      from enigma import irange, tuples, printf
      
      # "clock" arithmetic
      clock = lambda n, m=12: (n % m) or m
      
      # interval count for each hour
      ps = list(irange(0, 11))
      
      # find a consecutive sequence of 6 intervals that is half the total
      t = sum(ps)
      for (i, s) in enumerate(tuples(ps, 6)):
        if sum(s) * 2 == t:
          (h1, h2) = (i + 1, i + 7)
          printf("{h1}, {h2}", h1=clock(h1), h2=clock(h2))
      

      Solution: The clock is correct at 4 o’clock and 10 o’clock.

      In striking 4, 5, 6, 7, 8, 9 o’clock the clock pauses for 3 + 4 + 5 + 6 + 7 + 8 = 33 intervals.

      In striking 10, 11, 12, 1, 2, 3 o’clock the clock pauses for 9 + 10 + 11 + 0 + 1 + 2 = 33 intervals.

      Like

  • Unknown's avatar

    Jim Randell 5:53 pm on 1 May 2020 Permalink | Reply
    Tags:   

    Teaser 3006: Raffle tickets 

    From The Sunday Times, 3rd May 2020 [link] [link]

    At our local bridge club dinner we were each given a raffle ticket. The tickets were numbered from 1 to 80. There were six people on our table and all our numbers were either prime or could be expressed as the product of non-repeating primes (e.g. 18 = 2×3×3 is not allowed). In writing down the six numbers you would use each of the digits 0 to 9 once only. If I told you the sum of the six numbers (a perfect power) you should be able to identify the numbers.

    List the numbers (in ascending order).

    [teaser3006]

     
    • Jim Randell's avatar

      Jim Randell 6:14 pm on 1 May 2020 Permalink | Reply

      This Python program runs in 172ms.

      Run: [ @repl.it ]

      from enigma import irange, inf, is_duplicate, factor, seq_all_different as all_different, filter_unique, iroot, printf
      
      # find numbers with different prime factors, and no repeated digits
      ns = list()
      for n in irange(1, 80):
        if is_duplicate(n): continue
        fs = factor(n)
        if fs and all_different(fs):
          ns.append(n)
      
      # find sets of k numbers, that use the digits 0-9 exactly once
      def generate(ns, k, ds=set(), s=[]):
        # are we done?
        if k == 0:
          if len(ds) == 10:
            yield tuple(s)
        else:
          # add in another number
          for (i, n) in enumerate(ns, start=1):
            t = str(n)
            if ds.intersection(t): continue
            yield from generate(ns[i:], k - 1, ds.union(t), s + [n])
      
      # find 6-sets that are unique by sum
      (ss, _) = filter_unique(generate(ns, 6), sum)
      
      # find powers for n
      def powers(n, p=1):
        for k in irange(p, inf):
          x = iroot(n, k)
          if x ** k == n:
            yield (x, k)
          if x == 1: break
      
      # output solution
      for s in ss:
        t = sum(s)
        ps = list(powers(t, 2))
        printf("{s} -> {t} {ps}")
      

      Solution: The numbers are: 2, 3, 41, 58, 69, 70.

      The sum is 243 (= 3^5).

      We can find 78 different sets of 6 numbers that use the digits 0-9 exactly once, but only the set given above has a unique sum (which is the largest possible sum).

      Like

    • GeoffR's avatar

      GeoffR 11:25 am on 5 May 2020 Permalink | Reply

      The programme found four solutions, but only the first is a correct unique solution
      ie Tickets: 2, 3, 41, 58, 69, 70.

      There is anpother duplicate solution with a total of 216, but the programme did not find it. Two other potential solutions also had a total of 225, so do not give a unique solution.

      The programme would only run under the Chuffed solver in a reasonable time.

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 0..9:A; var 0..9:B; var 0..9:C; var 0..9:D;
      var 0..9:E; var 0..9:F; var 0..9:G; var 0..9:H;
      var 0..9:I; var 0..9:J;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I,J]);
      
      % total of six factors
      var 100..400: total;
      
      % perfect powers possible between 100 and 400
      constraint total == pow(2,8) \/ total == pow(3,4) \/ total == pow(3,5) 
      \/ total == pow(5,3) \/ total == pow(6,3) \/ total == pow(7,3)
      \/ total == pow(12,2) \/ total == pow(13,2) \/ total = pow(14,2)
      \/ total == pow(15,2) \/ total == pow(17,2) \/ total = pow(18,2);
      
      % numbers are A, B, CD, EF, GH, IJ
      total = A + B + CD + EF + GH + IJ;
      var 11..80: CD = 10*C + D;
      var 11..80: EF = 10*E + F;
      var 11..80: GH = 10*G + H;
      var 11..80: IJ = 10*I + J;
      
      % Possible prime factors for CD, EF, GH and IJ
      var 2..79: a1; var 2..79: a2; var 2..79: a3;
      var 2..79: b1; var 2..79: b2; var 2..79: b3;
      var 2..79: c1; var 2..79: c2; var 2..79: c3;
      var 2..79: d1; var 2..79: d2; var 2..79: d3;
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      constraint is_prime (A) /\ is_prime(B);
      
      % CD is prime or has two or three different prime factors
      constraint (is_prime(CD))
      \/ 
      (a1 != a2 /\ CD == a1 * a2 /\ is_prime(a1) /\ is_prime(a2))
      \/  
      (a1 != a2 /\ a1 != a3 /\ a2 != a3 /\ CD == a1 * a2 * a3 /\ 
      is_prime(a1) /\ is_prime(a2) /\ is_prime(a3));
      
      % EF is prime or has two or three different prime factors
      constraint (is_prime(EF))
      \/ 
      (b1 != b2 /\ EF == b1 * b2 /\ is_prime(b1) /\ is_prime(b2))
      \/  
      (b1 != b2 /\ b1 != b3 /\ b2 != b3 /\ EF == b1 * b2 * b3 /\ 
      is_prime(b1) /\ is_prime(b2) /\ is_prime(b3));
      
      % GH is prime or has two or three different prime factors
      constraint (is_prime(GH))
      \/ 
      (c1 != c2 /\ GH == c1 * c2 /\ is_prime(c1) /\ is_prime(c2))
      \/  
      (c1 != c2 /\ c1 != c3 /\ c2 != c3 /\ GH == c1 * c2 * c3 /\ 
      is_prime(c1) /\ is_prime(c2) /\ is_prime(c3));
      
      % IJ is prime or has two or three different prime factors
      constraint (is_prime(IJ))
      \/ 
      (d1 != d2 /\ IJ == d1 * d2 /\ is_prime(d1) /\ is_prime(d2))
      \/  
      (d1 != d2 /\ d1 != d3 /\ d2 != d3 /\ IJ == d1 * d2 * d3 /\ 
      is_prime(d1) /\ is_prime(d2) /\ is_prime(d3));
      
      constraint increasing([A ,B, CD, EF, GH, IJ]);
      
      solve satisfy;
      
      output ["Tickets: " ++ show(A) ++ ", " ++ show(B) ++ ", " 
      ++ show(CD) ++ ", " ++ show(EF) ++ ", " ++ show(GH) ++ ", "
      ++ show(IJ) ++ ", total = " ++ show(total) ];
      
      
      % Tickets: 2, 3, 41, 58, 69, 70, total = 243  <<< Correct Solution
      % time elapsed: 0.03 s
      % ----------
      % Tickets: 2, 3, 14, 58, 69, 70, total = 216 << A duplicate solution exists
      % time elapsed: 0.03 s
      % ----------
      % Tickets: 2, 5, 38, 41, 69, 70, total = 225 - duplicate 
      % time elapsed: 0.03 s
      % ----------
      % Tickets: 2, 5, 30, 41, 69, 78, total = 225 _ duplicate
      % time elapsed: 0.03 s
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 11:56 am on 30 April 2020 Permalink | Reply
    Tags:   

    Teaser 2876: Bonfire toffee 

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

     In this subtraction sum I have consistently replaced digits with letters, different letters being used for different digits:

    BONFIRETOFFEE = TREATS

    What is the number of ENTRIES?

    [teaser2876]

     
    • Jim Randell's avatar

      Jim Randell 11:56 am on 30 April 2020 Permalink | Reply

      We can solve this puzzle directly using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      >>> python -m enigma SubstitutedExpression "TREATS + TOFFEE = BONFIRE" --answer="ENTRIES"
      (TREATS + TOFFEE = BONFIRE) (ENTRIES)
      (769470 + 758899 = 1528369) (9276390) / A=4 B=1 E=9 F=8 I=3 N=2 O=5 R=6 S=0 T=7
      (769870 + 754499 = 1524369) (9276390) / A=8 B=1 E=9 F=4 I=3 N=2 O=5 R=6 S=0 T=7
      ENTRIES = 9276390 [2 solutions]
      

      Solution: ENTRIES = 9276390.

      There are two possible sums, the values for A and F being interchangeable:

      769470 + 758899 = 1528369
      769870 + 754499 = 1524369

      Like

  • Unknown's avatar

    Jim Randell 8:29 am on 28 April 2020 Permalink | Reply
    Tags:   

    Teaser 2593: Spell it out! 

    From The Sunday Times, 3rd June 2012 [link] [link]

    I have written down a very large number (but with fewer than twenty-five digits). If you spell out each of its digits as a word, then for each digit its last letter is the same as the first letter of the next digit (the last letter of the last digit being the same as the first letter of the first digit). One example of such a number would be 83821.

    Neither my number nor the sum of its digits is a palindromic number (but in fact the original number is one more than a palindrome).

    What is my number?

    [teaser2593]

     
    • Jim Randell's avatar

      Jim Randell 8:30 am on 28 April 2020 Permalink | Reply

      This Python program runs in 99ms.

      Run: [ @repl.it ]

      from enigma import (int2words, irange, is_palindrome, nsplit, printf)
      
      # digits as words
      words = list(int2words(d) for d in irange(0, 9))
      
      # possible next numbers
      succ = dict()
      for (d1, w1) in enumerate(words):
        succ[d1] = list(d2 for (d2, w2) in enumerate(words) if w1[-1] == w2[0])
      
      # find numbers that form a loop according to relationship r
      def solve(ns, k, r=succ):
        # check the numbers form a loop
        if ns[0] in r[ns[-1]]:
          yield ns
        # can we add another number
        if len(ns) < k:
          for n in r[ns[-1]]:
            yield from solve(ns + [n], k)
      
      # consider possible initial digits
      for n in succ.keys():
        # find loops with length less than 25
        for ns in solve([n], 24):
          # the number itself is 1 more than a palindrome
          if not (ns[0] + 1 == ns[-1] and is_palindrome(ns[1:-1])): continue
          # the sum of the digits is not a palindrome
          t = sum(ns)
          if is_palindrome(nsplit(t)): continue
          # output solution
          printf("{ns} -> {t}")
      

      Solution: The number is 183838383838383838382.

      The number is 21 digits long. And the digit sum is (8 + 3) × 9 + (1 + 8 + 2) = 110.

      Like

  • Unknown's avatar

    Jim Randell 9:19 am on 26 April 2020 Permalink | Reply
    Tags: by: Jonathan Welton   

    Brainteaser 1814: Clear heads 

    From The Sunday Times, 22nd June 1997 [link]

    “I have affixed a card to each of your foreheads”, explained the professor of logic to his three most able students. All three were perfect logicians capable of instantly deducing the logical consequences of any statement.

    He continued: “You can see the others’ cards, but none of you can see your own. On each card is written a positive whole number and one of the numbers is the sum of the other two”.

    The professor turned to the first student, Albert: “From what you can see and what you have heard, can you deduce the number on your card?”. Albert could not.

    The professor then asked Bertrand the same question. He could not deduce the number on his card.

    Likewise, when asked next, Charles could not deduce the number on his card.

    The professor then asked Albert the same question again and this time Albert was able to deduce that the number on his card was 50.

    What number was on Bertrand’s card, and what number was on Charles’s card?

    This puzzle is included in the book Brainteasers (2002). The puzzle text above is taken from the book.

    [teaser1814]

     
    • Jim Randell's avatar

      Jim Randell 9:20 am on 26 April 2020 Permalink | Reply

      Each person can see two numbers, and knows their own number must be either the sum or difference of these two numbers.

      So when A is asked, the only way he would be able to narrow this choice down to a single number is if B and C’s numbers are the same, as the difference would be zero, and this is not allowed, so he would know his own number was the sum of B and C’s numbers. So A will be able to declare at the first question only if the set numbers is of the form (2n, n, n).

      Similarly, when B is asked, he will be able to declare if the set of numbers is of the form (n, 2n, n).

      But we also know that if B sees (2n, ?, n) that his own number must be 3n or n, but it cannot be n, as A would have declared if the numbers were (2n, n, n). So B can also declare if the sets of numbers are of the form (2n, 3n, n).

      When C is asked, he can declare if the numbers of the form: (n, n, 2n), (2n, n, 3n), (n, 2n, 3n), (2n, 3n, 5n).

      A is then asked again, and this time declares. So the numbers must fit one of the following patterns:

      (3n, 2n, n)
      (4n, 3n, n)
      (3n, n, 2n)
      (4n, n, 3n)
      (5n, 2n, 3n)
      (8n, 3n, 5n)

      Of these patterns, only one gives integer values when the first element is 50:

      (5n, 2n, 3n) = (50, 20, 30)

      Solution: Bertrand’s number was 20. Charles’s number was 30.


      Here is a Python program that generates the appropriate patterns for each question, and then determines what sequences of numbers match the patterns for the 4th question. It runs in 73ms.

      Run: [ @repl.it ]

      from enigma import irange, inf, flatten, div, printf
      
      # update pattern p, element i becomes the sum of the other elements
      def update(p, i):
        p = list(p)
        p[i] = sum(p) - p[i]
        return p
      
      # produce patterns that lead to declarations for A, B, C, A, B, C, ...
      def declare():
        ds = list()
        for i in irange(0, inf):
          ps = list()
          # initial seeds
          if i < 3: ps.append(update([1, 1, 1], i))
          # add in alternatives from the previous rounds
          j = i % 3
          for p in flatten(ds):
            ps.append(update(p, j))
          yield ps
          # keep the previous 2 rounds
          ds.append(ps)
          if len(ds) > 2: ds.pop(0)
      
      # find declaration patterns that correspond to the 4th question
      for (i, ps) in enumerate(declare(), start=1):
        printf("[{i} -> {ps}]")
        if i == 4: break
      
      # given A calculate B and C from the patterns
      A = 50
      for (a, b, c) in ps:
        (B, C) = (div(b * A, a), div(c * A, a))
        if B and C:
          printf("A={A} B={B} C={C}")
      

      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