Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 1:45 pm on 10 November 2019 Permalink | Reply
    Tags: ,   

    Brainteaser 1742: Not-so-safe deposit 

    From The Sunday Times, 4th February 1996 [link]

    The safe deposit boxes at our bank are arranged in a huge pyramid, the top of which is illustrated:

    When the were first opened the top box had a quantity of gold coins placed in it and the rest were empty. One night a burglar broke into that box, emptied it, and (cleverly, he thought) instead of running off with the coins, he found the two empty boxes in the row below and put some of his haul in one and the rest in the other.

    Word spread through the underworld about the bank’s lax security, and a sequence of burglaries took place. Each burglar broke into one box containing some coins, emptied it, found two empty boxes in the row below, placed some of his haul in one of these boxes and the rest in the other.

    Ages later the manager of the bank opened up the top box and was horrified to find it empty. So he tried the boxes in the next row and found them empty too. He carried on like this until he found some coins in one particular row. In fact, no matter how many burglaries had taken place, there could not have been more empty rows at the top of the pyramid. And indeed if any fewer burglaries had taken place all those rows could not have been empty.

    (a) How many empty rows at the top were there?
    (b) How many burglaries were there?

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

    [teaser1742]

     
    • Jim Randell's avatar

      Jim Randell 1:45 pm on 10 November 2019 Permalink | Reply

      We can try to empty the uppermost rows as quickly as possible.

      Initially, in the top row there is 1 box, initially containing the complete amount:

      [0] 1/1 = 1

      The first burglar breaks in, and splits the amount between the two boxes in the second row. So each of the 2 boxes contains 1/2 of the complete amount:

      [1] 0/1 + 2/2 = 1

      The second burglar breaks in and splits the amount from one of the boxes in the second row between 2 (of the 3) boxes in the third row:

      [2] 0/1 + 1/2 + 2/4 = 1

      The third burglar can’t burgle the remaining box in the second row, as there aren’t enough empty boxes in the third row to hide the spoils. So he splits the amount from one of the boxes in the third row into 2 (of the 4) boxes in the fourth row:

      [3] 0/1 + 1/2 + 1/4 + 2/8 = 1

      The fourth burglar can burgle the box in the second row, as there are now 2 empty boxes in the third row:

      [4] 0/1 + 0/2 + 3/4 + 2/8 = 1

      And so the process continues:

      [5] 0/1 + 0/2 + 2/4 + 4/8 = 1
      [6] 0/1 + 0/2 + 2/4 + 3/8 + 2/16 = 1
      [7] 0/1 + 0/2 + 2/4 + 2/8 + 4/16 = 1
      [8] 0/1 + 0/2 + 1/4 + 4/8 + 4/16 = 1
      [9] 0/1 + 0/2 + 1/4 + 4/8 + 3/16 + 2/32 = 1
      [10] 0/1 + 0/2 + 1/4 + 3/8 + 5/16 + 2/32 = 1
      [11] 0/1 + 0/2 + 1/4 + 3/8 + 4/16 + 4/32 = 1
      [12] 0/1 + 0/2 + 1/4 + 3/8 + 3/16 + 6/32 = 1
      [13] 0/1 + 0/2 + 1/4 + 2/8 + 5/16 + 6/32 = 1
      [14] 0/1 + 0/2 + 0/4 + 4/8 + 5/16 + 6/32 = 1

      So we’ve managed to empty the first 3 rows (in 14 burglaries).

      Can we empty the fourth row?

      What if every row below it was full? We would have:

      S = 5/16 + 6/32 + 7/64 + 8/128 + …
      S = (1/16 + 4/16) + (1/32 + 5/32) + (1/64 + 6/64) + (1/128 + 7/128) + …
      S = (1/16 + 1/32 + 1/64 + 1/128 + …) + (4/16) + (5/32 + 6/64 + 7/128 + …)
      S = (1/8)(1/2 + 1/4 + 1/8 + 1/16 + …) + (1/4) + S/2
      S = (1/8) + (1/4) + S/2
      S/2 = 3/8
      S = 3/4

      We can check this by calculating the sum the first few terms:

      >>> sum(fdiv(n + 1, 2**n) for n in irange(4, 100))
      0.75
      

      So there is not enough room to hide the entire amount using just rows 5 onwards, hence we can never empty row 4.

      Solution: (a) The first 3 rows were empty.

      The number of burglaries required to achieve any situation is one less than the number of occupied boxes.

      And as the fractions of the original collection decrease as we move down the rows we want to occupy the minimal number of boxes possible. If the first 3 rows are empty this minimal number is 4 on the 4th row, 5 on the 5th row, 6 on the 6th row (4/8 + 5/16 + 6/32 = 1), which required 4 + 5 + 6 − 1 = 14 burglaries.

      Solution: (b) There were 14 burglaries.

      Like

      • John Crabtree's avatar

        John Crabtree 9:50 pm on 13 November 2019 Permalink | Reply

        While experimenting with this, I found that one box in row 2 could be replaced by:
        i) a pyramid with one box full in row 4, two in row 5, three in row 6 etc
        or ii) one box full in row 4, and three in all lower rows
        Add the two together, and the grid would be full with two boxes full in row 4, and all all boxes in lower rows. It does not matter if this pattern can actually be reached. Row 4 cannot be empty. it is quite easy to check if the first three rows can be emptied.

        Like

  • Unknown's avatar

    Jim Randell 4:53 pm on 8 November 2019 Permalink | Reply
    Tags:   

    Teaser 2981: Faulty pedometer 

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

    Judith is a keen walker who uses a five-digit pedometer to record her number of steps. Her pedometer is inaccurate as some of the counters consistently move on to 0 early by missing out one or more digits. For instance, one of them might roll over from 7 to 0 every time instead of from 7 to 8, missing out digits 8 and 9. She is, however, well aware of this and can work out the correct number of steps.

    After walking her usual distance, the pedometer shows 37225 steps but she knows that the true number is 32% less than this. A second distance she walks requires a 30% reduction in the number displayed to give the true number of steps.

    How many steps is the second distance?

    [teaser2981]

     
    • Jim Randell's avatar

      Jim Randell 7:38 am on 9 November 2019 Permalink | Reply

      Originally I thought the pedometer would be accumulating steps (so the second reading would add some steps to the first reading), but I could not find a solution where the second reading should be reduced by 30% (although I did get an answer where it was reduced by 28%).

      So I considered the problem where the pedometer is reset to zero before the start of each walk, and doesn’t wrap around. This gives a unique solution.

      This Python program finds possible bases for the 32% reduction, and then uses those bases to look for values with a 30% reduction. It runs in 161ms.

      Run: [ @repl.it ]

      from enigma import (div, irange, nconcat, printf)
      
      # find k bases that give a reading of R for actual value N
      def bases(R, N, k, bs=[]):
        if k == 0:
          if R == N:
            yield bs
        else:
          (r, d) = divmod(R, 10)
          for b in irange(d + 1, 10):
            (n, x) = divmod(N, b)
            if x == d:
              yield from bases(r, n, k - 1, bs + [b])
      
      # turn actual value n into a reading according to bases bs
      def reading(n, bs):
        ds = list()
        for b in bs:
          (n, d) = divmod(n, b)
          ds.insert(0, d)
        if n > 9: return
        ds.insert(0, n)
        return nconcat(ds)
      
      # solve the puzzle
      # R = initial reading
      # p = percentage of R for actual reading
      # q = percentage for next reading
      def solve(R, p, q):
        N = div(R * p, 100)
        printf("[reading = {R} -> actual = {N}]")
      
        # compute bases for the first 4 digits
        for bs in bases(R, N, 4):
          printf("[bases = {bs}]")
      
          # find an actual value for the next reading
          for n in irange(1, 99999):
            # calculate the required reading
            r = div(n * 100, q)
            if r is None: continue
            # calculate the reading from the bases
            r2 = reading(n, bs)
            if r2 is None: break
            if r == r2:
              yield (n, r, bs)
      
      # solve for the required parameters
      for (n, r, bs) in solve(37225, 68, 70):
        printf("steps = {n} [reading={r}, bases={bs}]")
      

      Solution: The second distance was 10080 steps.

      The units digit skips 9.

      The tens digit operates normally.

      The hundreds digit skips 9.

      The thousands digit skips 8 and 9.

      The ten thousands digit must be able to read as far as 3 correctly (in order for the initial reading to be 37225).

      So, initially, 25313 steps gives a reading of 37225.

      25313 = (((3 × 8) + 7) × 9 + 2) × 10 + 2) × 9 + 5

      and:

      25313 = 0.68 × 37225

      And then a value of 10080 steps gives a reading of 14400.

      10080 = (((1 × 8) + 4) × 9 + 4) × 10 + 0) × 9 + 0

      and:

      10080 = 0.70 × 14400

      Like

  • Unknown's avatar

    Jim Randell 12:50 pm on 5 November 2019 Permalink | Reply
    Tags: , ,   

    Brain-Teaser 508: [Family birthdays] 

    From The Sunday Times, 7th March 1971 [link]

    Grandma said: “Grandpa and I share between 100 and 120 years of age, being both over 50 years old. The total ages of our family (our son and his children — all of different age years, and over 12 months) exactly equal [my own age. On the same day next year they will exactly equal] Grandpa’s age.”

    “At present, Grandpa’s age is divisible by the age of only one child, but in one year’s time it will be divisible by the separate ages of three children, and also by the number in our family, while my own age will be divisible by the age of one child only (unity always excluded).”

    “During the year ahead on just one day in May, Grandpa’s age will be divisible by that of our son, whose birthday is earlier.”

    “From Grandpa downwards, what are our ages?”

    The following apology was published with Brain-Teaser 511:

    It is regretted that by a printing error the following words were omitted after “exactly equal”: “my own age. On the same day next year they will exactly equal”.

    I have inserted the missing text in the puzzle above.

    This puzzle was originally published with no title.

    [teaser508]

     
    • Jim Randell's avatar

      Jim Randell 2:22 pm on 5 November 2019 Permalink | Reply

      I wrote a program to solve the puzzle as originally stated (without the additional text) and found 15 solutions.

      So I created a MiniZinc model that makes it easy to play around with variations on the puzzle.

      When the constraints arising from the missing text are incorporated we get a single solution.

      %#! minizinc -a
      
      % grandpa and grandma are aged between 51 and 69
      var 51..69: gp;
      var 51..69: gm;
      
      % but the total is not more than 120
      constraint not(gp + gm > 120);
      
      % son's age
      var 18..51: son;
      
      % kids ages (0 for no kid)
      array[1..10] of var 0..16: kids;
      
      % kids are descending order, and all different
      constraint forall (i in 2..10) (kids[i] > 0 -> kids[i - 1] > kids[i]);
      
      % son + total of kids ages = gm [originally was = gp]
      constraint son + sum(kids) = gm;
      
      % son + total of kids ages in 1 year = gp, so: gp - gm = number of children
      % [originally this condition was omitted]
      constraint sum (i in 1..10) (kids[i] > 0) = gp - gm;
      
      % exactly 1 kid divides gp
      constraint sum (i in 1..10) (kids[i] > 1 /\ gp mod kids[i] = 0) = 1;
      
      % but exactly 3 kids divide gp in 1 year
      constraint sum (i in 1..10) (kids[i] > 0 /\ (gp + 1) mod (kids[i] + 1) = 0) = 3;
      
      % and exactly 1 kid divides gm in 1 year
      constraint sum (i in 1..10) (kids[i] > 0 /\ (gm + 1) mod (kids[i] + 1) = 0) = 1;
      
      % also the number of kids + 1 divides into gp in 1 year
      constraint (gp + 1) mod (1 + sum (i in 1..10) (kids[i] > 0)) = 0;
      
      % (son + 1) divides gp
      constraint gp mod (son + 1) = 0;
      
      % at least 16 years between generations
      constraint son + 16 < min([gp, gm]);
      constraint max(kids) + 16 < son;
      
      solve satisfy;
      

      And we can format the output with Python 3 by using the minizinc.py wrapper:

      from minizinc import MiniZinc
      
      for (gp, gm, son, kids) in MiniZinc("teaser508.mzn").solve(result="gp gm son kids"):
        kids = list(x for x in kids.values() if x > 0)
        print(f"Grandpa = {gp}; Grandma = {gm}; Son = {son}; Children = {kids}")
      

      So the answer to the corrected puzzle is:

      Solution: Grandpa = 62; Grandma = 56; Son = 30; Children = 8, 6, 5, 4, 2, 1.

      Without the constraints to enforce a 16 year gap between generations we can have a further solution (with 7 children):

      Grandpa = 63; Grandma = 56; Son = 20; Children = 15, 6, 5, 4, 3, 2, 1

      Like

  • Unknown's avatar

    Jim Randell 2:46 pm on 4 November 2019 Permalink | Reply
    Tags:   

    Teaser 2790: Factoidels 

    From The Sunday Times, 13th March 2016 [link] [link]

    In order to introduce the class to “lowest common denominators”,  Amelia’s teacher asked them to find the lowest number with the property that each of 1, 2, 3, 4 and 5 divided exactly into it. They correctly calculated the answer as 60, and the teacher called this the “factoidel” of 5. He went on to demonstrate that the factoidel of 6 was also 60. Being keen, Amelia investigated factoidels at home and then told the teacher that she had found the highest set of four consecutive numbers whose factoidels are all different (at least she had cleverly checked well into the billions).

    What were those four consecutive numbers?

    [teaser2790]

     
    • Jim Randell's avatar

      Jim Randell 2:47 pm on 4 November 2019 Permalink | Reply

      It’s easy enough to write a program that looks for sequences of 4 consecutive distinct “factoidels”. And this finds the supposed “largest” solution very quickly, but how do we know when to stop looking?

      The sequence of “factoidels” is:

      A[n] = lcm(1, 2, …, n)

      If we consider the ratio of consecutive terms:

      B[n] = A[n] / A[n − 1]
      where n > 1

      then:

      B[n] = p, if n = p^k for some prime p and integer k
      B[n] = 1, otherwise

      (See OEIS A014963) [ @oeis.org ]

      To find 4 consecutive terms in A[] that are all different, we need to find 3 consecutive terms in B[] that are non-unity. i.e. three consecutive integers that are powers of primes.

      So suppose the numbers we are looking for are: (n, n + 1, n + 2).

      If n is even, i.e. n = 2^k, then (n + 2) is also even, and must be a power of two:

      n + 2 = 2^k + 2 = 2^(k + 1)

      The only solution is k = 1, (n, n + 1, n + 2) = (2, 3, 4), which give the following terms from the sequences:

      B[2] = 2, B[3] = 3, B[4] = 2
      A[1] = 1, A[2] = 2, A[3] = 6, A[4] = 12

      If n is odd, then n + 1 = 2^k.

      k = 1 gives no sequence in B[].

      If k = 2 we have (n, n + 1, n + 2) = (3, 4, 5), which gives the following terms:

      B[3] = 3, B[4] = 2, B[5] = 5
      A[2] = 2, A[3] = 6, A[4] = 12, A[5] = 60

      If k =3 we have (n, n + 1, n + 2) = (7, 8, 9), which gives the following terms:

      B[7] = 7, B[8] = 2, B[9] = 3
      A[6] = 60, A[7] = 420, A[8] = 840, A[9] = 2520

      In general for k ≥ 4 we have (n, n + 1, n + 2) = (2^k – 1, 2^k, 2^k + 1).

      Catalan’s Conjecture (now a proved theorem) [ @wikipedia.org ] tells us that the only two consecutive numbers that are non-trivial powers are 8 and 9, so there is only a solution if (2^k − 1) and (2^k + 1) are both primes (otherwise we have another consecutive pair of non-trivial powers).

      But all twin primes apart from (3, 5) are of the form (6n − 1, 6n + 1), so we won’t find a larger solution no matter how far we go.

      Hence the three solutions found above are the only solutions, and it is sufficient to check up to n = 9

      Run: [ @replit ]

      from enigma import (irange, lcm, printf)
      
      (f, s) = (1, [])
      for n in irange(1, 9):
        f = lcm(f, n)
        s.append(f)
        if len(s) > 4: s.pop(0)
        if len(set(s)) == 4:
          printf("{ns} -> {s}", ns=tuple(irange(n - 3, n)))
      

      Solution: The four consecutive numbers are: 6, 7, 8, 9.

      Even without all the maths, computationally, with a simple prime test, we can very quickly (less than a second) verify that there are no twin primes of the form (2^k ± 1) for 2 < k ≤ 50. And this is certainly enough to replicate Amelia’s checks.

      Like

  • Unknown's avatar

    Jim Randell 4:32 pm on 1 November 2019 Permalink | Reply
    Tags: ,   

    Teaser 2980: Egyptian weights and measures 

    From The Sunday Times, 3rd November 2019 [link] [link]

    We were wondering why ancient Egyptians chose to represent arbitrary fractions as sums of distinct unit fractions of the form 1/n (thus 5/7 = 1/2 + 1/5 + 1/70). One of us recalled long ago watching our greengrocer use four brass weights of 1/2, 1/4, 1/8, 1/16 lb to weigh any number of ounces up to 15 by stacking some of them on one side of her balancing scales. We wanted to make a metric equivalent, a set of distinct weights of unit fractions of a kilo, each weighing a whole number of grams, to weigh in 10g steps up to 990g.

    Naturally, we wanted to use as little brass as possible, but we found that there were several possible such sets. Of these, we chose the set containing the fewest weights.

    List, in increasing order, the weights in our set.

    [teaser2980]

     
    • Jim Randell's avatar

      Jim Randell 5:03 pm on 1 November 2019 Permalink | Reply

      Possible weights are the divisors of 1000.

      This Python program looks for subsets that permit all the required weights to be made, and then selects those sets with the minimal possible weight. It runs in 501ms.

      Run: [ @repl.it ]

      from enigma import (divisors, inf, subsets, printf)
      
      # find weights that are unit fractions of 1000
      weights = divisors(1000)
      weights.remove(1000)
      
      # find minimal weight sets of weights that can be used to weigh all
      # multiples of 10 from 10 to 990
      (w, rs) = (inf, list())
      
      # choose a set of weights
      for ws in subsets(weights, min_size=7):
        k = sum(ws)
        if k < 990 or k > w: continue
      
        # collect amounts
        amounts = set()
        # choose a subset of the weights to use
        for s in subsets(ws, min_size=1):
          # calculate the total weight
          t = sum(s)
          # is it a multiple of 10 between 10 and 990?
          if 9 < t < 991 and t % 10 == 0:
            amounts.add(t)
      
        # can we make 99 different amounts?
        if len(amounts) == 99:
          if k < w: (w, rs) = (k, list())
          rs.append(ws)
          printf("[{k} = {ws} ({n})]", n=len(ws))
      
      # and find the minimum number weights in the set
      n = min(len(ws) for ws in rs)
      
      # output solutions
      for ws in rs:
        if len(ws) == n:
          printf("min = {w}; set of {n} weights = {ws}")
      

      Solution: There are 10 weights in the set: 2g, 5g, 8g, 10g, 25g, 40g, 50g, 100g, 250g, 500g.

      The total weight of the set is 990g (which is obviously the minimum possible total to be able to weigh up to 990g).

      There are 4 viable sets of weights that have a total weight of 990g:

      set of 10 weights = (2, 5, 8, 10, 25, 40, 50, 100, 250, 500)
      set of 11 weights = (1, 2, 4, 8, 10, 25, 40, 50, 100, 250, 500)
      set of 12 weights = (1, 2, 4, 5, 8, 10, 20, 40, 50, 100, 250, 500)
      set of 13 weights = (1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 125, 200, 500)


      When I initially read the puzzle I solved it allowing weights to be placed on both sides of the scales, and I found two sets of 7 weights which can be used to achieve the required values, where both sets weigh 990g in total:

      set of 7 weights = (5, 20, 40, 50, 125, 250, 500)
      set of 7 weights = (5, 20, 40, 100, 125, 200, 500)

      Like

  • Unknown's avatar

    Jim Randell 10:08 am on 31 October 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 507: [Colourful buses] 

    From The Sunday Times, 28th February 1971 [link]

    The Colourful Bus Company serves the five towns of Antwich, Bearton, Catville, Dogchester and Eagleham. The towns are joined by seven roads as shown on the map.

    Each bus of the company is either Red or Blue or Green or Yellow. Each of the seven roads is covered in one direction only and by one colour of bus only.

    I spent three days recently riding round the district on the buses, each day staring at Antwich and finishing at Eagleham. My journeys were on the following buses in order:

    Day 1: Red, Blue, Yellow, Blue, Green, Yellow, ???
    Day 2: Green, Red, Blue, Green, Red, Blue, Red, ???
    Day 3: Green, Yellow, Blue, Yellow, Blue, Red, ???

    I forgot to note the colour of the last bus each day.

    If a bus runs from Antwich to Bearton, what is the direction and colour of the bus between Catville and Dogchester?

    This puzzle was originally published with no title.

    [teaser507]

     
    • Jim Randell's avatar

      Jim Randell 10:10 am on 31 October 2019 Permalink | Reply

      This Python 3 program runs in 77ms.

      Run: [ @repl.it ]

      from enigma import update, join, printf
      
      # the towns, the buses, the roads
      towns = (A, B, C, D, E) = list("ABCDE")
      buses = "rbgy" 
      roads = {
        A: (B, C, D),
        B: (A, C, E),
        C: (A, B, D),
        D: (A, C, E),
        E: (B, D),
      }
      
      # follow a journey
      # bs = buses remaining to take
      # p = current location
      # t = target location
      # m = current map
      # return (map, towns)
      def journey(bs, p, t, m, s=None):
        if s is None: s = [p]
        # are we done?
        if not bs:
          # can we reach the target in one hop?
          if t in roads[p]:
            # is the road already on the map
            if (p, t) in m:
              yield (m, s + [t])
            # can we add it to the map?
            elif (t, p) not in m:
              yield (update(m, [(p, t)], ['?']), s + [t])
        else:
          # choose an onward road
          for x in roads[p]:
            # is it already in the map?
            if (p, x) in m:
              # and is it the right colour?
              if m[(p, x)] == bs[0]:
                # solve for the remaining buses
                yield from journey(bs[1:], x, t, m, s + [x])
              elif m[(p, x)] == '?':
                yield from journey(bs[1:], x, t, update(m, [(p, x)], [bs[0]]), s + [x])
            # can we add it to the map?
            elif (x, p) not in m:
              # solve for the remaining buses
              yield from journey(bs[1:], x, t, update(m, [(p, x)], [bs[0]]), s + [x])
      
      # solve for a collection of journeys between s and t
      def solve(s, t, js, m={}, ss=[]):
        # are we done?
        if not js:
          yield (m, ss)
        else:
          # solve for the first journey
          for (m1, s1) in journey(js[0], s, t, m):
            # and then for the remaining journeys
            yield from solve(s, t, js[1:], m1, ss + [s1])
      
      # solve the journeys
      for (m, ss) in solve(A, E, ["rbybgy", "grbgrbr", "gybybr"], { (A, B): '?' }):
        # output solution
        printf("map: {m}", m=join((join((k[0], '->', k[1], ' = ', v)) for (k, v) in m.items()), sep="; "))
        for (i, s) in enumerate(ss, start=1):
          printf("day {i}: {s}", s=join(s, sep=" -> "))
        printf()
      

      Solution: The bus that runs from Catville to Dogchester is red.

      The bus routes are as shown in the following diagram:

      The journeys are:

      Day 1: A → B → E → D → A → C → B → E
      Day 2: A → C → D → A → C → D → A → B → E
      Day 3: A → C → B → E → D → A → B → E

      This is the only solution with a bus running from A to B. However if the bus ran from B to A the solution would be:

      A → C = green
      A → D = red
      B → A = blue
      C → B = red
      C → D = yellow
      D → E = blue
      E → B = yellow

      Like

  • Unknown's avatar

    Jim Randell 9:15 pm on 30 October 2019 Permalink | Reply
    Tags:   

    Teaser 2817: Our secret 

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

    Eight friends joined a very secret society and each was given a different membership number between 70 and 100. The friends are Bec, Cal, Doc, Ian, Jon, Luv, Rev and one other – and I am not willing to divulge his common three-letter name. But I can tell you that his membership number is 84. Also, Bec’s membership number is the highest of the eight. The friends noticed that, for any pair of them, their names had at least one letter in common if and only if their membership numbers had a common prime factor.

    (a) What was Ian’s membership number?
    (b) What was the name of the eighth friend?

    [teaser2817]

     
    • Jim Randell's avatar

      Jim Randell 9:17 pm on 30 October 2019 Permalink | Reply

      The following recursive Python 3 program finds two sets of three letters that may be combined to form a “common three-letter” name. But only one of them really works.

      It runs in 148ms. (Internal runtime is 80ms).

      from enigma import (irange, factor, join, update, subsets, cache, printf)
      
      factor = cache(factor)
      
      # membership numbers are allocated such that membership numbers share
      # factors exactly when the corresponding members names share letters
      
      # number for the mystery man
      xxx = 84
      
      # check if <name>, <number> can be added to members in dict <d>
      def check(name, number, d):
        ls = set(name)
        fs = set(factor(number))
        return all(ls.isdisjoint(k) == fs.isdisjoint(factor(v)) for (k, v) in d.items())
      
      # allocate numbers from <numbers> to the names in <names>
      # (the assignment is stored in dictionary <d>)
      def allocate(names, numbers, d):
        # are we done?
        if not names:
          yield d
        else:
          # consider the next name
          for (i, n) in enumerate(numbers):
            # check names share letters only when they share factors
            if check(names[0], n, d):
              yield from allocate(names[1:], numbers[:i] + numbers[i + 1:], update(d, [(names[0], n)]))
      
      # choose a number for Bec (the highest number)
      for b in irange(85, 100):
      
        # remaining allowable numbers
        ns = list(irange(70, b - 1))
        ns.remove(xxx)
      
        # allocate numbers for the remaining names
        for d in allocate(['cal', 'doc', 'ian', 'jon', 'luv', 'rev'], ns, { 'bec': b }):
      
          # output the allocations
          printf("{d}", d=join((join((k, '=', v)) for (k, v) in sorted(d.items(), key=lambda x: x[::-1])), sep=' '))
      
          # find (up to) three letters that form xxx's name
          for ls in subsets(set(join(d.keys())), min_size=1, max_size=3):
            if check(ls, xxx, d):
              ls = join(sorted(ls)).ljust(3, '?')
              names = set(join(s) for s in subsets(ls, size=len, select="mP"))
              printf("letters = {ls} -> names = {names}", names=join(sorted(names), sep=', '))
      

      Solution: (a) Ian’s membership number is 76. (b) The eighth friend is named Vic.

      The solutions found by the program are “civ” and “acv”. The only rearrangement that gives a common three-letter name is “Vic” (which as the setter is Victor Bryant, is probably what is wanted as the answer). Although one could argue that “Cav” or “Vac” are no worse than the names used for other members.

      So the membership numbers are:

      Doc = 75
      Ian = 76
      Rev = 77
      Cal = 78
      Vic = 84
      Luv = 91
      Jon = 95
      Bec = 99

      Shared letters and factors are (each pair only shown once):

      Doc with: Cal (C, 3); Vic (C, 3); Jon (O, 5); Bec (C, 3)
      Ian with: Cal (A, 2); Vic (I, 4); Jon (N, 19)
      Rev with: Vic (V, 7); Luv (V, 7); Bec (E, 11)
      Cal with: Vic (C, 6); Luv (L, 13); Bec (C, 3)
      Vic with: Luv (V, 7); Bec (C, 3)

      However if we were to use “Vac” (or “Cav”) instead of Vic, we would have the following pairings for Vac:

      Vac with: Doc (C, 3); Ian (A, 4); Rev (V, 7); Cal (AC, 6); Luv (V, 7); Bec (C, 3)

      Vac and Cal give us the only pairing that shares more than 1 letter, so if this had been disallowed we would know for certain that the three letters that make up the name of the eighth friend were “civ”.


      This posting completes my notes for all Sunday Times Teaser puzzles that I have previously posted to replit [ @replit ] at the time of publication.

      I do however have more code for puzzles that I didn’t post, so there may well be more to come.

      Like

  • Unknown's avatar

    Jim Randell 8:00 pm on 25 October 2019 Permalink | Reply
    Tags:   

    Teaser 2979: Mischievous Sam 

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

    I set Sam a question, the answer to which was a 3-digit number, with the digits increasing by 1 from first to last (e.g. 789).

    Sam eventually produced a 3-digit answer, but only 2 of his digits were correct and in the correct position. The third digit was wrong.

    Investigating further I found that Sam had the correct answer but, for devilment, decided to change it into a different (single-digit) base.

    If I were to tell you which of his 3 digits was the wrong one, you should be able to tell me:

    (a) the correct answer, and;
    (b) the base used by Sam.

    What are they?

    [teaser2979]

     
    • Jim Randell's avatar

      Jim Randell 10:36 pm on 25 October 2019 Permalink | Reply

      There are only 7 possible 3-digit decimal results, and each of these can only be expressed as a 3-digit number in a limited number of single digit integer bases, so the search space is very small.

      The following Python program runs in 82ms.

      Run: [ @repl.it ]

      from enigma import (defaultdict, irange, tuples, nconcat, cbrt, intc, nsplit, int2base, printf)
      
      # record numbers by the position of the incorrect digit
      r = defaultdict(list)
      
      # consider sequences of 3 consecutive increasing digits
      for ds in tuples(irange(1, 9), 3):
        n = nconcat(ds)
      
        # consider the digits of n in other bases
        for b in irange(intc(cbrt(n)), 9):
          bs = nsplit(n, base=b)
      
          # find the positions of digits that differ
          ps = list(i for (i, (b, d)) in enumerate(zip(bs, ds)) if b != d)
          if len(ps) != 1: continue
      
          printf("[{n} = {m} (base {b}) -> {ps}]", m=int2base(n, b))
          r[ps[0]].append((n, b))
      
      # find unique solutions
      for (k, vs) in r.items():
        if len(vs) != 1: continue
        # output solution
        (n, b) = vs[0]
        printf("incorrect {k} -> {n} = {m} (base {b})", m=int2base(n, b))
      

      Solution: (a) The correct answer is 123; (b) Sam used base 8.

      The only possible solutions are:

      123 → 323 (base 6)
      123 → 173 (base 8)
      456 → 556 (base 9)

      The first and third of these differ from the decimal representation in the first (most significant) digit. Which leaves the second (which differs in the second digit) as the solution.

      If we allow bases higher than 9 we find there is one further potential candidate solution:

      789 → 489 (base 13)

      But this differs from the decimal representation in the first digit, so would not change the answer to the puzzle.

      Like

    • GeoffR's avatar

      GeoffR 7:54 pm on 28 October 2019 Permalink | Reply

      I used a number base calculator to give an assisted manual solution.

      Link: https://www.rapidtables.com/convert/number/base-converter.html?x=456&sel1=10&sel2=9

      
      # Teaser 2979
      
      # Number base calculator gives this table
      # which includes one value with two digits the same
      #
      # Base10  Base4  Base5  Base6  Base7  Base8  Base9
      # ------------------------------------------------
      # 123     n/a    443    323    234    173    146
      #-----------------------------------------------
      # 234     n/a    n/a    n/a    453    352    280
      #-----------------------------------------------
      # 345     n/a    n/a    n/a    n/a    531    423
      #-----------------------------------------------
      # 456     n/a    n/a    n/a    n/a    710    556
      #-----------------------------------------------
      # 567     n/a    n/a    n/a    n/a    n/a    700
      #-----------------------------------------------
      # 678     n/a    n/a    n/a    n/a    n/a    833
      #-----------------------------------------------
      # 789     n/a    n/a    n/a    n/a    n/a    n/a
      #-----------------------------------------------
      
      # n/a means 3-digit value not available for given base
      
      

      Like

    • GeoffR's avatar

      GeoffR 11:34 am on 3 November 2019 Permalink | Reply

      
      from time import perf_counter_ns
      start = perf_counter_ns()
      
      def nbr_to_base(num, base):
        dgts = []
        while num:
          num, r = divmod(num, base)
          dgts.append(r)
        return ''.join(str(x) for x in reversed(dgts))
      
      def diff_pos(n1, n2):
        # Split each of two numbers into three digits
        a, b, c = n1 // 100, n1 // 10 % 10, n1 % 10
        d, e, f = n2 // 100, n2 // 10 % 10, n2 % 10
      
        # check 2 of the 3 digits are in correct position
        # and only one digit is in the wrong position in Sam's number
       
        # check if 1st and 2nd digits are in correct position
        if a == d and b == e and c != f:
          return 3
       
        # check if 1st and 3rd digits are in correct position
        if a == d and c == f and b != e:
          return 2
       
        # check if 2nd and 3rd digits are in correct position
        if b == e and c == f and  a != d:
          return 1
        
        return 0
      
      # 3-digit numbers with digits increasing by 1 from first to last
      for num in (123, 234, 345, 456, 567, 678, 789):
        for nb in range(4, 10):
          N = nbr_to_base(num, nb)
          if 100 < int(N) < 1000:
            dp = diff_pos(num, int(N))
            if dp:      
              print(f"Answer {num} ({N} in base {nb}) differs in position {dp}", end='')
              print(f" (in {(perf_counter_ns() - start) / 1e6:.3f} milliseconds)")
      
      # Answer 123 (323 in base 6) differs in position 1 (in 8.936 milliseconds)
      # Answer 123 (173 in base 8) differs in position 2 (in 16.619 milliseconds)
      # Answer 456 (556 in base 9) differs in position 1 (in 35.507 milliseconds)
      
      
      
      

      The first and third solutions both give the incorrect digit as the first position, so we cannot tell the incorrect digit from these two solutions.

      The second solution gives a single position for the incorrect digit position, so this is the answer i.e Answer 123 (173 in base 8) differs in position 2.

      Like

    • Frits's avatar

      Frits 12:05 pm on 8 April 2021 Permalink | Reply

      I wanted to test the filter_unique function with a complex lambda function otherwise Jim’s line 16 approach could have been used. The “correct” function is taken from Mastermind puzzles.

      # calculate the number of dead-right's
      correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys))
      
      # ceil a positive number
      ceil = lambda n: int(n) if n == int(n) else int(n) + 1
          
      # convert from integer to any base number            
      def int2base(n, b, D="0123456789abcdefghijklmnopqrstuvwxyz"):
        return D[0] if not n else (int2base(n // b, b)).lstrip(D[0]) + D[n % b]
       
      # return all entries where f(x) occurs only once
      #                 or where f(x) occurs more than once
      def filter_unique(seq, f=lambda x: x, mode="unique"):
        d = dict()
        for s in seq:
          d[f(s)] = f(s) in d
      
        if mode == "unique":      # occuring only once
          return [s for s in seq if not d[f(s)]]
        else:                     # occuring more than once
          return [s for s in seq if d[f(s)]]
            
      li = [] 
      # 3-digit numbers with digits increasing by 1 from first to last
      for n in (123, 234, 345, 456, 567, 678, 789):
        # limit the used bases so they result in a 3 digit number
        for b in range(ceil(n ** (1 / 3)), int(n ** 0.5) + 1):
          N = int2base(n, b)
         
          # Sam eventually produced a 3-digit answer
          if not N.isdigit(): continue
         
          # if 2 digits are correct the other must be incorrect
          if correct(str(n), N) == 2:      
            li.append((str(n), N, b))
      
      f = filter_unique(li, lambda s: [i for i, x in enumerate(s[0]) 
                                                if x not in s[1]][0])
                                
      if len(f) == 1:
        print(f"correct answer = {f[0][0]}, Sam's base = {f[0][-1]}")
      

      Like

  • Unknown's avatar

    Jim Randell 8:20 am on 25 October 2019 Permalink | Reply
    Tags:   

    Teaser 2835: Jeweller’s rouge 

    From The Sunday Times, 22nd January 2017 [link] [link]

    Fabulé’s latest creation consists of a set of equal-sized silver cubes. On each face of each cube there is one diagonal of identical rubies. No two cubes are the same, but had Fabulé made any more such cubes then it would have been necessary to repeat one of the designs.

    How many cubes are there in the set?

    [teaser2835]

     
    • Jim Randell's avatar

      Jim Randell 8:20 am on 25 October 2019 Permalink | Reply

      First I created a generic class to describe the rotations of the cube.

      There are 24 rotational positions of the cube (any of the 6 faces can be selected to be the “U” face, and then any of the 4 sides can be selected to be the “F” face; 6 × 4 = 24). We can identify the rotation by the faces occupying the “U” and “F” positions.

      In the code below we recorded the position and orientation of each face. Once we have determined the U, L, F rotations the remaining 21 can be calculated by combine these. But the code below uses pre-computed values for convenience.

      # labels for the faces
      (U, D, L, R, F, B) = (0, 1, 2, 3, 4, 5)
      
      # the 24 rotational positions of the cube
      # we record the positions of the faces
      # and their orientations (in clockwise quarter turns)
      _rotations = (
        # the first 6 match up with a rotation of the face U, D, L, R, F, B
        # faces               orientations
        # U  D  L  R  F  B    U  D  L  R  F  B      U F
        ((U, D, F, B, R, L), (1, 3, 0, 0, 0, 0)), # U R = rotate U
        ((U, D, B, F, L, R), (3, 1, 0, 0, 0, 0)), # U L = rotate D = rotate UUU
        ((B, F, L, R, U, D), (2, 0, 1, 3, 0, 2)), # B U = rotate L
        ((F, B, L, R, D, U), (0, 2, 3, 1, 0, 2)), # F D = rotate R = rotate LLL
        ((L, R, D, U, F, B), (1, 1, 1, 1, 1, 3)), # L F = rotate F
        ((R, L, U, D, F, B), (3, 3, 3, 3, 3, 1)), # R F = rotate B = rotate FFF
        # the remaining rotations can be derived from those above
        ((U, D, L, R, F, B), (0, 0, 0, 0, 0, 0)), # U F = rotate []
        ((U, D, R, L, B, F), (2, 2, 0, 0, 0, 0)), # U B = rotate UU
        ((D, U, R, L, F, B), (2, 2, 2, 2, 2, 2)), # D F = rotate FF
        ((D, U, L, R, B, F), (0, 0, 2, 2, 2, 2)), # D B = rotate LL
        ((D, U, F, B, L, R), (3, 1, 2, 2, 2, 2)), # D L = rotate ULL
        ((D, U, B, F, R, L), (1, 3, 2, 2, 2, 2)), # D R = rotate LLU
        ((L, R, F, B, U, D), (2, 0, 1, 3, 1, 1)), # L U = rotate FU
        ((L, R, B, F, D, U), (0, 2, 3, 1, 1, 1)), # L D = rotate FD
        ((L, R, U, D, B, F), (3, 3, 1, 1, 3, 1)), # L B = rotate FUU
        ((R, L, B, F, U, D), (2, 0, 1, 3, 3, 3)), # R U = rotate BD
        ((R, L, F, B, D, U), (0, 2, 3, 1, 3, 3)), # R D = rotate BU
        ((R, L, D, U, B, F), (1, 1, 3, 3, 1, 3)), # R B = rotate BUU
        ((F, B, R, L, U, D), (2, 0, 1, 3, 2, 0)), # F U = rotate RUU
        ((F, B, U, D, L, R), (3, 3, 2, 0, 3, 1)), # F L = rotate RD
        ((F, B, D, U, R, L), (1, 1, 0, 2, 1, 3)), # F R = rotate RU
        ((B, F, R, L, D, U), (0, 2, 3, 1, 2, 0)), # B D = rotate LUU
        ((B, F, D, U, L, R), (1, 1, 2, 0, 1, 3)), # B L = rotate LD
        ((B, F, U, D, R, L), (3, 3, 0, 2, 3, 1)), # B R = rotate LU
      )
      
      # a class representing the rotations of the cube
      class Cube(object):
      
        def __init__(self, faces=(U, D, L, R, F, B), orientations=(0, 0, 0, 0, 0, 0)):
          self.faces = tuple(faces)
          self.orientations = tuple(orientations)
      
        # a new cube derived from the old one by applying the specified transformation
        def transform(self, faces, orientations):
          return Cube(
            faces=tuple(self.faces[i] for i in faces),
            orientations=tuple((self.orientations[i] + r) % 4 for (i, r) in zip(faces, orientations)),
          )
      
        # generate all rotations of the cube
        def rotations(self):
          for (faces, orientations) in _rotations:
            yield self.transform(faces, orientations)
      

      We can then use the following short Python program to find distinct patterns of diagonals under rotation. It runs in 97ms

      Run: [ @codepad ] [ @replit ]

      from enigma import (subsets, printf)
      
      # a canonical representation of a pattern of diagonals
      # - faces cannot be distinguished
      # - diagonals are the same when rotated 180 degrees
      # so we only need to consider the parity of the orientations
      def canonical(s):
        cube = Cube(orientations=s)
        return min(tuple(x % 2 for x in r.orientations) for r in cube.rotations())
      
      # consider possible diagonals on the faces (we only need 0 and 1)
      patterns = set(canonical(s) for s in subsets((0, 1), size=6, select="M"))
      
      printf("{n} patterns", n=len(patterns))
      

      Solution: There are 8 cubes in the set.

      Here’s my “back of an envelope” sketch of the 8 distinct patterns:

      Like

  • Unknown's avatar

    Jim Randell 3:23 pm on 24 October 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 506: [The sword of Nyarlathotep] 

    From The Sunday Times, 21st February 1971 [link]

    Students of the “Necronomicon” will be pleased to learn that sheet 73-74, missing from the copy at Miskatonic University, has come to light in the manuscript recently bought by the British Museum. On it the mad Arab, Abdul al Hazrad, has written:

    “Cthulhu na fa’t’agn!

    To fashion the sword of Nyarlathotep, take of fair metals for the hilt and the guard and the blade, enough.

    The hilt shall be copper, unless the guard be silver in which case the blade shall be copper unless the hilt is gold.

    The guard shall be copper unless the blade be gold in which case the guard shall be silver unless the hilt is silver.

    The blade shall be gold unless the guard be gold in which case the blade shall be silver unless the hilt is copper.

    Fashion then the sword of Nyarlathotep.”

    Thus: Hilt = ?, Guard = ?, Blade = ?

    This puzzle was originally published with no title.

    [teaser506]

     
    • Jim Randell's avatar

      Jim Randell 3:24 pm on 24 October 2019 Permalink | Reply

      The conditions are expressed in a similar manner, so once the pattern of the text is determined it is easy to express the constraints.

      This Python program examines all the possibilities for the construction. It runs in 81ms.

      from enigma import (subsets, printf)
      
      # test: "A unless X (in which case B)"
      test = lambda X, A, B=True: (B if X else A)
      
      # available metals
      metals = (Cu, Ag, Au) = ("Cu", "Ag", "Au")
      
      # choose metals for the Blade, Hilt, Guard
      for (B, H, G) in subsets(metals, size=3, select="M"):
      
        # "the hilt shall be copper, unless the guard be silver in which
        # case the blade shall be copper unless the hilt is gold"
        if not test(G == Ag, H == Cu, test(H == Au, B == Cu)): continue
      
        # "the guard shall be copper unless the blade be gold in which
        # case the guard shall be silver unless the hilt is silver"
        if not test(B == Au, G == Cu, test(H == Ag, G == Ag)): continue
      
        # "the blade shall be gold unless the guard be gold in which case
        # the blade shall be silver unless the hilt is copper"
        if not test(G == Au, B == Au, test(H == Cu, B == Ag)): continue
      
        # output solution
        printf("B={B} H={H} G={G}")
      

      Solution: The blade is gold. The hilt is gold. The guard is silver.

      Like

      • John Crabtree's avatar

        John Crabtree 4:28 am on 27 October 2019 Permalink | Reply

        “unless” means that one condition or the other is true, but not both.
        Statements 2 and 3 give that the blade must be gold.
        Statement 2 simplifies to the the guard shall be silver unless the hilt is silver.
        The statements 1 and 2 give that the guard must be silver and the blade must be gold

        Like

        • John Crabtree's avatar

          John Crabtree 4:04 pm on 6 November 2019 Permalink | Reply

          An expanded and slightly corrected solution:

          “Unless” means one or the other, but not both.
          Putting statements 2 and 3 into Boolean form, where “.” means “AND”, “~” means “NOT”, and “+” means “OR”
          Statement 2. ~Bg.Gc + Bg.Gs.~Hs + Bg.~Gc.~Gs.Hs = 1
          Statement 3. Bg.~Gg + ~Bg.Bs.Gg.~Hc + ~Bg.~Bs.Gg.Hc = 1
          Multiplying the two and removing terms which are impossible gives
          Bg.Gs.~Hs = 1
          And so the blade is gold and the guard is silver.
          Statement 1. ~Gs.Hc + Gs.Bc.~Hc.~Hg + Gs.~Bc.Hg = Gs.~Bc.Hg = Hg = 1
          And so the hilt is gold.

          Like

  • Unknown's avatar

    Jim Randell 10:49 am on 23 October 2019 Permalink | Reply
    Tags:   

    Teaser 2819: An age-old problem 

    From The Sunday Times, 2nd October 2016 [link] [link]

    Five men of different ages under fifty are celebrating their joint birthday in the pub. Alan’s age is the average of their five ages. When Bob is three times Alan’s age today, the sum of Alan’s and Colin’s ages will equal the sum of their five ages today. Furthermore, Derek will then be three times his age today, and Eric will be one year more than double Bob’s age today.

    The landlord checked that the youngest of the five was allowed to buy alcohol.

    Who is the youngest, and how old is he?

    [teaser2819]

     
    • Jim Randell's avatar

      Jim Randell 10:50 am on 23 October 2019 Permalink | Reply

      The conditions in the puzzle text give us 4 equations in 5 variables:

      4A − B − C − D − E = 0
      6A − 3B − D − E = 0
      3A − B − 2D = 0
      3A − 3B + E = 1

      If we set a value for one of the ages, then the other 4 ages are determined.

      We can then look for a set of integer ages, where only the youngest member of the party is asked for proof of age.

      I supposed the barman might ask anyone who looked 21 or younger for proof of age, and this gives a unique solution.

      This Python program uses the [[ matrix.linear() ]] solver from the enigma.py library to solve the equations. It runs in 148ms.

      Run: [ @repl.it ]

      from enigma import irange, matrix, as_int, arg, printf
      
      # the constraints give the following four equations:
      #
      #   4A -  B -  C -  D - E = 0
      #   6A - 3B      -  D - E = 0
      #   3A -  B      - 2D     = 0
      #   3A - 3B           + E = 1
      
      eqs = [
        # A   B   C   D   E
        [ 4, -1, -1, -1, -1 ], # = 0
        [ 6, -3,  0, -1, -1 ], # = 0
        [ 3, -1,  0, -2,  0 ], # = 0
        [ 3, -3,  0,  0,  1 ], # = 1
        [ 1,  0,  0,  0,  0 ], # = A
      ]
      
      # age range for the men (min, max)
      m = arg( 1, 0, int)
      M = arg(49, 1, int)
      printf("[ages: min = {m}, max = {M}]")
      
      # choose a value for A
      for a in irange(m, M):
      
        # solve the equations for integers
        try:
          ss = tuple(as_int(x) for x in matrix.linear(eqs, [0, 0, 0, 1, a]))
        except ValueError:
          continue
      
        # discard any solutions with ages not in the required range
        if any(x < m or x > M for x in ss): continue
      
        # count how many are 21 or under?
        n = sum(x < 22 for x in ss)
        if n != 1: continue
      
        # output the solution
        s = min(zip(ss, "ABCDE"))
        (A, B, C, D, E) = ss
        printf("youngest={s[1]} age={s[0]} [A={A} B={B} C={C} D={D} E={E}]")
      

      Solution: Colin is the youngest. He is 20.

      There are three sets of ages (between 1 and 49) that satisfy the equations:

      A=6 B=8 C=4 D=5 E=7 (min=4)
      A=17 B=23 C=12 D=14 E=19 (min=12)
      A=28 B=38 C=20 D=23 E=31 (min=20)

      In the first two sets more than one of the “men” is below the legal age to buy alcohol (18 in the UK), so the third set is the most likely answer. The youngest man is aged 20, and so could reasonably be asked for proof of age.

      If we were to restrict the minimum age to 18 only the final set is possible. (And this remains true if we take the minimum age down as far as 13).

      Like

    • GeoffR's avatar

      GeoffR 3:04 pm on 23 October 2019 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Alan, Bob, Colin, Derek, Eric
      var 1..49: A; var 1..49: B; var 1..49: C;
      var 1..49: D; var 1..49: E; 
      
      % Jim's four equations
      constraint 4*A - B - C - D - E == 0;
      constraint 6*A - 3*B - D - E == 0;
      constraint 3*A - B - 2*D == 0;
      constraint 3*A - 3*B + E  == 1;
      
      % Minimum age to buy alcohol = 18
      constraint min([A,B,C,D,E]) > 17;
      
      solve satisfy;
      
      output ["Alan = " ++ show(A) ++ ", Bob = " ++ show(B) ++ ", Colin = " ++ show(C) ++ 
      ", Derek = " ++ show(D) ++ ", Eric = " ++ show(E)] ++ 
      ["\nYoungest of five people = " ++ show(min([A,B,C,D,E])) ++ " years"]
      
      % Alan = 28, Bob = 38, Colin = 20, Derek = 23, Eric = 31
      % Youngest of five people = 20 years
      % ----------
      % ==========
      % Finished in 151msec
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:19 pm on 18 October 2019 Permalink | Reply
    Tags:   

    Teaser 2978: Norfolk Flats 

    From The Sunday Times, 20th October 2019 [link] [link]

    Sam has purchased Norfolk Flats; an area of farmland (less than 100 hectares) bordered by six straight fences. He intends to farm an area which is an equilateral triangle with corners that are the midpoints of three of the existing boundaries. This creates three more distinct areas (one for each of his sons); these areas are identical in shape and size and have two sides that are parallel.

    Sam measured the area (in square metres) which each son will farm and also his own area. One of the numbers is a square and the other a cube. If I told you which was which, you should be able to work out the area of Norfolk Flats.

    What (in square metres) is that area?

    [teaser2978]

     
    • Jim Randell's avatar

      Jim Randell 5:34 pm on 18 October 2019 Permalink | Reply

      I supposed that the plot of land in question was a regular hexagon.

      Then, if the hexagon has side x the area of Sam’s triangular enclosure is:

      sam = (9/16)x²√3

      and the area of the enclosure for each son is:

      son = (5/16)x²√3

      So we can say Sam’s enclosure is 9 area units and each son’s enclosure is 5 area units.

      Here is a visual representation of the situation:

      So, we need to look for squares and cubes where 5 times one of them is 9 times the other.

      This Python program runs in 83ms.

      Run: [ @replit ]

      from enigma import (irange, inf, div, is_square, printf)
      
      # max area (100 hectares = 1_000_000 square metres)
      M = 1000000
      
      # collect the areas for each case (sam, son, total)
      (case1, case2) = (list(), list())
      
      # consider values for the cube
      for x in irange(1, inf):
        cube = x ** 3
        if not (8 * cube < 3 * M): break
      
        # does 5 * square = 9 * cube ?
        square = div(9 * cube, 5)
        if square is not None and is_square(square):
          (sam, son) = (square, cube)
          area = sam + 3 * son
          if area < M:
            printf("[1] son = cube = {cube} -> sam = square = {square}, area = {area}")
            case1.append((sam, son, area))
      
        # does 9 * square = 5 * cube ?
        square = div(5 * cube, 9)
        if square is not None and is_square(square):
          (sam, son) = (cube, square)
          area = sam + 3 * son
          if area < M:
            printf("[2] sam = cube = {cube} -> son = square = {square}, area = {area}")
            case2.append((sam, son, area))
      
      # consider the cases
      for ss in (case1, case2):
        if len(ss) != 1: continue
        (sam, son, area) = ss[0]
        printf("area = {area} [sam = {sam}, son = {son}]")
      

      Solution: The total area of Norfolk Flats is 243,000 square metres (= 24.3 hectares).

      Sam’s enclosure is 91,125 square metres (= 45³).

      Each son’s enclosure is 50,625 square metres (= 225²).

      So each side of the hexagon is about 305.83 metres long.

      This is the only viable solution where the area of Sam’s enclosure is a perfect cube, and the area of each son’s enclosure is a perfect square. So it is the answer to the puzzle.

      If the area of Sam’s enclosure is a perfect square, and the area of each son’s enclosure is a perfect cube, we find there are three potential solutions (where the total area is below 1 million square metres):

      sam = 225 = 15²; son = 125 = 5³; area = 600
      sam = 14400 = 120²; son = 8000 = 20³; area = 38400
      sam = 164025 = 405²; son = 91125 = 45³; area = 437400

      So this case is ruled out as the answer.


      Manually:

      [1] In the case: 5a² = 9b³

      We see that 3 must divide a, and 5 must divide b. So:

      let: a = 3p, b = 5q
      → p² = 25q³

      So 5 must divide p:

      let: p = 5r
      → r² = q³

      So we are looking for numbers that are simultaneously squares and cubes, i.e. powers of 6.

      r² = q³ = k⁶
      r = k³, q = k²
      → a = 15k³, b = 5k²

      And we are interested in cases where:

      a² +3b³ < 1,000,000
      600 k⁶ < 1,000,000
      → k = 1, 2, 3

      So there are three possible solutions in this case (those given above).

      [2] In the case: 5a³ = 9b²

      We can similarly arrive at a solution of:

      a = 45k², b = 225k³

      And we require:

      a³ + 3b² < 1,000,000
      243,000 k⁶ < 1,000,000
      → k = 1

      So in this case there is a single solution, and from this we get the answer to the puzzle.

      Like

    • Robert Brown's avatar

      Robert Brown 10:39 pm on 18 October 2019 Permalink | Reply

      It doesn’t say the fences are the same length. Alternate short & long fences makes a diagram that fits the words in the text – only restriction I can see is that ‘son’ is < 'sam'. If this is true, I don't see a possible solution.

      Like

      • Jim Randell's avatar

        Jim Randell 11:03 pm on 18 October 2019 Permalink | Reply

        @Robert: I think if you don’t require the hexagon to be regular it is possible to find side lengths that will work for many different (square, cube) combinations, so it wouldn’t be possible to work out the total area being told which of the enclosures areas was a square which was a cube.

        But with a regular hexagon there are many fewer possibilities and we can find a unique solution, so it is probably what the setter had in mind (although not what the text of the puzzle explicitly states).

        Like

    • GeoffR's avatar

      GeoffR 3:30 pm on 22 October 2019 Permalink | Reply

      
      # This solution uses Jim's diagram i.e son/sam = 5/9 in area ratio
      # F + 3.(5/9).F < 1000000 -> F < 375000
      # Hence squares < 613, cubes < 73
      
      from collections import defaultdict
      
      # Two dictionaries to collect squares and cubes for each case
      # i.e 5 * square = 9 * cube or 9 * square = 5 * cube
      
      d1 = defaultdict(list)  # 5 * square = 9 * cube
      d2 = defaultdict(list)  # 9 * square = 5 * cube
      
      # F + 3.(5/9).F < 1000000 -> F < 375000
      # hence squares < 613, cubes < 73
      
      sq = [n * n for n in range(10, 613)]
      cb = [n * n * n for n in range(10, 73)]
      
      for x in sq:
        for y in cb:
          if 5 * x == 9 * y:    
            d1[x] += [(x, y)]  
          # Other solution     
          if 9 * x == 5 * y:
            d2[x] += [(x, y)] 
      
      # One of the two dictionaries must have a single solution
      for k,v in d1.items():
        if len(v) == 1 and len(d1) == 1:
          print(f"Norfolk Flats Area = {3 * v[0][0] + v[0][1]} sm")
          print(f"Son's area = {v[0][0]} sm, Sam's area = {v[0][1]} sm")
          
      for k,v in d2.items():
        if len(v) == 1 and len(d2) == 1:
          print(f"Norfolk Flats Area = {3 * v[0][0] + v[0][1]} sm")
          print(f"Son's area = {v[0][0]} sm, Sam's area = {v[0][1]} sm")
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:57 am on 18 October 2019 Permalink | Reply
    Tags:   

    Teaser 2821: Magic cards 

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

     Joe placed nine cards on the table to form the magic square shown on the left below (where each row, column and diagonal has the same total). Then he turned over each card one at a time and the result is shown on the right below: it is not magic.

    Penny then rearranged the cards to form a magic square which, after each card was turned over, was also magic.

    What (in increasing order) were the four corner numbers in her magic square with the higher total?

    [teaser2821]

     
    • Jim Randell's avatar

      Jim Randell 9:58 am on 18 October 2019 Permalink | Reply

      A magic square can be characterised using three variables: x, y, z as the sums:

      
      a = x+y       | b = x+z+z | c = x+y+y+z
      --------------+-----------+------------
      d = x+y+y+z+z | e = x+y+z | f = x
      --------------+-----------+------------
      g = x+z       | h = x+y+y | i = x+y+z+z
      
      

      Each row, column, and diagonal combine to give 3(x+y+z) = the magic constant.

      Given values for a, e, f we can derive: x = f, y = a − f, z = e − a and generate the complete square.

      This Python program solves the puzzle in 87ms.

      Run: [ @replit ]

      from enigma import (unpack, div, subsets, cproduct, printf)
      
      # make an additive magic square (a b c d e f g h i) given (a e f)
      def magic(a, e, f):
        (x, y, z) = (f, a - f, e - a)
        return (
          x + y, x + z + z, x + y + y + z,
          x + y + y + z + z, x + y + z, x,
          x + z, x + y + y, x + y + z + z,
        )
      
      # is a magic square canonical? (a < c, g, i; b < d)
      is_canonical = unpack(lambda a, b, c, d, e, f, g, h, i: a < min(c, g, i) and b < d)
      
      # the cards are all different, so can stand for themselves
      # (here we list them in canonical sorted order)
      cards = [ (1, 12), (2, 7), (3, 9), (3, 13), (4, 7), (4, 11), (5, 14), (6, 8), (8, 9) ]
      
      # consider a card both ways up
      flips = unpack(lambda x, y: ((x, y), (y, x)))
      
      # we arrange the cards as:
      #
      #   a1 b1 c1    a2 b2 c2
      #   d1 e1 f1    d2 e2 f2
      #   g1 h1 i1    g2 h2 i2
      
      # the centre card must have a sum of s
      s = div(sum(x + y for (x, y) in cards), 9)
      assert s is not None
      
      # find a card with the right sum for the e's
      for (k, (e2, e1)) in enumerate(cards):
        if not (e1 + e2 == s): continue
      
        # choose cards for the a's and the f's
        for (a, f) in subsets(cards[:k] + cards[k + 1:], size=2, select="P"):
          for ((a1, a2), (f1, f2)) in cproduct([flips(a), flips(f)]):
            # and generate the squares
            s1 = magic(a1, e1, f1)
            if not is_canonical(s1): continue
            s2 = magic(a2, e2, f2)
            # check the corresponding cards
            cs = sorted(min(flips(c)) for c in zip(s1, s2))
            if cs != cards: continue
            # collect the corners (a, c, g, i)
            corners = sorted(s1[x] for x in (0, 2, 6, 8))
            printf("corners={corners} [s1={s1} s2={s2}]")
      

      Solution: The four corner squares are: 3, 7, 9, 13.

      The magic constant in the first square is 24, in the second square it is 18.

      Like

  • Unknown's avatar

    Jim Randell 9:38 am on 17 October 2019 Permalink | Reply
    Tags:   

    Brainteaser 1734: Scratchcard 

    From The Sunday Times, 10th December 1995 [link]

    Every Saturday The Daily Broadsheet gives readers a free scratchcard comprising two crosses and a number of ticks concealed beneath silver foil squares. Readers have to scratch just three of the squares to reveal what is beneath. Anyone who scratches only ticks can use the card to obtain a 50p discount on The Sunday Broadsheet. The probability of revealing three ticks, expressed as a percentage, is a whole number larger than 50%.

    To increase circulation the paper intends to increase the number of silver squares, still with only two crosses. This will increase the chances of finding ticks when scratching three squares, and again the probability will be a whole number percentage.

    How many more silver squares do they intend to add?

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

    [teaser1734]

     
    • Jim Randell's avatar

      Jim Randell 9:39 am on 17 October 2019 Permalink | Reply

      If there are n ticks (where n ≥ 3), then the probability of scratching off 3 ticks is:

      (n / (n + 2)) × ((n − 1) / (n + 1)) × ((n − 2) / n)
      = (n − 1)(n − 2) / (n + 1)(n + 2)

      This Python program finds values of n where this probability is an integer percentage, greater than 50%.

      Run: [ @repl.it ]

      from enigma import irange, inf, first, printf
      
      # find n, p where probability p is an integer percentage > m
      def solve(m):
        for n in irange(3, inf):
          (p, r) = divmod(100 * (n - 1) * (n - 2), (n + 1) * (n + 2))
          if r == 0 and p > m: yield (n, p)
          if p == 99 and r > 0: break
      
      # find the first two (n, p) values with p > 50
      ((n1, p1), (n2, p2)) = first(solve(50), 2)
      d = n2 - n1
      printf("{d} more squares [{n1} ticks -> {p1}%; {n2} ticks -> {p2}%]")
      

      Solution: They intend to add 9 more squares.

      With 16 squares (14 ticks and 2 crosses) the probability is 65%.

      With 25 squares (23 ticks and 2 crosses) the probability is 77%.

      In fact there are only 4 values for n which give an integer percentage for p:

      n = 3, p = 10%
      n = 4, p = 20%
      n = 14, p = 65%
      n = 23, p = 77%

      Like

  • Unknown's avatar

    Jim Randell 9:41 am on 16 October 2019 Permalink | Reply
    Tags: ,   

    Teaser 2825: Twin sets 

    From The Sunday Times, 13th November 2016 [link] [link]

    The twins Wunce and Repete each made a list of positive perfect squares. In Wunce’s list each of the digits 0 to 9 was used exactly once, whereas in Repete’s list each of the digits was used at least once.

    Wunce commented that the sum of his squares equalled their year of birth, and Repete responded by saying that the sum of his squares was less than the square of their age.

    What is the sum of Wunce’s squares, and what is the sum of Repete’s?

    As stated there are multiple potential solutions to the puzzle. In the comments I give some additional conditions that allow a unique solution to be found (which is the same as the published solution).

    [teaser2825]

     
    • Jim Randell's avatar

      Jim Randell 9:42 am on 16 October 2019 Permalink | Reply

      As originally stated there are multiple solutions to this puzzle.

      I made the following additional suppositions in order to allow a unique solution to be arrived at.

      1. the puzzle is set on/after the twins birthday in 2018;
      2. the lists do not contain repeats;
      3. the squares are greater than 1.

      The following Python program then finds the unique solution in 281ms.

      Run: [ @repl.it ]

      from enigma import irange, isqrt, filter2, is_duplicate, arg, printf
      
      # Y = year the puzzle is set (which apparently should be 2018)
      Y = arg(2018, 0, int)
      # m = minimum allowable square (the root of the square is used)
      m = arg(2, 1, int)
      printf("[Y = {Y}, m={m}]")
      
      # squares (as strings, without repeated digits)
      (_, squares) = filter2(is_duplicate, (str(i * i) for i in irange(m, isqrt(Y))))
      
      # for Wunce find a pan-digital subset (without repeated digits)
      # ss = squares to consider
      # s = squares used so far
      # ds = digits used so far
      def solve1(ss, s=[], ds=set()):
        # are we done?
        if len(ds) == 10:
          yield list(int(x) for x in s)
        else:
          # chose the next element
          for (i, x) in enumerate(ss):
            if ds.intersection(x): continue
            yield from solve1(ss[i + 1:], s + [x], ds.union(x))
      
      # for Repete find a pan-digital set of squares (allowing repeated digits)
      # below a given total
      # n = largest square to consider
      # t = sum of squares must be less than this
      # s = squares used so far
      # ds = digits used so far
      def solve2(n, t, s=[], ds=set()):
        # are we done?
        if len(ds) == 10:
          yield s
        # choose the next element
        for i in irange(n, m, step=-1):
          i2 = i * i
          if i2 < t:
            yield from solve2(i - 1, t - i2, s + [i2], ds.union(str(i2)))
      
      # find sequences for Wunce
      for s1 in solve1(squares):
        t1 = sum(s1)
        printf("Wunce: total = {t1}, squares = {s1}")
      
        # calculate the age
        age = Y - t1
        age2 = age * age
        printf("age = {age}, age^2 = {age2}")
      
        # find sequences for Repete
        for s2 in solve2(age - 1, age2):
          t2 = sum(s2)
          printf("Repete: total = {t2}, squares = {s2}")
      

      Solution: The sum of Wunce’s squares is 1989. The sum of Repete’s squares is 831.

      Wunce’s list of squares is (324, 576, 1089) = (18², 24², 33²).

      The sum of which is 1989. So in 2018 the twins would be 29.

      Repete’s list of squares is (4, 9, 25, 36, 81, 100, 576) = (2², 3², 5², 6², 9², 10², 24²).

      The sum of which is 831. Which is less than 841 = 29².

      If we allow 1 in the list of squares then the sum of Repete’s list could be 832. And there are further possibilities if squares in the list are allowed to be repeated.


      The published solution is as follows:

      The sum of Wunce’s squares was 1989. We apologise that this Teaser needed to have been published in 2018 or later in order to resolve the sum of Repete’s squares (the intended answer was 831). All entries showing a correct answer for the first part of the Teaser will be included in the prize draw.

      Like

    • Frits's avatar

      Frits 1:43 pm on 20 October 2020 Permalink | Reply

      Same three assumptions.

      from enigma import nsplit, flatten
      from itertools import combinations as comb
      
      # contains all digits 0-9
      alldigits = lambda li: all(i in flatten([nsplit(x) for x in li]) 
                                 for i in range(0, 10))
      
      sq = [i*i for i in range(2, 45)]
      
      # check squares for Wunce
      for j in range(3, 6):
        # digit 7 only occurs first at sq[22] (576)
        # so pick some squares < 576 and at least one >= 576
        for j1 in range(1, j):
          for p1 in comb(sq[:22], j1):
            for p2 in comb(sq[22:], j - j1):
              p = p1 + p2
             
              if sum(p) not in range(1900, 2020): continue
              if sum(len(str(x)) for x in p) != 10: continue
              if not alldigits(p): continue
              
              age = 2018 - sum(p)
              limit = age * age
              
              space = limit - 576
              if space < 0: continue
              
              # we may only pick one or two squares from group 576, 625, ....
              fromHighest = 1 if space < 625 else 2
              
              # highest index with square smaller than limit and 2 smallest squares
              h2 = max([i for i, x in enumerate(sq) if x <= limit - 4 - 9])
              
              # check squares for Repete
              for k in range(3, 10):
               for j3 in range(1, fromHighest + 1):
                  for q2 in comb(sq[22:h2+1], j3):
                    picked = sum(q2)
                    # highest index with square smaller than limit minus q2 entries
                    h1 = max([i for i, x in enumerate(sq) if x <= limit - picked])
                    
                    for q1 in comb(sq[:h1+1], k - j3):
                      q = q1 + q2
                      if not alldigits(q): continue
                      if sum(q) >= age*age: continue
                      print("sum Wunce's", sum(p), p)
                      print("sum Repete's", sum(q), q)
                      print("Age", age)
      

      Like

  • Unknown's avatar

    Jim Randell 8:26 am on 15 October 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 505: [Colourland] 

    From The Sunday Times, 14th February 1971 [link]

    Colourland is inhabited by three races, all similar in appearance — Redmen, a truthful people; Greenmen, who always lie; and Bluemen, who sometimes tell the truth and sometimes lie. Their houses are painted either red, green or blue, but no one lives in a house with the colour of his race name.

    One day, after a football match, three of the winning team, which won 1-0, were standing together. Their shirts were numbered 1, 2 and 3, and by tradition these three would be one from each race, each occupying a different coloured house.

    I asked number 1 if number 2 told the truth more often than number 3, to which he answered “No”. I then asked number 2 if any of the three of them had scored the winning goal, his reply being “I did”.

    I next asked one of them if he lived in a certain coloured house, and from his reply I was in fact able to determine:

    (i) the colour of each tribesman’s house, and:
    (ii) which (if any) scored the goal.

    Can you?

    This puzzle was originally published with no title.

    [teaser505]

     
    • Jim Randell's avatar

      Jim Randell 8:27 am on 15 October 2019 Permalink | Reply

      The tribes on the island are described as “similar in appearance”, which I take to mean that the setter is not able to determine which tribe the person he is questioning is from, other than from the answers to the questions.

      This Python program runs in 84ms.

      from enigma import (subsets, cproduct, join, printf)
      
      # honesty rating
      h = dict(R=2, B=1, G=0)
      
      # valid replies for tribe t, statement s
      def valid(t, s):
        s = bool(s)
        if t == "R": return s
        if t == "G": return (not s)
        return True
      
      # collect responses
      rs = list()
      
      # assign tribes
      for (t1, t2, t3) in subsets("RGB", size=len, select="P"):
      
        # check statement: "Is 2 more truthful than 3?" -> 1: "No"
        if not valid(t1, not (h[t2] > h[t3])): continue
      
        # choose a goal scorer
        for g in (0, 2):
      
          # check statement: "Who scored the winning goal?" -> 2: "2"
          if not valid(t2, g == 2): continue
      
          # assign houses
          for (h1, h2, h3) in subsets("RGB", size=len, select="P"):
            # no one lives in the same colour house as his tribe
            if t1 == h1 or t2 == h2 or t3 == h3: continue
      
            rs.append(((t1, t2, t3), (h1, h2, h3), g))
      
      
      # Q3 to player P: "Is the colour of your house Q?"
      for (P, Q) in cproduct([(1, 2, 3), "RGB"]):
        i = P - 1 # index for player P
      
        # collect results
        (ys, ns) = (set(), set())
        for (tribes, houses, g) in rs:
      
          # solution is (house colours by tribe, goal scorer)
          s = (tuple(x + houses[tribes.index(x)] for x in "RGB"), ("2" if g == 2 else "not 2"))
      
          # answer is "Yes"
          if valid(tribes[i], houses[i] == Q):
            ys.add(s)
      
          # answer is "No"
          if valid(tribes[i], houses[i] != Q):
            ns.add(s)
      
        # examine the responses
        for (s, t) in zip([ys, ns], ["Yes", "No"]):
          # we only want one
          if len(s) != 1: continue
          (hs, g) = s.pop()
          # and the goal scorer must be determined
          if g.startswith("not "): continue
          # output solution
          printf("Q3 to player {P}: \"Is your house {Q}?\"; {t} -> houses = {hs}; scorer = {g}", hs=join(hs, sep=" "))
      

      Solution: Yes. (i) Redman in blue house. Blueman in green house. Greenman in red house; (ii) Redman scored the goal.

      The third question is asked of player 2, and is: “Is your house green?”.

      An answer of “No” allows the questioner to infer:

      Player 2 is Redman. His house is blue. He scored the goal.
      Player 1 and 3 are: Greenman in a red house and Blueman in a green house, but we don’t know which player has which number.

      Similarly, if the third question asked (still of player 2) is: “Is your house red?”, and the answer received is “Yes”, then we can infer:

      Player 2 is Greenman. His house is blue. He did not score the goal.
      Player 1 and 3 are: Redman in a green house and Blueman in a red house, but we don’t which player has which number.

      But we cannot determine who the goal scorer is, only that it isn’t player 2.


      If we consider the first question.

      If the person we ask is Blue, then they could answer anything. So the following are possible:

      #1=B, #2=R, #3=G
      #1=B, #2=G, #3=R

      If the person asked the first question is Red, then #2 does not tell the truth more often than #3:

      #1=R, #2=G, #3=B

      If the person asked the first question is Green, then #2 does tell the truth more often than #3:

      #1=G, #2=R, #3=B

      So there are four possible assignments of players to tribes, and #2 is not Blue, so we can ask them a question, and know we are not going to get a “random” answer.

      If we ask #2 “Do you live in a green house?”. We know if G is asked this they must answer “Yes” (as they don’t), so if we get an answer of “No” we know that #2 must be R, and they do not live in a green house.

      In this situation the houses are: RB, BG, GR, and we also know that R is #2, and so answered Q2 truthfully, so R scored the goal.

      Like

  • Unknown's avatar

    Jim Randell 9:54 am on 14 October 2019 Permalink | Reply
    Tags:   

    Teaser 2826: By heck! 

    From The Sunday Times, 20th November 2016 [link] [link]

    The registration number of my old car consists of three consecutive letters of the alphabet in reverse order, followed by a three-figure number consisting of the same digit written three times, followed finally by a single letter.

    I am a fan of “hex” (i.e. arithmetic in base 16, with “digits” 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E and F) and I realised that the registration number looked like a number in hex. So I worked out its equivalent value in our usual base-10 arithmetic. Miraculously the answer was a nine-figure number that used all nine of the non-zero digits.

    What is the registration number?

    [teaser2826]

     
    • Jim Randell's avatar

      Jim Randell 9:55 am on 14 October 2019 Permalink | Reply

      The number plate reads as the following hex digits abcdefg, where:

      a = b + 1
      b = c + 1
      c > 9
      d = e = f < 10
      g > 9

      So there are 4 × 10 × 6 = 240 possibilities for the registration number which can be examined by a straightforward program to find those which correspond to an appropriate decimal number.

      This Python program runs in 47ms.

      Run: [ @repl.it ]

      from enigma import (irange, cproduct, nconcat, nsplit, int2base, printf)
      
      # consider possible hex digits
      for (x, y, z) in cproduct([irange(10, 13), irange(0, 9), irange(10, 15)]):
        # calculate the numerical value of the number plate (as hex)
        n = nconcat((x + 2, x + 1, x, y, y, y, z), base=16)
        # split n into base 10 digits
        ds = nsplit(n, base=10)
        # there should be 9 different digits, not including 0
        if len(ds) == 9 and 0 not in ds and len(set(ds)) == 9:
          # output solution
          printf("{h} hex = {n} dec", h=int2base(n, base=16))
      

      Solution: The registration number is CBA777F.

      Like

  • Unknown's avatar

    Jim Randell 3:03 pm on 11 October 2019 Permalink | Reply
    Tags:   

    Teaser 2977: Enjoying retirement 

    From The Sunday Times, 13th October 2019 [link] [link]

    George and Martha have worked on separate departments of a company which has four-digit telephone extensions. George looked at his extension and it was ABCD. Martha’s (larger) also had A, B and C as her first three digits but not necessarily in that order. Her last digit was E. They added up their two four-digit numbers and found that the least significant digit was F. They then looked at the difference and that was a four-digit number of which the least significant digit was G. They then looked at the product and the least significant digit was H. They then looked at the average of the extensions; in it the first two digits were equal, the last two digits were also equal, and the least significant digit was J. I have thus mentioned nine digits, all positive and unequal.

    What was Martha’s extension?

    [teaser2977]

     
    • Jim Randell's avatar

      Jim Randell 3:34 pm on 11 October 2019 Permalink | Reply

      I assumed that the average of the two extension numbers was a whole number, so both extension numbers must have the same parity.

      The following Python program runs in 92ms.

      Run: [ @repl.it ]

      from enigma import (irange, subsets, nconcat, nsplit, div, printf)
      
      # allowable digits
      digits = set(irange(1, 9))
      
      # check digits are allowable and all different
      def check(*ds):
        return len(ds) + len(digits.difference(ds)) == len(digits)
      
      # consider digits D and E, which must have the same parity
      for (D, E) in subsets(digits, size=2, select='P'):
        if not(D % 2 == E % 2): continue
      
        # the sum ends in F, difference ends in G, the product ends in H
        (F, G, H) = (n % 10 for n in (D + E, E - D, D * E))
        if not check(D, E, F, G, H): continue
      
        # choose first three digits A, B, C to give ABCD = George's extension
        for (A, B, C) in subsets(digits.difference((D, E, F, G, H)), size=3, select='P'):
          xG = nconcat(A, B, C, D)
      
          # and permute ABC to give XYZ, where XYZE = Martha's extension
          for (X, Y, Z) in subsets((A, B, C), size=3, select='P'):
            xM = nconcat(X, Y, Z, E)
      
            # George's number is less than Martha's, difference is 4 digits
            if xM - xG < 1000: continue
      
            # average ...
            avg = nsplit(div(xG + xM,  2))
            # has first 2 and last 2 digits equal
            if not(avg[0] == avg[1] and avg[-2] == avg[-1]): continue
            # and the final digit is J
            J = avg[-1]
            if not check(A, B, C, D, E, F, G, H, J): continue
      
            printf("George = {xG}, Martha = {xM} [A={A} B={B} C={C} D={D} E={E} F={F} H={H} J={J}]")
      

      Solution: Martha’s extension number is 8563.

      George’s extension number is 6859.

      So if George’s number is ABCD, then Martha’s is BCAE.

      The sum is 15422, which ends in F=2.

      The difference is 1704, which ends in G=4.

      The product is 58733617, which ends in H=7.

      And the average (mean) is 7711, which starts with two 7’s and ends with two J’s; J=1.

      Each non-zero digit is mentioned once. We have (1, 2, 3, 4, 5, 6, 7, 8, 9) = (J, F, E, G, C, A, H, B, D).

      Like

    • GeoffR's avatar

      GeoffR 9:31 am on 13 October 2019 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;    
      var 1..9: G; var 1..9: H; var 1..9: J;
      
      constraint all_different([A, B, C, D, E, F, G, H, J]);
      
      % George's extension number
      var 1000..9999: george = 1000*A + 100*B + 10*C + D; 
      
      % Martha's extension number
      var 1000..9999: martha ;
      
      % Average of George's and Martha's numbera
      var 1000..9999: av ; 
       
      constraint av == (martha + george) div 2;
      constraint martha > george;
      
      % Martha's extension had the first three digits A,B,C in some order
      % Martha's 1st Digit
      constraint 
         martha div 1000 == A 
      \/ martha div 1000 == B 
      \/ martha div 1000 == C;
      
      % Martha's 2nd Digit
      constraint 
         ((martha div 100) mod 10) == A
      \/ ((martha div 100) mod 10) == B
      \/ ((martha div 100) mod 10) == C;
      
      % Martha's 3rd Digit
      constraint 
         ((martha div 10) mod 10) == A
      \/ ((martha div 10) mod 10) == B
      \/ ((martha div 10) mod 10) == C;
      
      % Martha's 4th Digit
      constraint martha mod 10 == E;
      
      % Sum of george and martha has F as least significant digit
      constraint (george + martha) mod 10 == F;
      
      % Difference of george and martha has G as least significant digit
      constraint (martha - george) > 1000  
      /\ (martha - george) mod  10 == G;
      
      % Product of george and martha has H as least significant digit
      constraint (martha * george) mod 10 == H;
      
      % Average has first 2 digits equal
      constraint av div 1000 ==  (av div 100) mod 10;
        
      % Average has last 2 digits equal to J
      constraint (av div 10) mod 10 == J /\ av mod 10 = J;
      
      solve satisfy;
      
      output [ "Martha's extension is " ++ show(martha) 
      ++ "\n" ++ "George's extension is " ++ show(george)
      ++ "\n" ++ "Average of two extensions is " ++ show(av)];
      
      % Martha's extension is 8563
      % George's extension is 6859
      % Average of two extensions is 7711
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:38 am on 11 October 2019 Permalink | Reply
    Tags:   

    Teaser 2829: Making a dozen 

    From The Sunday Times, 11th December 2016 [link]

    In this addition sum different letters consistently stand for different digits:

    What is the value of LETTERS?

    [teaser2829]

     
    • Jim Randell's avatar

      Jim Randell 10:39 am on 11 October 2019 Permalink | Reply

      This is a straightforward alphametic sum.

      We can solve it using the [[ SubstitutedSum() ]] solver from the enigma.py library.

      The following command executes in 164ms.

      Run: [ @repl.it ]

      % python -m enigma SubstitutedSum "SEVEN + THREE + TWO = TWELVE"
      (SEVEN + THREE + TWO = TWELVE)
      (82524 + 19722 + 106 = 102352) / E=2 H=9 L=3 N=4 O=6 R=7 S=8 T=1 V=5 W=0
      (82526 + 19722 + 104 = 102352) / E=2 H=9 L=3 N=6 O=4 R=7 S=8 T=1 V=5 W=0
      

      Solution: LETTERS = 3211278.

      The values for N and O can be interchanged, but we are asked for the value of LETTERS, so the solution is unique.

      Like

    • GeoffR's avatar

      GeoffR 11:01 am on 12 October 2019 Permalink | Reply

      
      % A Solution in MiniZinc
      
      %   S E V E N
      %   T H R E E
      %       T W O
      % -----------
      % T W E L V E
      % -----------
      include "globals.mzn";
       
      var 1..9:S; var 0..9:E; var 0..9:V; var 0..9:N;
      var 1..9:T; var 0..9:H; var 0..9:R; var 0..9:W;   
      var 0..9:O; var 0..9:L;
      
      % Carry digits for adding columns
      var 0..2: carry1; var 0..2: carry2; var 0..2: carry3; 
      var 0..2: carry4; var 0..2: carry5; 
       
      constraint all_different([S, E, V, N, T, H, R, W, O, L]);
      
      % Working left from the right hand column
      constraint (N + E + O) mod 10 == E 
      /\ (N + E + O) div 10 == carry1;
      
      constraint (2*E + W + carry1) mod 10 == V 
      /\ (2*E + W + carry1) div 10 == carry2;
      
      constraint (V + R + T + carry2) mod 10 == L 
      /\ (V + R + T + carry2) div 10 == carry3;
      
      constraint (E + H + carry3) mod 10 == E 
      /\ (E + H + carry3) div 10 == carry4;
      
      constraint (S + T + carry4) mod 10 == W 
      /\ (S + T + carry4) div 10 == carry5;
      
      constraint T = carry5;
      
      solve satisfy;
      
      output [ "LETTERS = " ++ show(L),show(E),
      show(T),show(T),show(E), show(R),show(S) ]
      
      ++ ["\n" ++ "SEVEN = " ++  show(S),show(E),
      show(V),show(E),show(N)]
      ++ [", THREE = " ++  show(T),show(H),show(R),
      show(E),show(E) ]
      ++ [", TWO = " ++  show(T),show(W),show(O) ]
      ++ [", TWELVE = " ++  show(T),show(W),show(E),
      show(L),show(V),show(E)];
      
      % LETTERS = 3211278
      % SEVEN = 82526, THREE = 19722, TWO = 104, TWELVE = 102352
      % -----------------
      % LETTERS = 3211278
      % SEVEN = 82524, THREE = 19722, TWO = 106, TWELVE = 102352
      % Finished in 161msec
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 11:51 am on 9 October 2019 Permalink | Reply
    Tags:   

    Teaser 2836: Squaring up 

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

    Alice had a large collection of one centimetre square tiles. She used them to make a set of some larger squares of different sizes, all with sides of less than a metre. When I saw these squares I removed one corner tile from each. Then, for each mutilated shape, Alice moved the minimum number of tiles to transform it into a rectangle. Overall she moved two hundred tiles. This resulted in a set of rectangles all of whose sides were a prime number of centimetres long.

    What (in increasing order) were the lengths of the sides of her original squares?

    [teaser2836]

     
    • Jim Randell's avatar

      Jim Randell 11:51 am on 9 October 2019 Permalink | Reply

      If Alice has made a square of side n (so n < 100), then she has used n² tiles.

      The setter removes one corner tile from the square, leaving (n² − 1) tiles.

      Alice can then take the remaining (n − 1) tiles from the same row (or column) as the removed tile, to produce a rectangle with dimensions n × (n − 1), and the removed (n − 1) tiles can be added back to form an additional column (or row) of a rectangle with dimensions (n + 1) × (n − 1).

      We require both these dimensions to be prime, so we are looking for twin primes of the form (n − 1) and (n + 1).

      We need to find a set of primes that sum to 200, where each is the lower of a pair of twin primes.

      This Python program runs in 85ms.

      Run: [ @repl.it ]

      from enigma import (primes, tuples, subsets, printf)
      
      # find possible shapes, record p
      ps = list(p for (p, q) in tuples(primes.between(1, 100), 2) if q == p + 2)
      
      # find a subset that sums to 200
      for s in subsets(ps, min_size=3):
        if sum(s) == 200:
          # the sides of the original squares
          ns = list(p + 1 for p in s)
          printf("squares = {ns}")
      

      Solution: The lengths of the sides of the original squares were: 30, 42, 60, 72.


      Examining all subsets could be a costly approach if the parent set is large. In this case there are only 8 candidate primes, so there are at most 255 (non-empty) subsets to consider.

      So here is an alternative program, although on a small problem like this there isn’t much difference in execution time.

      from enigma import (primes, tuples, printf)
      
      # find possible shapes, record p
      ps = list(p for (p, q) in tuples(primes.between(1, 100), 2) if q == p + 2)
      
      # decompose <t> into numbers from <ns>
      def decompose(t, ns, s=[]):
        if t == 0:
          yield s
        else:
          for (i, n) in enumerate(ns):
            if n > t: break
            yield from decompose(t - n, ns[i + 1:], s + [n])
      
      # find a subset that sums to 200
      for s in decompose(200, ps):
        # the sides of the original squares
        ns = list(p + 1 for p in s)
        printf("squares = {ns}")
      

      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