Recent Updates Page 56 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 2:50 pm on 28 November 2019 Permalink | Reply
    Tags:   

    Teaser 2890: Squares on cubes 

    From The Sunday Times, 11th February 2018 [link] [link]

    Liam has a set of ten wooden cubes; each has a different number (from 1 to 10) painted on one face (the other five faces are blank). He has arranged them in a rectangular block with all numbers upright and facing outwards. Each vertical side of the block shows some numbers which can be read as a number that is a perfect square. No two square numbers have the same number of digits.

    Which square number must be present?

    [teaser2890]

     
    • Jim Randell's avatar

      Jim Randell 2:51 pm on 28 November 2019 Permalink | Reply

      The cubes are placed on the table, so that the numbers are showing on vertical faces. They are then slid around on the table to arrange them into a contiguous rectangular block with all the numbers showing on the outside vertical edges of the block.

      The cubes must be arranged into a 2×5 block (a 1×10 block is not possible), and each of the 4 vertical sides of the block reads as a square number. And each of the 4 square numbers has a different number of digits.

      The sides composed of 2 blocks cannot involve 10 (as none of 10, 10_, _10 are square numbers), so they must display square numbers made of 1 number (1 digit) and 2
      numbers (2 digits) and the 5 block sides must show squares made of 3 numbers (3 digits) and 4 numbers (5 digits, including 10).

      This Python program runs in 103ms.

      Run: [ @repl.it ]

      from enigma import (irange, subsets, concat, is_square, Accumulator, diff, join, printf)
      
      # single digits (10 must appear in the 4-number square)
      digits = irange(1, 9)
      
      # make squares from k numbers
      def squares(numbers, k):
        for ns in subsets(numbers, size=k, select="P"):
          s = int(concat(ns))
          if is_square(s):
            yield ns
      
      # find values common to all solutions
      r = Accumulator(fn=set.intersection)
      
      # choose a 1 number square [1, 4, 9]
      for s1 in squares(digits, 1):
      
        # choose a 2 number square [not 10]
        for s2 in squares(diff(digits, s1), 2):
      
          # choose a 3 number square [not 10]
          for s3 in squares(diff(digits, s1 + s2), 3):
      
            # choose a 4 number square [must have 10]
            for s4 in squares(diff(digits, s1 + s2 + s3) + (10,), 4):
      
              # turn the squares into strings
              ss = tuple(map(concat, (s1, s2, s3, s4)))
      
              printf("{s1} {s2} {s3} {s4}")
              r.accumulate(set(ss))
      
      printf("common squares = {r}", r=join(sorted(r.value), sep=", "))
      

      Solution: The square number 9 must be present.

      The three possible arrangements for the square numbers are:

      (9, 36, 784, 11025)
      (9, 81, 324, 51076)
      (9, 81, 576, 23104)

      Here is a diagram of the first arrangement.

      Imagine the cubes are laid out like a 2×5 block of chocolate viewed from above. The numbers are etched on the the sides of the cubes. But the block has started to melt and so the bottom of it has splayed out, allowing us to see all four of the square numbers when viewed from directly above:

      Like

    • GeoffR's avatar

      GeoffR 8:23 am on 30 November 2019 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Looking for 1-digit, 2-digit, 3-digit and 5-digit squares
      % on cubes to use up all 10 numbers (1-10) in a 5 by 2 block
      % of cubes in same arrangement as Jim
      
      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; var 0..9: K;
      
      % 2,3 and 5 digit squares
      var 10..99: BC = 10*B + C;
      var 100..999: DEF = 100*D + 10*E + F;
      var 10000..99999: GHIJK; 
      
      set of int: sq2 = {n*n | n in 4..9 };
      set of int: sq3 = {n*n | n in 10..31};  
      set of int: sq5 = {n*n | n in 100..316};
      
      % Four squares
      constraint A == 4 \/ A == 9;
      constraint BC in sq2;
      constraint DEF in sq3;
      constraint GHIJK in sq5;
      
      constraint G == GHIJK div 10000 
      /\ H == GHIJK div 1000 mod 10
      /\ I = GHIJK div 100 mod 10 
      /\ J == GHIJK div 10 mod 10
      /\ K = GHIJK mod 10;
      
      % Number 10 must be in the five digit square GHIJK
      constraint (G == 1 /\ H = 0) \/  (H == 1 /\ I = 0)
      \/ (I == 1 /\ J = 0) \/ (J == 1 /\ K = 0);
      
      % check digit '1' occurs twice only i.e as 1 and 10
      constraint count_eq([A, BC div 10, BC mod 10,
      DEF div 100, DEF div 10 mod 10, DEF mod 10,
      G, H, I, J, K ], 1, 2);
      
      % the set of digits used
      var set of int: my_set  = {A, BC div 10, BC mod 10,
      DEF div 100, DEF div 10 mod 10, DEF mod 10,
      G, H, I, J, K };
      
      % maximum set of digits used
      set of int: all_dig == {1,2,3,4,5,6,7,8,9,0};
      
      constraint my_set == all_dig;
      
      solve satisfy;
      
      % Four squares
      output [ "Squares: 1-digit = " ++ show(A)
      ++ ", 2-digit = " ++ show(BC)
      ++ ", 3-digit = " ++ show(DEF)
      ++ ", 5-digit = " ++ show(GHIJK)];
      
      % Squares: 1-digit = 9, 2-digit = 36, 3-digit = 784, 5-digit = 11025
      % ----------
      % Squares: 1-digit = 9, 2-digit = 81, 3-digit = 576, 5-digit = 23104
      % ----------
      % Squares: 1-digit = 9, 2-digit = 81, 3-digit = 324, 5-digit = 51076
      % ----------
      % ==========
      % Finished in 684msec
      
      

      Without the constraint that the digit one is the duplicated digit, I found three more possible sets of squares:

      (4,36,289, 51076)
      (9,36,784,21025)
      (9,25,784, 36100)

      @Jim: Please confirm your run-time for this code.Thanks,

      Like

      • Jim Randell's avatar

        Jim Randell 8:43 am on 1 December 2019 Permalink | Reply

        @Geoff: On my machine I get a comparative runtime of 377ms for the code you posted (using the gecode solver – the chuffed solver fails on this model).

        Like

  • Unknown's avatar

    Jim Randell 2:31 pm on 26 November 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 511: What’s my age? 

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

    I recently had an odd letter from a puzzle addict, who wrote:

    “I know that you are between 25 and 80, and I’ve made a bet (at fair odds) that I can deduce your age from your Yes/No answers to the following questions:

    1. Are you under 55?
    2. Is your age a prime number?
    3. If the digits of your age are reversed, is the result prime?
    4. Is the digital root of your age even?

    Please reply on enclosed stamped postcard.”

    I did so, and had a reply a few days later:

    “Many thanks. As soon as I read your first three answers I knew that the fourth answer must lead me to your age. You are …” (and he gave a figure wrong by well over 20 years).

    Puzzled, I rechecked my four answers and found to my horror that I had carelessly transposed two of them and so had misled him.

    How old am I?

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

    [teaser511]

     
    • Jim Randell's avatar

      Jim Randell 2:32 pm on 26 November 2019 Permalink | Reply

      This Python program runs in 73ms.

      from enigma import (defaultdict, irange, is_prime, nreverse, digrt, subsets, update, printf)
      
      # collect <answers> -> <ages>
      d = defaultdict(list)
      
      # consider possible ages
      for n in irange(25, 80):
        # the statements
        ss = (
          # 1. "Age is under 55?"
          (n < 55),
          # 2. "Age is prime?"
          is_prime(n),
          # 3. "Reverse of age is prime?"
          is_prime(nreverse(n)),
          # 4. "Digital root of age is even?"
          (digrt(n) % 2 == 0),
        )
        d[ss].append(n)
      
      # generate sequences with two (different) values swapped
      def swap(s):
        s = list(s)
        # swap two of the values
        for ((i, x), (j, y)) in subsets(enumerate(s), size=2):
          if x != y: yield tuple(update(s, [(i, y), (j, x)]))
      
      # find values for the first three questions where the final question gives a definite answer
      Bool = (True, False)
      for k in subsets(Bool, size=3, select="M"):
        # possible mistaken keys
        ks = list(k + (x,) for x in Bool)
        # each must lead to a single answer
        if not all(len(d[k]) == 1 for k in ks): continue
        # consider possible mistaken keys
        for k in ks:
          m = d[k][0] # mistaken age
          # look for possible real keys
          for r in swap(k):
            # possible real age
            for n in d[r]:
              # must be more than 20 years difference
              if abs(n - m) > 20:
                printf("age = {n}, real = {r}; swap = {k}, age = {m}")
      

      Solution: The setter’s age is 71.


      There is only one set of values for the answers to the first three questions that lead to two ages that can be differentiated by the answer to the fourth question.

      If all the first three questions are answered “Yes”, then:

      If the answer to the fourth question is “Yes”, the age is 31.
      If the answer to the fourth question is “No”, the age is 37.

      The setter then discovers that he has exchanged the places of two of the answers.

      So the answer sent cannot be: (“Yes”, “Yes”, “Yes”, “Yes”), as swapping any two of them would make no difference.

      So the sent answers must have been: (“Yes”, “Yes”, “Yes”, “No”). Leading the puzzler to believe that the setters age was 37.

      But this is incorrect “by well over 20 years”, so the setter must be much less than 17 (not possible) or much more than 57.

      If we move the “No” into different positions we get:

      (“No”, “Yes”, “Yes”, “Yes”) → 71
      (“Yes”, “No”, “Yes”, “Yes”) → 35, 38
      (“Yes”, “Yes”, “No”, “Yes”) → 29, 47, 53

      Only the first of these gives an age much more than 57, so the answers for the first and fourth question were accidentally swapped.

      Like

  • Unknown's avatar

    Jim Randell 1:15 pm on 25 November 2019 Permalink | Reply
    Tags:   

    Teaser 2886: Metal arithmetic 

    From The Sunday Times, 14th January 2018 [link] [link]

    The area of one face of a hot, steel cuboid block was a single-figure whole number of square feet; and it was within one per cent of this after cooling and contraction. When cool, it was cut, parallel to this face, into blocks of the same width and height, but unequal length. For the first cut block, its width, height and length, in inches, were different two-figure whole numbers with only four factors (including 1 and the number), and they had only the factor 1 in common. The same applied to the other cut blocks. Curiously, the six digits of the width, height and length of the first cut block were also all different.

    In ascending order, what were the dimensions, in inches, of the shortest block?

    [teaser2886]

     
    • Jim Randell's avatar

      Jim Randell 1:16 pm on 25 November 2019 Permalink | Reply

      This Python program runs in 81ms.

      Run: [ @replit ]

      from enigma import (irange, divisors, subsets, divc, is_duplicate, printf)
      
      # find 2-digit numbers with exactly 4 divisors
      ns = dict()
      for n in irange(10, 99):
        fs = divisors(n)
        if len(fs) == 4:
          ns[n] = set(fs)
      
      # check x and y share exactly k divisors
      def check(x, y, k=1):
        return len(ns[x].intersection(ns[y])) == k
      
      # find x, y dimensions
      for (x, y) in subsets(ns.keys(), size=2):
      
        # cross section is within 1% of a single digit number of square feet
        A = x * y
        k = divc(A, 144)
        if k > 9: continue
        if not (100 * A > 99 * 144 * k): continue
      
        # x and y share only one divisor
        if not check(x, y): continue
      
        # find possible values for z dimension
        zs = list(z for z in ns.keys() if z != x and z != y and check(x, z) and check(y, z))
        # it is implied there are at least 3 blocks
        if not (len(zs) > 2): continue
      
        # and there must be a z such that x, y, z consist of 6 different digits
        if all(is_duplicate(x, y, z) for z in zs): continue
      
        # output dimensions of the smallest block
        printf("x={x} y={y} z={z} [A={A} k={k} zs={zs}]", z=min(zs))
      

      Solution: The shortest block measured 21 × 34 × 55 inches.

      The 21 in. × 34 in. face has an area of 714 sq. in., when heated up the face expands to exactly 5 square feet = 720 sq. in.

      The area of the face when cold is 99.17% of this area.

      The 4 divisors of 21 are: 1, 3, 7, 21.

      The 4 divisors of 34 are: 1, 2, 17, 34.

      The cold block was cut into lengths of 55 in., 65 in., 95 in. (it is implied the block was cut into at least 3 pieces).

      The 4 divisors of 55 are: 1, 5, 11, 55.

      The 4 divisors of 65 are: 1, 5, 13, 65.

      The 4 divisors of 95 are: 1, 5, 19, 95.

      So the first block cut could be: 21 × 34 × 65, using the digits: 1, 2, 3, 4, 5, 6, or: 21 × 34 × 95, using the digits: 1, 2, 3, 4, 5, 9.

      And the shortest block is 21 × 34 × 55, which repeats the digit 5.

      Like

  • Unknown's avatar

    Jim Randell 8:11 am on 24 November 2019 Permalink | Reply
    Tags:   

    Brainteaser 1766: House squares 

    From The Sunday Times, 21st July 1996 [link]

    There is an even number of houses in my road, numbered consecutively from 1 upwards, with the odd numbers on one side and the even numbers on the other.

    My neighbour’s son was practising with his new calculator. He found the sum of the squares of all the house numbers on one side of the road and subtracted it from the sum of the squares of all the house numbers on the other side of the road. The digits in his answer were all the same.

    Noticing that two of the houses displayed “For Sale” signs he decided to carry out a second calculation omitting those two houses. This gave an answer 50% higher than the first.

    Which two houses are for sale?

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

    [teaser1766]

     
    • Jim Randell's avatar

      Jim Randell 8:12 am on 24 November 2019 Permalink | Reply

      This Python program runs in 70ms.

      Run: [ @repl.it ]

      from enigma import seq_all_same, nsplit, irange, div, subsets, printf
      
      # generate the difference between the sum of the even and odd squares up to n
      def generate():
        # sums of squares
        n = d = 0
        while True:
          n += 1
          d -= n * n
          n += 1
          d += n * n
          yield (n, d)
      
      # find solutions to the puzzle
      def solve():
        # look at differences for increasing even n
        for (n, d) in generate():
          # the difference should be a repdigit
          if not seq_all_same(nsplit(d)): continue
          # the second answer is 50% higher
          diff = div(d, 2)
          if diff is None: continue
      
          # look for the house numbers for sale p, q
          for (p, q) in subsets(irange(1, n), size=2):
            delta = sum([-1, 1][x % 2] * x * x for x in (p, q))
            if abs(delta) == diff:
              yield (p, q, n, d, d + diff)
      
      # we only need the first solution
      for (p, q, n, d, d2) in solve():
        printf("for sale = {s} [{n} houses; difference = {d} -> {d2}]", s=(p, q))
        break
      

      Solution: The two houses for sale are 14 and 23.

      There are 36 houses in the street.

      The result of the first calculation is 666.

      The result of the second calculation is 999.

      The difference between the sums of the squares of the first n even numbers and the first n odd numbers can be expressed:

      D(n) = n (2n + 1)

      Which allows a slightly shorter program than generating the differences constructively.

      Like

  • Unknown's avatar

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

    Teaser 2983: Maths for dessert 

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

    George and Martha have their five daughters round the dinner table. After the meal, they had ten cards numbered 0 to 9 inclusive and randomly handed two to each daughter. Each was invited to form a two-digit number. The daughter drawing 0 obviously had no choice and had to announce a multiple of ten.

    However, the others each had the choice of two options. For example if 3 and 7 were present, either 37 or 73 would be permissible. George added up the five two-digit numbers (exactly one being divisible by 9) and Martha noticed that three of the individual numbers divided exactly into that total.

    What was the total of the remaining two numbers?

    [teaser2983]

     
    • Jim Randell's avatar

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

      This Python program runs in 76ms.

      Run: [ @repl.it ]

      from enigma import (partitions, irange, cproduct, filter2, ordered, printf)
      
      # form numbers from digits a and b
      def numbers(a, b):
        if a > 0: yield 10 * a + b
        if b > 0: yield 10 * b + a
      
      # deal out the 10 cards in pairs
      for ps in partitions(irange(0, 9), 2):
        # exactly one of the numbers is divisible by 9
        if sum((a + b) % 9 == 0 for (a, b) in ps) != 1: continue
        # form the numbers from the pairs
        for ns in cproduct(numbers(*p) for p in ps):
          # exactly three of the numbers divide into the total
          t = sum(ns)
          (ds, rs) = filter2((lambda n: t % n == 0), ns)
          if len(ds) != 3: continue
          # output solution
          printf("sum{rs} = {s} [numbers = {ns}, total = {t}]", rs=ordered(*rs), s=sum(rs), ns=ordered(*ns))
      

      Solution: The sum of the numbers that do not divide into the total is 147.

      The numbers are: 14, 28, 50, 63, 97.

      Of these only 63 is divisible by 9.

      The sum of the numbers is 252, which is divisible by 14, 28, and 63.

      And the remaining numbers (50 and 97) sum to 147.

      Like

    • GeoffR's avatar

      GeoffR 9:47 am on 25 November 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:I;
      
      % Make J the zero digit
      int: J == 0;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I,J]);
        
      % Five 2-digit numbers 
      var 10..99: AB = 10*A + B;
      var 10..99: CD = 10*C + D;
      var 10..99: EF = 10*E + F;
      var 10..99: GH = 10*G + H;
      var 10..99: IJ = 10*I + J;
      
      var 10..400: all_dig; 
      var 10..200: dig2;   % sum of the remaining two numbers
      
      constraint all_dig = AB + CD + EF + GH + IJ;
      
      % One of the five 2-digit numbers is divisible by 9
      constraint sum ( [ AB mod 9 == 0, CD mod 9 == 0, 
      EF mod 9 == 0, GH mod 9 == 0, IJ mod 9 == 0 ] ) = 1;
      
      % Three of the 2-digit numbers divide the five digit total
      constraint all_dig mod AB == 0 /\ all_dig mod CD == 0 
      /\ all_dig mod EF == 0 /\ all_dig mod GH != 0 /\ all_dig mod IJ != 0;
      
      % Sum of two digits which do not divide the five digit total
      constraint  dig2 == GH + IJ ;
      
      solve satisfy;
      
      output [ "Total of the remaining two numbers = " ++ show(dig2)
      ++ "\nTotal of five 2-digit numbers = " ++ show(all_dig)++ "\n"
      ++"The five numbers are " ++ show(AB) ++ ", " ++ show(CD) ++ ", " 
      ++ show(EF) ++ ", " ++ show(GH) ++ " and " ++ show(IJ) ];
      
      % Total of the remaining two numbers = 147
      % Total of five 2-digit numbers = 252
      % The five numbers are 28, 14, 63, 97 and 50
      
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 7:56 am on 28 November 2019 Permalink | Reply

      
      from itertools import permutations
      
      for p in permutations('1234567890'):
        a, b, c, d, e, f, g, h, i, j = p
        
        ab, cd, ef = int(a + b), int(c + d), int(e + f)
        gh, ij = int(g + h), int(i + j)
      
        # check all five numbers are 2-digit numbers
        n5 = (ab, cd, ef, gh, ij)
        if all( x < 100 and x >= 10 for x in n5):
          tot = ab + cd + ef + gh + ij
      
          # two conditions to check
          if (sum(1 for y in n5 if y % 9 == 0) == 1 
            and sum(1 for z in n5 if tot % z == 0) == 3) :
               
            if (tot % ab == 0 and tot % cd == 0 and tot % ef == 0
              and tot % gh != 0 and tot % ij != 0) :
              print(f"Three factors are {ab}, {cd} and {ef}" )
              print(f"Two remaining numbers are {gh} and {ij} (total {gh+ij})")
              break    # to stop repeated answers
      
      # Three factors are 14, 28 and 63
      # Two remaining numbers are 50 and 97 (total 147)
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:48 pm on 18 November 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 510: [Angling competition] 

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

    Announcing that the Baron’s team had won the angling competition, the Mayor told those assembled that the winning catch weighed 19st 1lb 7oz and consisted of plaice, halibut and turbot. He also disclosed the remarkable fact that each fish of the same kind had been precisely the same weight — each plaice 2lb 8oz, each halibut 4lb 1oz and each turbot 6lb 6oz.

    Although His Worship mentioned that half the total number of fish in the winning catch were of one and the same kind and that there were three times as many of one kind as of one other kind, he lamentably failed to give the further details so many were waiting to hear.

    One enthusiast, for example, wished to know the total weight of all the plaice landed by the Baron’s team.

    Can you say what it was? (Answers in lbs).

    This puzzle was originally published with no title.

    [teaser510]

     
    • Jim Randell's avatar

      Jim Randell 9:40 am on 21 November 2019 Permalink | Reply

      Using oz for for measuring weight, we have:

      1 lb = 16 oz
      1 st = 14 lb = 224 oz

      So the total weight of fish is:

      total = 19 st, 1 lb, 7 oz = 4279 oz

      And the individual types of fish weigh:

      plaice = 2 lb, 8 oz = 40 oz
      halibut = 4 lb, 1 oz = 65 oz
      turbot = 6 lb, 6 oz = 102 oz

      This Python program uses the [[ express() ]] function from the enigma.py library. It runs in 110ms.

      from enigma import (express, div, printf)
      
      # find numbers of plaice, halibut, turbot
      for s in express(4279, (40, 65, 102), min_q=1):
        t = sum(s)
        # half the total was of one kind
        h = div(t, 2)
        if h is None or h not in s: continue
        # and there were 3 times as many of one kind than of an other
        if not any(3 * x in s for x in s): continue
        # output solution
        (p, h, t) = s
        printf("plaice = {p}, halibut = {h}, turbot = {t}")
      

      Solution: The total weight of plaice was 82 lb, 8 oz (= 82.5 lb).

      There were 33 plaice (= 1320 oz = 82 lb, 8 oz = 5 st, 12 lb, 8 oz).

      There were 11 halibut (= 715 oz = 44 lb, 11 oz = 3 st, 2 lb, 11 oz).

      There were 22 turbot (= 2244 oz = 140 lb, 4 oz = 10 st, 4 oz).

      Like

  • Unknown's avatar

    Jim Randell 9:57 am on 17 November 2019 Permalink | Reply
    Tags: by: Conor Henderson   

    Brainteaser 1757: Enigma variations 

    From The Sunday Times, 19th May 1996 [link]

    I recently joined Enigma, the international club for puzzle-solvers. When I received my membership card I noticed that my membership number was a six-figure number and that its digits could be rearranged to make three consecutive two-figure numbers.

    I also noticed that its six digits could be rearranged to form two consecutive three-figure numbers.

    Furthermore, no one with a lower six-figure membership number could make the same claims.

    What is my membership number?

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

    [teaser1757]

     
    • Jim Randell's avatar

      Jim Randell 8:51 am on 19 November 2019 Permalink | Reply

      We can examine sequences of 3 consecutive 2-digit numbers (there are only 88 of them), and look for ones whose digits can be rearranged to form 2 consecutive 3-digit numbers. Each of these produces a collection of 6-digit membership numbers, and we can choose the smallest of these.

      This Python program runs in 135ms.

      Run: [ @repl.it ]

      from enigma import irange, tuples, multiset, nsplit, subsets, nconcat, Accumulator, printf
      
      # collect the digits from a sequence of numbers
      digits = lambda ns: multiset.from_seq(*(nsplit(n) for n in ns))
      
      # turn digits ds into n consecutive k-digit numbers
      def consecutive(ds, n, k):
        # extract the first k-digit number
        for s in subsets(ds, size=k, select="mP"):
          if s[0] == 0: continue
          # make the sequence of consecutive numbers
          s = nconcat(s)
          ss = tuple(irange(s, s + n - 1))
          # does it use the same digits?
          if digits(ss) == ds:
            yield ss
      
      # find the smallest number
      r = Accumulator(fn=min)
      
      # consider 3 consecutive 2-digit numbers
      for n2s in tuples(irange(10, 99), 3):
        ds = digits(n2s)
      
        # can the digits be rearranged to form 2 consecutive 3-digit numbers?
        n3s = list(consecutive(ds, 2, 3))
        if not n3s: continue
      
        # construct the smallest possible 6 digit number from the digits
        n = sorted(ds)
        # but leading zeros aren't allowed
        if n[0] == 0:
          # bring the first non-zero digit forward
          n.insert(0, n.pop(next(i for (i, x) in enumerate(n) if x > 0)))
        n = nconcat(n)
      
        printf("[{n:06d} -> {n2s} -> {n3s}]")
        r.accumulate(n)
      
      # output solution
      printf("membership number = {r.value:06d}")
      

      Solution: Your membership number is: 102222.

      The six digits of 102222 can be rearranged as (20, 21, 22) and also (220, 221).

      The largest such number is 999987:

      999987 → (97, 98, 99), (997, 998)

      There are 19 viable sequences of 3 consecutive 2-digit numbers, which give a total of 1195 candidate membership numbers.

      If leading zeros were allowed (which would not be uncommon for membership numbers) the lowest viable membership number would be 012222 (and there would be 1350 candidate numbers).

      Like

  • Unknown's avatar

    Jim Randell 9:06 am on 17 November 2019 Permalink | Reply
    Tags:   

    Brainteaser 1747: Lottery charges 

    From The Sunday Times, 10th March 1996 [link]

    Chargealot are having a new set of lottery balls made. They are going to be painted with numbers from 1 (not 01) to 49. The painter decides that this is one chance to make a fortune from the lottery and so he charges accordingly. He divides the digits into two types and charges a certain whole number of pounds for painting each digit of one type (each time it occurs) and a different whole number of pounds for all the other digits (each time they occur). Both charges are more than £50 but less than £100.

    To make the cost sound reasonable, instead of quoting a price for the whole set he quoted the average price per ball. Of course some balls cost less than average, and some balls cost more, but just one ball’s price was equal to the average.

    What were his two charges per digit?

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

    [teaser1747]

     
    • Jim Randell's avatar

      Jim Randell 9:07 am on 17 November 2019 Permalink | Reply

      We can divide the digits from 0-9 into two sets, “type A” and “type B” with associated costs a and b.

      There are 9 single digit numbers and 40 2-digit numbers, so the 49 numbers use 89 digits in total.

      If, in total, there are nA type A digits and nB type B digits then we have:

      nA + nB = 89
      T = a × nA + b × nB = 49 × m

      where T is the total cost and m is the average cost per ball, and only one of the balls can cost the average amount.

      This Python program runs in 158ms.

      Run: [ @repl.it ]

      from collections import defaultdict
      from enigma import subsets, diff, irange, nsplit, div, printf
      
      # place 0 in set A and choose some other digits to go in set B
      for B in subsets(irange(1, 9), min_size=1):
        A = diff(irange(0, 9), B)
      
        # group the numbers by (A, B) usage
        d = defaultdict(list)
        nA = 0
        for n in irange(1, 49):
          # count the A and B digits
          k = [0, 0]
          for x in nsplit(n):
            k[x in B] += 1
          d[tuple(k)].append(n)
          nA += k[0]
        nB = 89 - nA
      
        # find a number in a class by itself
        for ((i, j), vs) in d.items():
          if len(vs) != 1: continue
      
          # find costs for the digits in A and B
          for a in irange(51, 99):
            b = div(a * (49 * i - nA), nB - 49 * j)
            if b is None or b < 51 or b > 99: continue
      
            # check none of the other groups cost the same as the mean
            m = a * i + b * j
            if any(a * x + b * y == m for (x, y) in d.keys() if not(x == i and y == j)): continue
      
            # output solution
            printf("cost = ({a}, {b}), digits = ({A}, {B}); mean = {m}, number = {vs[0]}")
      

      Solution: Each digit costs £ 74 or £ 83.

      The ball that costs the mean amount is one of the repeated digit balls, i.e. 11, 22, 33, 44. It costs £ 148 (i.e. 2× £ 74).

      The digit used twice on the mean ball is the only digit that costs £ 74, all the other digits cost £ 83.

      The total cost for all the balls is therefore £ 7252.

      Like

  • Unknown's avatar

    Jim Randell 5:15 pm on 15 November 2019 Permalink | Reply
    Tags:   

    Teaser 2982: Antique tea pot 

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

    My local antiques dealer marks each item with a coded price tag in which different digits represent different letters. This enables him to tell the whole number of pounds he paid for the item. I bought a tea pot from him tagged MOE.

    Inside the tea pot was a scrap of paper which I used to work out his code. The letters AMOUNT I SPENT had been rearranged to make the multiplication sum above.

    How much did he pay for the tea pot?

    [teaser2982]

     
    • Jim Randell's avatar

      Jim Randell 5:18 pm on 15 November 2019 Permalink | Reply

      The multiplication is straightforward to solve manually.

      Programatically, we can solve this puzzle using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file executes in 145ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "MAN * PIN = TOOTIN"
      
      "MAN * N = TUON"
      "MAN * I = AASA"
      "MAN * P = TMEP"
      
      --answer="MOE"
      

      Solution: MOE = 350, so the teapot cost £ 350.

      The corresponding digits for AMOUNT I SPENT are 235961 7 84061.

      Like

    • GeoffR's avatar

      GeoffR 3:03 pm on 16 November 2019 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";    
      
      %       M A N
      %    X  P I N 
      %    ---------
      %     T U O N
      %   A A S A
      % T M E P
      %------------
      % T O O T I N
      %-------------
      
      var 0..9: A; var 0..9: M; var 0..9: O; var 0..9: U;
      var 0..9: N; var 0..9: T; var 0..9: I;
      var 0..9: S; var 0..9: P; var 0..9: E;
      
      constraint M > 0 /\ P > 0 /\ T > 0 /\ A > 0;
      
      var 100..999: MAN = 100*M + 10*A + N;
      var 100..999: PIN = 100*P + 10*I + N;
      var 100..999: MOE = 100*M + 10*O + E;
      
      var 1000..9999: TUON = 1000*T + 100*U + 10*O + N;
      
      var 10000..99999: AASA = 11010*A + 100*S;
      
      var 100000..999999: TMEP = 100000*T + 10000*M
      + 1000*E + 100*P;
      
      var 100000..999999: TOOTIN = 100100*T + 11000*O
      + 10*I + N;
      
      constraint MAN * PIN == TOOTIN;
      constraint N * MAN == TUON;
      constraint I * MAN * 10 == AASA;
      constraint P * MAN * 100 == TMEP;
      
      solve satisfy;
      
      output [ "MOE = £" ++ show(MOE) ++ "\n" ++
      "Main sum is " ++ show(MAN) ++ " X " ++
      show(PIN) ++ " = " ++ show(TOOTIN) ];
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:50 am on 12 November 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 509: [Truth and lies] 

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

    Alpha, Beta, and Gamma are brothers. One of them always tells the truth, one always lies, and the other sometimes tells the truth and sometimes lies.

    I asked Gamma who was the elder of Alpha and Beta. Gamma (who was a little shy) whispered the answer to Alpha who the told me that Gamma had said that Alpha was older than Beta. Beta agreed that he was younger than Alpha, but he warned me that Alpha did not always tell the truth.

    The eldest of the three said that Gamma was more honest than Beta, and the youngest agreed with him. Furthermore, the younger of Beta and Gamma said that Alpha always told the truth.

    Who is the eldest? And who is the most honest?

    This puzzle was originally published with no title.

    [teaser509]

     
    • Jim Randell's avatar

      Jim Randell 8:51 am on 12 November 2019 Permalink | Reply

      This Python program runs in 70ms.

      Run: [ @replit ]

      from enigma import (subsets, printf)
      
      # the possible behaviours:
      
      # T tells the truth
      def T(s):
        return bool(s)
      
      # F lies
      def F(s):
        return not s
      
      # U is unreliable
      def U(s):
        return True
      
      # honesty ratings
      honest = { F: 0, U: 1, T: 2 }
      
      # assign behaviours to A, B, G
      for (fA, fB, fG) in subsets((T, F, U), size=3, select="P"):
      
        # "B said A does not always tell the truth"
        if not fB(fA != T): continue
      
        # choose an age ordering for A, B, G
        for (aA, aB, aG) in subsets((0, 1, 2), size=3, select="P"):
      
          # "B said B was younger than A"
          if not fB(aB < aA): continue
      
          # "the younger of B and G said that A always told the truth"
          if not (fB if aB < aG else fG)(fA == T): continue
      
          # "A said that G said that A was older than B"
          if not fA(fG(aA > aB)): continue
      
          # map ages to behaviours
          m = dict(zip((aA, aB, aG), (fA, fB, fG)))
      
          # "the eldest said G was more honest than B"
          # "the youngest agreed G was more honest than B"
          s = (honest[fG] > honest[fB])
          if not (m[2](s) and m[0](s)): continue
      
          # output solution
          (A, B, G) = (x.__name__ for x in (fA, fB, fG))
          (a, b, g) = (["youngest", "middle", "eldest"][x] for x in (aA, aB, aG))
          printf("A={A}, {a}; B={B}, {b}; G={G}, {g}")
      

      Solution: Alpha is the eldest. Beta is the most honest.

      Alpha is the eldest, and sometimes tells the truth and sometimes lies.

      Beta is the middle, and always tells the truth.

      Gamma is the youngest, and always lies.

      Like

  • 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

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