Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:49 am on 30 October 2020 Permalink | Reply
    Tags:   

    Teaser 3032: Darts display 

    From The Sunday Times, 1st November 2020 [link] [link]

    I noticed a dartboard in a sports shop window recently. Three sets of darts were positioned on the board. Each set was grouped as if the darts had been thrown into adjacent numbers (e.g., 5, 20, 1) with one dart from each set in a treble. There were no darts in any of the doubles or bulls.

    The darts were in nine different numbers but the score for the three sets was the same. If I told you whether the score was odd or even you should be able to work out the score. The clockwise order of numbers on a dartboard is:

    20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5

    What was the score that all three sets of darts made?

    [teaser3032]

     
    • Jim Randell's avatar

      Jim Randell 10:05 am on 30 October 2020 Permalink | Reply

      This Python program runs in 49ms.

      Run: [ @repl.it ]

      from collections import defaultdict
      from enigma import tuples, unpack, subsets, union, filter_unique, printf
      
      # the scores on the dartboard
      board = (20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5)
      
      # group possible scores together
      d = defaultdict(list)
      for (a, b, c) in tuples(board, 3, circular=1):
        for k in (3 * a + b + c, a + 3 * b + c, a + b + 3 * c):
          d[k].append((a, b, c))
      
      # find scores with three disjoint sets of sectors
      rs = list()
      for (k, vs) in d.items():
        for sss in subsets(vs, size=3):
          if len(union(sss)) == 9:
            rs.append((k, sss))
      
      # find scores unique by parity
      rs = filter_unique(rs, unpack(lambda k, sss: k % 2), unpack(lambda k, sss: k)).unique
      
      # output solution
      for (k, sss) in rs:
        printf("score = {k} {sss}")
      

      Solution: The score was 56.

      The three groups of darts are:

      2; treble 17; 3
      19; treble 7; 16
      treble 11; 14; 9

      This is the only disjoint collection with an even score.

      It is possible to make odd scores of 43, 47, 61.

      And the score of 61 can be made in 4 different ways, so is the answer to a variation on the puzzle where: “if I told you the score, you still wouldn’t be able to work out the set of sectors where the darts were placed”.

      Like

  • Unknown's avatar

    Jim Randell 12:17 pm on 29 October 2020 Permalink | Reply
    Tags:   

    Teaser 1928: Prime of life 

    From The Sunday Times, 29th August 1999 [link]

    Today my daughter was looking through her old scrapbook and came across a rhyme I had written about her when she was a little girl:

    Here’s a mathematical rhyme:
    Your age in years is a prime;
    Mine is too,
    And if you add the two
    The answer’s a square — how sublime!

    She was surprised to find that this is also all true today. Furthermore is will all be true again some years hence.

    How old are my daughter and I?

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

    [teaser1928]

     
    • Jim Randell's avatar

      Jim Randell 12:18 pm on 29 October 2020 Permalink | Reply

      When rhyme was written the daughter’s age was some prime d, the father’s age some prime f, and (d + f) is a square number.

      Unless they were born on the same day at the same time one of them have their next birthday before the other.

      If it is the daughter, the ages progress like this:

      (d, f) → (d + 1, f) → (d + 1, f + 1) → (d + 2, f + 1) → (d + 2, f + 2) …

      And if the father has the next birthday, the ages progress like this:

      (d, f) → (d, f + 1) → (d + 1, f + 1) → (d + 1, f + 2) → (d + 2, f + 2) …

      So we just need to examine these sequences to look for two more occurrences of the ages being prime, and their sum being a square.

      This Python program runs in 44ms.

      from enigma import (primes, is_square, printf)
      
      # primes for ages
      primes.expand(150)
      
      # check for viable ages
      check = lambda a, b: a in primes and b in primes and is_square(a + b)
      
      # extend the list to length k, with birthdays a then b
      def extend(a, b, k=3):
        rs = [(a, b)]
        while a < 150 and b < 150:
          a += 1
          if check(a, b): rs.append((a, b))
          b += 1
          if check(a, b): rs.append((a, b))
          if not (len(rs) < k): return rs
      
      # consider the daughter's at the time of the rhyme
      for d in primes.between(2, 16):
      
        # now consider possible ages for the father
        for f in primes.between(d + 16, 50):
          # together they make a square
          if not is_square(d + f): continue
      
          # solutions where daughters birthday is next
          rs = extend(d, f)
          if rs: printf("d -> f: {rs}")
      
          # solutions where fathers birthday is next
          rs = extend(f, d)
          if rs: printf("f -> d: {rs}")
      

      Solution: Today the daughter is 7 and the father is 29.

      The three pairs of ages are:

      d = 2, f = 23: d + f = 25 = 5²
      d = 7, f = 29: d + f = 36 = 6²
      d = 61, f = 83: d + f = 144 = 12²

      So father’s birthday came first after the rhyme was written.

      Like

    • Frits's avatar

      Frits 1:44 pm on 29 October 2020 Permalink | Reply

      from enigma import SubstitutedExpression, is_prime, is_square
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [# father (age CDE) is a lot older than daughter (age AB)
         "AB + 15 < CDE",
         "PQ > 0",
         "RS > 0",
         # daughter must have already been born PQ years ago (and prime)
         # "when she was a little girl"
         "0 < AB - PQ < 10",
         # maximum age father
         "CDE < 125",
         "CDE + RS < 125",
         
         # PQ years ago
         "is_prime(AB - PQ - w)",
         "is_prime(CDE - PQ - x)",
         "is_square(AB + CDE - 2 * PQ - w - x)",
         # now
         "is_prime(AB)",
         "is_prime(CDE)",
         "is_square(AB + CDE)",
         # RS years later
         "is_prime(AB + RS + y)",
         "is_prime(CDE + RS + z)",
         "is_square(AB + CDE + 2 * RS + y + z)",
         
         # uncertainty bits, not both of them may be true
         "x + w < 2",
         "y + z < 2",
        ],
        answer="AB, CDE, PQ, RS, w, x, y, z",
        symbols="ABCDEPQRSwxyz",
        verbose=0,
        d2i=dict([(k, "wxyz") for k in {2,3,4,5,6,7,8,9}]),
        distinct="",   # allow variables with same values
        #reorder=0,
      )
      
      # Print answer
      for (_, ans) in p.solve():
        print(f"ages daughter {ans[0] - ans[2] - ans[4]} {ans[0]} "
              f"{ans[0] + ans[3] + ans[6]}")
        print(f"ages father   {ans[1] - ans[2]  - ans[5]} "
              f"{ans[1]} {ans[1] + ans[3]  + ans[7]}")
        print(f"squares       {ans[0] + ans[1] - 2*ans[2]  - ans[4] - ans[5]} "
              f"{ans[0] + ans[1]} {ans[0] + ans[1] + 2*ans[3] + ans[6] + ans[7]}")
        
        
      # ages daughter 2 7 61
      # ages father   23 29 83
      # squares       25 36 144
      

      Like

    • Frits's avatar

      Frits 4:16 pm on 29 October 2020 Permalink | Reply

      from enigma import printf
      
      # Prime numbers up to 124
      P =  [2, 3, 5, 7]
      P += [x for x in range(11, 100, 2) if all(x % p for p in P)]
      P += [x for x in range(101, 125, 2) if all(x % p for p in P)]
      
      # report two prime numbers (ascending) which sum to <n>
      def twoprimes(n, dif=0): 
        li = []
        i = 1
        p1 = 2 
        while(p1 < n - p1): 
          if n - p1 in P:
            # difference between primes is not exact
            if dif == 0 or (dif - 1 <= abs(n - 2 * p1) <= dif + 1): 
              li.append([p1, n - p1])
          p1 = P[i]
          i += 1
        return li  
      
      # square candidates for when daughter was a little girl   
      for i in range(4, 11):  # also allowing for people like B. Ecclestone
        for t1 in twoprimes(i * i): 
          # "when she was a little girl"
          if t1[0] > 9: continue
          dif1 = int(t1[1]) - int(t1[0])
          # square candidates for now
          for j in range(i + 1, 15):
            for t2 in twoprimes(j * j, dif1): 
              # square candidates for in te future
              for k in range(j + 1, 16):
                for t3 in twoprimes(k * k, dif1): 
                  printf("{t1[0]} + {t1[1]} = {su}", su = int(t1[0]) + int(t1[1]))
                  printf("{t2[0]} + {t2[1]} = {su}", su = int(t2[0]) + int(t2[1]))
                  printf("{t3[0]} + {t3[1]} = {su}", su = int(t3[0]) + int(t3[1]))
      

      Like

    • GeoffR's avatar

      GeoffR 9:23 pm on 29 October 2020 Permalink | Reply

      % A Solution in MiniZinc     
      include "globals.mzn";
      
      % Three Daughters' ages
      % D1 = young girl, D2 = age now, D3 = future age
      var 1..10:D1; var 1..50:D2; var 1..80:D3;
      
      % Three corresponding Fathers ages
      var 13..40:F1; var 13..70:F2; var 13..105:F3;
      
      % All Fathers and Daughters ages are different
      constraint all_different ([F1, F2, F3, D1, D2, D3]);
      
      set of int: primes = {2, 3, 5, 7, 11, 13, 17,
      19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
      71, 73, 79, 83, 89, 97, 101, 103};
      
      set of int : squares = {16, 25, 36, 49, 64, 81,
       100, 121, 144, 169};
      
      % Age group constraints
      constraint F1 - D1 > 12 /\ D1 < 10 ;
      constraint D2 > D1 /\ D3 > D2;
      constraint F2 > F1 /\ F3 > F2;
      constraint F1 > D1 /\ F2 > D2 /\ F3 > D3;
      
      % 1st age differences between Father and Daughter
      constraint D2 - D1 = F2 - F1 + 1
      \/ D2 - D1 = F2 - F1 - 1;
      
      % Age difference is the same between the 2nd and 3rd age groups
      constraint F2 - D2 == F3 - D3;
      constraint F3 - F2 == D3 - D2;
      
      % All ages are prime numbers
      constraint F1 in primes /\ F2 in primes /\ F3 in primes;
      constraint D1 in primes /\ D2 in primes /\ D3 in primes;
      
      % All father and daughter group sums are squares
      constraint (F1 + D1) in squares /\ (F2 + D2) in squares 
      /\ (F3 + D3) in squares;
      
      solve satisfy;
      
      output[ "Father's ages in sequence are " ++ show(F1) ++
       ", " ++ show(F2) ++ ", " ++ show(F3)  
      ++"\nDaughters ages in sequence are " ++ show(D1) ++
       ", " ++ show(D2) ++  ", " ++ show(D3) ];
      
      % Father's ages in sequence are 23, 29, 83
      % Daughters ages in sequence are 2, 7, 61
      % ----------
      % ==========
      
      
      

      Like

      • Frits's avatar

        Frits 10:25 am on 30 October 2020 Permalink | Reply

        I feel lines 11 and 12 are too restrictive. However, removing them gives the same result.

        Like

    • GeoffR's avatar

      GeoffR 5:16 pm on 30 October 2020 Permalink | Reply

      @Frits:
      The constraint programming approach is to search for a state of the world in which a large number of constraints are satisfied at the same time. But we cannnot predict in advance which constraints are required for a solution. It is, of course, a different approach to imperative programming e.g. Python.

      It is therefore possible to cancel one or two constraints and still get a solution.
      We soon know if we have not got sufficient constraints when we don’t get a solution.

      Lines 11 and 12 are logical in that we know over several time scales that all the fathers and daughters ages will be different, so it is quite OK to make this a constraint, in my view.

      As an experiment, I remmed out Lines 22, 23 and 24, leaving Line 12 functional. This still gave the correct solution. I then remmed out Line 12 and I got three solutions – one correct and two wrong.
      This shows that Line 12 constraint is related to the constraints on Lines 22,23 and 24 in functionality.

      So we could equally say that Lines 22,23 and 24 could be removed, providing Line 12 was not removed.

      I think all the constraints I used have a reasonable basis, so it is probably best not to tinker with individual constraints and let constraint programming use its own internal procedures to find a solution.

      Like

    • Frits's avatar

      Frits 11:26 am on 31 October 2020 Permalink | Reply

      @GeoffR,

      My only point is that this particular constraint is not part of the requirements and can potentially give cause to miss certain solutions.

      You say that “we know over several time scales that all the fathers and daughters ages will be different”. I think it is possible the daughter “today” can have the same age as her father at the time she was a little girl (same for later on).

      Example (if we forget about primes):

      Father’s ages in sequence are 23, 44, 83
      Daughters ages in sequence are 2, 23, 62

      You may be right if the line 11/12 constraint emerges from all the requirements but that is not immediately clear to me.

      Like

  • Unknown's avatar

    Jim Randell 2:58 pm on 27 October 2020 Permalink | Reply
    Tags:   

    Teaser 2761: Digital shuffle 

    From The Sunday Times, 23rd August 2015 [link] [link]

    George and Martha have nine cards with a different non-zero digit on each. To teach their nephew to count they lined up the cards in increasing order. He then rearranged the order of the line and Martha was impressed when she noticed that no digit was in its original position. George was even more impressed when he found that the six-figure number formed by the last six cards was the square of the three-figure number formed by the first three.

    What was that three-figure number?

    [teaser2761]

     
    • Jim Randell's avatar

      Jim Randell 2:59 pm on 27 October 2020 Permalink | Reply

      We can use the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this alphametic puzzle.

      The following run file executes in 99ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      # non-zero digits
      --digits="1-9"
      
      # no digit is in its original position
      --invalid="1,A"
      --invalid="2,B"
      --invalid="3,C"
      --invalid="4,D"
      --invalid="5,E"
      --invalid="6,F"
      --invalid="7,G"
      --invalid="8,H"
      --invalid="9,I"
      
      "sq(ABC) = DEFGHI"
      
      --answer="ABC"
      

      Solution: The three-figure number was 854.

      Giving:

      854² = 729316

      If we remove the condition that “no digit is in its original position”, we find there is a further solution to the alphametic of:

      567² = 321489

      But this is disallowed as the digits 8 and 9 are in positions 8 and 9.

      Like

    • GeoffR's avatar

      GeoffR 4:23 pm on 27 October 2020 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D; 
      var 1..9:E; var 1..9:F; var 1..9:G; var 1..9:H;
      var 1..9:I;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I]);
      
      % Nine cards not in original position
      constraint A != 1 /\ B != 2 /\ C != 3
      /\ D != 4 /\ E != 5 /\ F != 6 
      /\ G != 7 /\ H != 8 /\ I != 9;
      
      % Number formed by 1st three cards
      var 100..999: ABC = 100*A + 10*B + C;
      
      % Number formed by last six cards
      var 100000..999999: DEFGHI = 100000*D + 10000*E
      + 1000*F + 100*G + 10*H + I;
      
      %  the six-figure number formed by the last six cards was
      % the square of the three-figure number formed by the first three
      constraint DEFGHI = ABC * ABC;
      
      solve satisfy;
      
      output ["Three figure number was " ++ show(ABC)
      ++"\nSix figure number was " ++ show(DEFGHI) ];
      
      % Three figure number was 854
      % Six figure number was 729316
      % ----------
      % ==========
      
      
      

      Like

    • Frits's avatar

      Frits 10:47 am on 28 October 2020 Permalink | Reply

      from itertools import dropwhile, count
       
      # return True if not all rules are met 
      def wrongSquare(trueList):
        
        # check if number meets the rules
        def OK(x):
          # booleans
          bs = trueList.copy()
          
          i = 0
          while x and i < 9:
            y = x % 10
            # no derangement
            if y != 9 - i:
              bs[y] = False
            x //= 10
            i += 1
          # all digits 1-9 used and no zero   
          return sum(bs) == 1 and bs[0]
          
        return lambda n: not OK(1000000 * n + (n * n)) 
       
      trueList = [True] * 10
      sol = next(dropwhile(wrongSquare(trueList), count(317)))
      print(f"{sol}^2 = {sol*sol}") 
      

      Like

    • GeoffR's avatar

      GeoffR 1:43 pm on 28 October 2020 Permalink | Reply

      
      from itertools import permutations
      
      for p1 in permutations('123456789'):
          a, b, c, d, e, f, g, h, i = p1
          # check numbers not in increasing order
          # no digit is in its original position
          if (a == '1' or b == '2' or c == '3' or
          d == '4' or e == '5' or f == '6' or
          g == '7' or g == '8' or i == '9'):
              continue
          # form 3-digit and 6-digit numbers
          abc = int(a + b + c)
          defghi = int(d + e + f + g + h + i)
          if abc ** 2 == defghi:
              print(f"Three digit number is {abc}")
              print(f"Six digit number is {defghi}")
              
      # Three digit number is 854
      # Six digit number is 729316
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:57 am on 25 October 2020 Permalink | Reply
    Tags:   

    Teaser 1923: Get back to me 

    From The Sunday Times, 25th July 1999 [link]

    I met a nice girl at a party and asked for her phone number. To prove that she was no pushover she made me work for it.

    “My number has seven digits, all different”, she told me. “If you form the largest number you can with those seven digits and subtract from it the reverse of that largest number, then you get another seven-digit number”, she added.

    “Then if you repeat the process with that new seven-digit number, you get another seven-digit number”, she added. “And if you repeat the process enough times you’ll get back to my phone number”.

    This information did enable me to get back to her!

    What is her telephone number?

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

    [teaser1923]

     
    • Jim Randell's avatar

      Jim Randell 8:58 am on 25 October 2020 Permalink | Reply

      When we apply the process to a number the order of the digits does not matter, as we immediately reorder the digits to make the largest possible number, and it’s reverse. So we only need to consider the possible collections of 7 distinct digits. If the same set of digits pops out of the process after several applications we have found a solution.

      As we keep applying the process we must eventually get to a collection of digits we have seen before (as there are only a finite number of collections of 7 digits). So if we haven’t found a solution by the time we get a repeated set of digits, then we are never going to find one.

      This Python program runs in 53ms.

      Run: [ @repl.it ]

      from enigma import subsets, irange, inf, nconcat, nsplit, ordered, printf
      
      # check the process, using a sequence of 7 ordered digits
      def check(ds):
        seen = set()
        ss = ds
        for i in irange(1, inf):
          if ss in seen: return None
          seen.add(ss)
          # make the largest number, and it's reverse
          n = nconcat(ss[::-1])
          r = nconcat(ss)
          # and subtract them
          s = n - r
          # are the digits in s the starting digits?
          ss = ordered(*(nsplit(s)))
          if len(ss) != 7: return None
          if i > 2 and ss == ds: return s
      
      # choose the 7 different digits in the phone number
      for ds in subsets(irange(0, 9), size=7):
        n = check(ds)
        if n is not None:
          printf("number = {n:07d}")
      

      Solution: The telephone number is: 7509843.

      Repeated application of the process yields:

      7509843: 9875430 − 0345789 = 9529641
      9529641: 9965421 − 1245699 = 8719722
      8719722: 9877221 − 1227789 = 8649432
      8649432: 9864432 − 2344689 = 7519743
      7519743: 9775431 − 1345779 = 8429652
      8429652: 9865422 − 2245689 = 7619733
      7619733: 9776331 − 1336779 = 8439552
      8439552: 9855432 − 2345589 = 7509843

      So we have to apply the process 8 times.

      Like

    • Frits's avatar

      Frits 2:24 pm on 27 October 2020 Permalink | Reply

      # nice girl's phone number abcdefg
      #
      # last transformation:
      #
      # h1h2h3ml3l2l1 = abcdefg + l1l2l3mh3h2h1
      #
      # c1...c5 carry bits
      #
      # h1 = a + l1
      # h2 = b + l2 + c5
      # h3 = c + l3 + c4 - 10*c5
      # m  = d + m  + c3 - 10*c4
      # l3 = e + h3 + c2 - 10*c3
      # l2 = f + h2 + c1 - 10*c2
      # l1 = g + h1 - 10*c1
      #
      # l1 < h1 --> c1 = 1
      # l2 < h2 + 1 --> c2 = 1
      # l3 < h3 + 1 --> c3 = 1
      # m = d + m + 1 - 10*c4 --> d = 10*c4 - 1 --> c4 = 1 --> d = 9
      #
      # h1 = a + l1 = a + g + h1 - 10 --> a + g = 10
      # h2 = b + l2 + c5 = b + f + h2 - 9 + c5 --> b + f = 9 - c5
      # h3 = c + l3 + 1 - 10*c5 = c + e + h3 - 9 + 1 - 10*c5 --> c + e = 8 + 10*c5
      # --> c5 = 0 and c + e = 8
      

      With A+G = 10, B+F = 9, C+E = 8, D = 9 enigma [[SubstitutedExpression]] leaves 64 ABCDEFG combinations to check.

      Like

      • Frits's avatar

        Frits 2:47 pm on 27 October 2020 Permalink | Reply

        Don’t know why I didn’t use carry bit c6 but the result is the same (c6 = 0).

        Like

  • Unknown's avatar

    Jim Randell 4:43 pm on 23 October 2020 Permalink | Reply
    Tags:   

    Teaser 3031: End of the beginning 

    From The Sunday Times, 25th October 2020 [link] [link]

    Jenny is using her calculator, which accepts the input of numbers of up to ten digits in length, to prepare her lesson plan on large numbers. She can’t understand why the results being shown are smaller than she expected until she realizes that she has entered a number incorrectly.

    She has entered the number with its first digit being incorrectly entered as its last digit. The number has been entered with its second digit first, its third digit second etc. and what should have been the first digit entered last. The number she actually entered into her calculator was 25/43rds of what it should have been.

    What is the correct number?

    [teaser3031]

     
    • Jim Randell's avatar

      Jim Randell 4:59 pm on 23 October 2020 Permalink | Reply

      See also: Enigma 1036, Enigma 1161, Teaser 2565.

      If we have a number, where the leading digit is a and the remaining k digits are bcdefg… = r, then we have:

      r.10 + a = (25/43)(a.10^k + r)

      The following Python program runs in 45ms.

      Run: [ @replit ]

      from enigma import (irange, div, int2base, printf)
      
      # consider k digit numbers
      for k in irange(1, 9):
        m = 10 ** k
        # consider possible leading digit
        for a in irange(1, 9):
          r = div(a * (25 * m - 43), 405)
          if r is None or not (r < m): continue
          # output solution
          printf("{a}{r} -> {r}{a}", r=int2base(r, width=k))
      

      Solution: The correct number is: 530864197.

      So the number entered is: 308641975.

      >>> fraction(308641975, 530864197)
      (25, 43)
      

      For this particular puzzle we can do some analysis can reduce the solution space further. (From the 9×9 = 81 cases to consider in the general case).

      In the equation:

      43(r.10 + a) = 25(a.10^k + r)

      we see that that each side is divisible by 5, so a must be 5 (as it cannot be 0).

      Which leaves:

      r = (25.10^k − 43) / 81

      Which we can then check with the 9 possible values for k manually or with an even shorter program:

      from enigma import (irange, div, int2base, printf)
      
      for k in irange(1, 9):
        r = div(25 * 10 ** k - 43, 81)
        if r is not None:
          printf("5{r} -> {r}5", r=int2base(r, width=k))
      

      Or, for a complete manual solution:

      Numbers of the form: (25.10^k − 43) / 9, look like this:

      k=1: 23
      k=2: 273
      k=3: 2773
      k=4: 27773

      To divide by 9 again we need the number to have a digital root of 9, and the only one in the required range is:

      k=8: 277777773

      Dividing this by 9 gives:

      r = 277777773 / 9 = 30864197

      Hence the correct number is 530864197, and the incorrect number 308641975.

      Like

      • hans droog's avatar

        hans droog 10:01 am on 6 November 2020 Permalink | Reply

        Hi Jim. would be obliged if you could explain formula in teaser 3031. Many thanks, Hans Droog

        Like

        • Jim Randell's avatar

          Jim Randell 10:10 am on 6 November 2020 Permalink | Reply

          @hans:

          As an example, if we had an 7 digit number abcdefg which was entered incorrectly on the calculator as bcdefga, then we would have:

          bcdefga = (25/43)(abcdefg)

          If we represent the 6 digit number bcdefg as r we can write this expression as:

          r.10 + a = (25/43)(a.10^6 + r)

          The expression I give in my solution is the general case when r is a k digit number.

          (“.” is multiplication. “^” is exponentiation).

          Is that clear?

          Like

    • Frits's avatar

      Frits 1:42 pm on 24 October 2020 Permalink | Reply

      Similar.

      print(["5"+str(lastpart // 81) for k in range(2, 10) 
            if (lastpart := (25 * 10**k - 43)) % 81 == 0][0])
      

      Like

    • Hugh Casement's avatar

      Hugh Casement 4:15 pm on 7 November 2020 Permalink | Reply

      Hans may have been put off by Jim’s use of a decimal point to mean multiplication.
      The international convention is a raised dot.

      Like

      • Jim Randell's avatar

        Jim Randell 10:13 am on 8 November 2020 Permalink | Reply

        If I were handwriting a decimal number I would use a “middle dot” for the decimal point, and a dot on the line to denote multiplication. Of course when typing the “.” (full stop) symbol has to do for both.

        Fortunately we don’t often have to deal with numbers with decimal points in puzzles.

        Like

    • hans droog's avatar

      hans droog 9:01 am on 8 November 2020 Permalink | Reply

      thats right Hugh

      Like

  • Unknown's avatar

    Jim Randell 8:59 am on 22 October 2020 Permalink | Reply
    Tags:   

    Teaser 2760: Prime location 

    From The Sunday Times, 16th August 2015 [link] [link]

    I have a rectangular garden of area just over one hectare. It is divided exactly into three parts — a lawn, a flower bed, and a vegetable patch. Each of these three areas is a right-angled triangle with sides a whole numbers of metres in length. A fence runs along two adjacent sides of the rectangular garden. The length of the fence is a prime number of metres.

    What are the dimensions of the rectangular garden?

    Note: 1 hectare = 10,000 square metres.

    [teaser2760]

     
    • Jim Randell's avatar

      Jim Randell 9:00 am on 22 October 2020 Permalink | Reply

      (See also: Enigma 1032).

      In order to fit together the three right-angled triangles have to be similar. So the larger triangles are scaled up versions of the smallest triangle.

      If we have three triangles with sides:

      (x, y, z)
      (y, y²/x, yz/x)
      (z, yz/x, z²/x)

      They can be fitted together to give two different rectangles:

      The total area of the three triangles is: yz² / x.

      and the dimensions of the rectangle are: (yz/x, z) or (y, z²/x).

      This Python program scales up Pythagorean Triples to find three triangles with the required area that fit together to give a prime semi-perimeter.

      It runs in 45ms.

      Run: [ @repl.it ]

      from enigma import pythagorean_triples, is_prime, div, as_int, fcompose, printf
      
      # make div throw an exception on inexact division
      ediv = fcompose(div, as_int)
      
      # generate pythagorean triples
      for (x, y, z) in pythagorean_triples(1000):
      
        # construct the triangles
        try:
          (yy_x, yz_x, zz_x) = (ediv(y * y, x), ediv(y * z, x), ediv(z * z, x))
        except ValueError:
          continue
      
        # check area
        A = y * zz_x
        if not(10000 < A < 12000): continue
      
        # consider the two possible arrangements
        for (i, (a, b)) in enumerate([(z, yz_x), (y, zz_x)], start=1):
          if is_prime(a + b):
            printf("rectangle = {a} x {b} [arrangement {i}]")
      

      Solution: The garden is 60m × 169m.

      The triangles are: (25, 60, 65), (60, 144, 156), (65, 156, 169), each of which is a (5, 12, 13) triangle, and they are scaled up by factors of (5, 12, 13).

      And they are arranged as in the second diagram, to give a 60 × 169 rectangle, with area 10140, and 10,140m² is 1.014 hectares. The semi-perimeter 60 + 169 = 229 is prime.

      The alternate arrangement has the same area (of course), but the semi-perimeter 65 + 156 = 221 is composite.

      Like

    • Frits's avatar

      Frits 1:00 pm on 22 October 2020 Permalink | Reply

      Assuming the garden is not very narrow (with sides more than 9 meters).
      We can then use variables AB and CDE as sides.

      We also assume there are only 2 arrangements as stated by Jim.

      from enigma import  SubstitutedExpression, is_prime
      
      p = SubstitutedExpression([
          # Area AB x CDE just over 10000
          "AB * CDE > 10000",
          "AB * CDE <= 11000",
      
          # sum of 2 adjacent sides is prime
          "is_prime(AB + CDE)",
          
          # setup 1
          #
          #        CDE
          #    +------------+
          #    | \LM        |
          #    |  / \ NOP   |
          # AB | /JK   \    |
          #    |/         \ |
          #    +------------+
          #    
          # force variables to be zero if no solution exists
          "a * FGHI == FGHI",
          "a * NOP == NOP",
          "a * JK == JK",
          "a * LM == LM",
          # JK >= 1
          "a * JK >= a", 
      
          # diagonal FGHI
          "a*(AB**2 + CDE**2) == a*(FGHI**2)",
          "a*(LM**2 + JK**2)  == a*(AB**2)",
          "a*(NOP**2 + JK**2) == a*(CDE**2)",
          
          # setup 2
          #
          #        CDE
          #    +-----------+
          #    |\       ./ | 
          # AB | \XYZ ./QRST
          #    |  \ ./     |
          #    +---v-------+
          #     UVW   
          #
          # UVW >= 1
          "b * UVW >= b",
          # force variables to be zero if no solution exists
          "b * UVW == UVW",
          "b * QRST == QRST",
          "b * XYZ == XYZ",
          
          # UVW is the smaller part of CDE
          "b*(2 * UVW) <= b*(CDE)",
      
          "b*(AB**2 + UVW**2)         == b*(XYZ**2)",
          "b*((CDE - UVW)**2 + AB**2) == b*(QRST**2)",
          "b*(XYZ**2 + QRST**2)       == b*(CDE**2)",
          
          # we need at least one solution
          "a + b > 0",
          ],
          verbose=0,
          symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZab",
          d2i=dict([(0, "AC")]  + [(k, "ab") for k in range(2,10)]),
          answer="AB * CDE, AB, CDE, (JK, LM, NOP, FGHI), (UVW, XYZ, QRST), a, b",
          distinct="",
          #reorder=0
      )
      
      # Print answers
      print("  area  AB  CDE       JK  LM NOP FGHI  UVW XYZ QRST")
      for (_, ans) in p.solve():
        ans = list(ans)
        print(f"{ans[:3]}", end="")
        if ans[5] == 1:
          print(f"{str(ans[3]):>22}")
          if ans[6] == 1:
            print(f" {ans[4]}")
        else:
          print(f"{'':<22} {ans[4]}")
        print()  
        
      #   area  AB  CDE       JK  LM NOP FGHI  UVW XYZ QRST
      # [10140, 60, 169]                       (25, 65, 156)  
      

      Like

      • Frits's avatar

        Frits 2:39 pm on 22 October 2020 Permalink | Reply

        Triangles AB, UVW, XYZ and AB, QRST, (CDE -UVW) have similar angles.
        So UVW / AB is equal to AB / (CDE – UVW).

        The following can be added for faster execution:

        "b*(UVW * (CDE - UVW)) == b * AB**2",
        

        Like

  • Unknown's avatar

    Jim Randell 9:02 am on 20 October 2020 Permalink | Reply
    Tags:   

    Teaser 1917: Trick shots 

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

    Joe’s billiard table is of high quality but slightly oversized. It is 14 ½ feet long by 7 feet wide, with the usual six pockets, one in each corner and one in the middle of each long side.

    Joe’s ego is also slightly oversized and he likes to show off with his trick shots. One of the most spectacular is to place a ball at a point equidistant from each of the longer sides and 19 inches from the end nearest to him. He then strikes the ball so that it bounces once off each of the four sides and into the middle pocket on his left.

    He has found that he has a choice of directions in which to hit the ball in order the achieve this effect.

    (a) How many different directions will work?
    (b) How far does the ball travel in each case?

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

    [teaser1917]

     
    • Jim Randell's avatar

      Jim Randell 9:03 am on 20 October 2020 Permalink | Reply

      As with beams of light, it is easier to mirror the table and allow the ball to travel in a straight line. (See: Enigma 1039, Enigma 1532, Teaser 2503).

      If we mirror the table along all four sides we get a grid-like pattern of tables and the path of the ball becomes a straight line between the source and the target pocket.

      The line must cross each colour side once. So vertically it must cross a green and a red (in some order) before ending up in the pocket. Which means only an upward line will do. Horizontally it must cross an orange and a blue line before hitting the pocket. The two possible paths are indicated in this diagram:

      We can then calculate the distances involved. In both cases the vertical distance is 2.5 widths = 210 inches.

      And the horizontal distances are: 2.5 lengths − 19 inches = 416 inches, and: 1.5 lengths + 19 inches = 280 inches.

      The required distances are then:

      >>> hypot(210, 416)
      466.0
      >>> hypot(210, 280)
      350.0
      

      Solution: (a) There are 2 directions which will work; (b) In once case the ball travels: 38 feet, 10 inches; in the other case: 29 feet, 2 inches.

      The paths of the ball on the table look like this:


      Like

  • Unknown's avatar

    Jim Randell 10:34 am on 18 October 2020 Permalink | Reply
    Tags:   

    Brain-Teaser 11: Circulation poor 

    From The Sunday Times, 7th May 1961 [link]

    Lazy Jack was engaged to deliver a circular to every house in the district. He found the supply of circulars would divide into fourteen equal batches of rather more than 300 each, so he decided to deliver one batch each day and thus spread the job over a fortnight.

    On the first day he faithfully distributed circulars one to a house, but that proved very tiring, so the next day he delivered two at each house he visited. With a fine sense of fairness, he never delivered to the same house twice, and one each succeeding day he chose the next smaller number of houses to visit that would enable him exactly to exhaust the day’s batch by delivering an equal number of circulars at each house. The fourteenth day’s batch of circulars all went through one letter box.

    To how many houses was delivery made?

    [teaser11]

     
    • Jim Randell's avatar

      Jim Randell 10:35 am on 18 October 2020 Permalink | Reply

      On each of 14 days, Jack delivers n leaflets:

      On the first day 1 leaflet to each of n houses.
      On the second day 2 leaflets to each of(n / 2) houses.

      On the 14th day n leaflets to 1 house.

      So we are looking for n, an even number, greater than 300, with exactly 14 divisors.

      We can then calculate the total number of houses visited by determining the sum of the divisors.

      This Python program runs in 45ms.

      Run: [ @repl.it ]

      from enigma import (irange, inf, divisors, printf)
      
      for n in irange(302, inf, step=2):
        ds = divisors(n)
        if len(ds) == 14:
          t = sum(ds)
          printf("t={t} [n={n} ds={ds}]")
          break
      

      Solution: Delivery was made to 762 houses.

      And there were 14×320 = 4480 leaflets to deliver.


      We can find numbers with exactly k divisors by considering factorisations of k (see: Enigma 1226).

      In this case, 14 = 1 × 14 = 2 × 7, so any number of the form:

      n = p^13
      n = p^1 × q^6

      for primes, p and q, will have exactly 14 divisors. [*]

      And we know one of the primes is 2.

      2^13 = 8192, which is too big.

      So we know: n = p × q^6.

      If p = 2, then the smallest possible values for n is 2 × 3^6 = 1458, which is also too big.

      So q = 2, and possible values for n are:

      3 × 2^6 = 192
      5 × 2^6 = 320
      7 × 2^6 = 448

      The only sensible candidate is: n = 320, the 14 divisors are:

      1, 2, 4, 5, 8, 10, 16, 20, 32, 40, 64, 80, 160, 320

      and their sum is 762.

      The sum can also be worked out directly from the prime factorisation of n:

      If σ(n) = the sum of the divisors of n, we have:

      σ(5 × 2^6) = σ(5) × σ(2^6)
      = (1 + 5) × (2^7 − 1)
      = 6 × 127
      = 762


      [*] In the published solution it is claimed that only numbers of the form (p^6 × q) have exactly 14 divisors.

      Like

    • Frits's avatar

      Frits 10:28 pm on 18 October 2020 Permalink | Reply

      Only to check if I could solve it with [[SubstitutedExpression]] without using external functions.

      from enigma import  SubstitutedExpression
      
      p = SubstitutedExpression([
          # Consider first 7 divisors (1, 2, .....)
          "XYZ > 300",
          "XYZ % 2 == 0",   # as specified
          "XYZ % IJ == 0",  # 7th divisor
          "XYZ % GH == 0",  # 6th divisor
          "XYZ % EF == 0",  # 5th divisor
          "XYZ % CD == 0",  # 4th divisor
          "XYZ % AB == 0",  # 3rd divisor
          # put them in order
          "GH < IJ",
          "EF < GH",
          "CD < EF", 
          "AB < CD", 
          "2 < AB ",
          # limit 7th divisor 
          "IJ < (XYZ // IJ)",
          # make sure there are only 7 divisors before 8th divisor
          "sum(1 for x in range(1, (XYZ // IJ)) if XYZ % x == 0) == 7",
          ],
          verbose=0,
          d2i={k:"X" for k in [x for x in range(0,10) if x != 3]},
          #accumulate=min,
          answer="(1, 2, AB, CD, EF, GH, IJ, XYZ)",
          distinct="",
          #reorder=0
      )
      
      # Solve and print answers
      
      for (_, ans) in p.solve():
       hs = 0
       # sum the divisors
       for i in range(0,7):
         hs += ans[i] + ans[7]//ans[i]
       print(f"{hs} houses") 
      

      Like

  • Unknown's avatar

    Jim Randell 4:44 pm on 16 October 2020 Permalink | Reply
    Tags:   

    Teaser 3030: Pot success 

    From The Sunday Times, 18th October 2020 [link] [link]

    In snooker, pot success (PS) is the percentage of how many pot attempts have been successful in that match (e.g. 19 pots from 40 attempts gives a PS of 47.5%). In a recent match, my PS was a whole number at one point. I then potted several balls in a row to finish a frame, after which my improved PS was still a whole number. At the beginning of the next frame, I potted the same number of balls in a row, and my PS was still a whole number. I missed the next pot, my last shot in the match, and, remarkably, my PS was still a whole number.

    If I told you how many balls I potted during the match, you would be able to work out those various whole-number PS values.

    How many balls did I pot?

    [teaser3030]

     
    • Jim Randell's avatar

      Jim Randell 5:03 pm on 16 October 2020 Permalink | Reply

      This Python program runs in 46ms.

      Run: [ @replit ]

      from enigma import (irange, div, peek, printf)
      
      # calculate PS
      PS = lambda p, t: div(100 * p, t)
      
      # generate scenarios with p balls potted (at the end)
      def generate(p):
        # consider total number of attempts (at the end)
        for t in irange(p + 1, 100):
          # final PS (ps4)
          ps4 = PS(p, t)
          if ps4 is None: continue
          # last shot was a miss, and the PS before it (ps3)
          ps3 = PS(p, t - 1)
          if ps3 is None: continue
          # before that 2 lots of k balls were potted
          for k in irange(2, (p - 1) // 2):
            ps2 = PS(p - k, t - 1 - k)
            if ps2 is None: continue
            ps1 = PS(p - 2 * k, t - 1 - 2 * k)
            if ps1 is None or not (ps1 < ps2): continue
            # return (t, k, PS) values
            yield (t, k, (ps1, ps2, ps3, ps4))
      
      # consider number of balls potted (at the end)
      for p in irange(5, 99):
        # accumulate ps values
        s = set(ps for (t, k, ps) in generate(p))
        # is there only one?
        if len(s) == 1:
          # output solution
          printf("balls potted = {p} -> PS = {ps}", ps=peek(s))
      

      Solution: You potted 13 balls.

      This corresponds to the following scenario:

      13 balls potted (2 runs of 5), PS = (3/15 = 20%; 8/20 = 40%; 13/25 = 52%; 13/26 = 50%)

      The other possible scenarios are:

      12 balls potted (2 runs of 5), PS = (2/5 = 40%; 7/10 = 70%; 12/15 = 80%; 12/16 = 75%)
      12 balls potted (2 runs of 4), PS = (4/16 = 25%; 8/20 = 40%; 12/24 = 50%; 12/25 = 48%)

      57 balls potted (2 runs of 15), PS = (27/45 = 60%; 42/60 = 70%; 57/75 = 76%; 57/76 = 75%)
      57 balls potted (2 runs of 25), PS = (7/28 = 25%; 32/50 = 64%; 57/75 = 76%; 57/76 = 75%)

      It seems plausible that these could correspond to a snooker match (it is possible to pot 10 reds + 10 colours + 6 colours = 26 balls in one frame, and we know at least 2 frames are involved). Although if just one of them did not, then the other scenario would provide a further solution.

      The program ensures that the PS values we are considering are non-zero, but allowing the initial PS to be zero gives a further solution:

      18 balls potted (2 runs of 9), PS = (0/6 = 0%; 9/15 = 60%; 18/24 = 75%; 18/25 = 72%)

      Like

    • Frits's avatar

      Frits 11:06 am on 17 October 2020 Permalink | Reply

      @Jim, I think you should also consider p=4 as ps1 might be zero and zero also is a whole number .

      Also if t would be (p + 1) then ps1, ps2 and ps3 would be 100 and ps2 would not be higher than ps1.
      If you start t from (p+ 2) you don’t have to code the ps1 < ps2 check as it will always be the case.

      Of course there always is a balance between efficiency and seeing right away that the code solves the requirements.

      Like

      • Jim Randell's avatar

        Jim Randell 11:50 am on 17 October 2020 Permalink | Reply

        @Frits: Unfortunately the term “whole number” isn’t precise. It can be used to mean the integers, the non-negative integers, or just the positive integers.

        For this puzzle I took it to be the positive integers (which gets around a possible problem when considering PS values of 0), so I didn’t have to consider PS values of zero. Which is probably what the setter intended, as I’m sure the puzzle is meant to have a unique solution.

        I also wanted to make the [[ ps1 < ps2 ]] condition explicit in the code (as without it there would be further solutions – which can be seen by removing the test).

        Like

    • Frits's avatar

      Frits 1:28 pm on 17 October 2020 Permalink | Reply

      Assuming whole numbers are in the range (1, …, 100) and with the “improved PS” check (which is not needed if we use “AB < CD").

      If we include zero as a whole number there are 2 solutions.

      from enigma import  SubstitutedExpression, multiset
      
      r = multiset()
      
      p = SubstitutedExpression([
          # we start with AB balls potted from CD attempts
          "AB <= CD",
          "CD > 0",
          # "I then potted several balls (XY > 1) in a row"
          # Assume CD + 2 * XY + 1 is roughly less than 100
          "XY > 1",
          "XY < (99 - CD) // 2",
           # the 4 pot successes being whole numbers (meaning 1..100 ???)
          "div(100 * AB, CD) > 0",
          "div(100 * (AB + XY), CD + XY) > 0",
          "div(100 * (AB + 2 * XY), CD + 2 * XY) > 0",
          "div(100 * (AB + 2 * XY), CD + 2 * XY + 1) > 0",
          # improved PS
          "div(100 * (AB + XY), CD + XY) > div(100 * AB, CD)",
          ],
          verbose=0,
          d2i={},
          answer="AB + 2 * XY, AB, CD, XY, " 
                 "(100 * AB / CD, 100 * (AB + XY) / (CD + XY),"
                 " 100 * (AB + 2 * XY) / (CD + 2 * XY),"
                 " 100 * (AB + 2 * XY) / (CD + 2 * XY + 1))",
          distinct="",
      )
      
      # Solve and print answers
      
      print("potted   start  several     pot success" )
      for (_, ans) in p.solve():
        print(f" {ans[0]:>2}     {str(ans[1:3]):<9}  {str(ans[3]):<3}  {ans[4]}")
        r.add(ans[0])
      
      for (k, v) in r.items():
        if v == 1:
          print(f"\n{k} balls potted")
      

      Like

  • Unknown's avatar

    Jim Randell 4:21 pm on 15 October 2020 Permalink | Reply
    Tags:   

    Teaser 2758: Square dance 

    From The Sunday Times, 2nd August 2015 [link]

    Dancers numbered from 1 to 9 were about to perform a square dance: five were dressed in red and the rest in blue. They stood in a 3-by-3 array with all three dancers in the first row wearing red and all three in another row wearing blue. Their numbers formed a magic square (i.e. the sum of the three numbers in any row, column or diagonal was the same). One of the dancers in red looked around and noted that the sum of the numbers of the four other dancers in red was the same as the sum of the numbers of the four dancers in blue.

    One of the dancers in blue was number 8, what were the numbers of the other three dancers in blue?

    [teaser2758]

     
    • Jim Randell's avatar

      Jim Randell 4:22 pm on 15 October 2020 Permalink | Reply

      The Magic Square uses the numbers 1 – 9 so we know that the magic sum is 15, and the central square is 5. (See: Enigma 1680, Enigma 1080).

      We can then choose two numbers (other than 5 and 8) for the reds on the top row, and that lets us work out all the remaining squares.

      This Python program runs in 46ms.

      Run: [ @replit ]

      # consider the square:
      #
      #   a b c
      #   d e f
      #   g h i
      #
      # where each latter stands for a number from 1 to 9
      #
      # the magic sum is: s = (1 + 2 + ... + 9) / 3 = 15
      #
      # and e = s / 3 = 5
      
      from enigma import (irange, div, subsets, printf)
      
      # the numbers
      numbers = set(irange(1, 9))
      
      # total sum, magic sum, centre square
      t = sum(numbers)
      s = div(t, 3)
      e = div(s, 3)
      
      # generate magic squares
      # 8 is a blue, so can't be in the first row
      for (a, b) in subsets(numbers.difference((e, 8)), size=2, select="P"):
        c = s - (a + b)
        if c in {8, a, b, e}: continue
      
        # fill out the rest of the square
        i = s - (a + e)
        f = s - (c + i)
        d = s - (e + f)
        g = s - (a + d)
        h = s - (g + i)
      
        # check it uses the numbers 1 - 9
        if numbers.difference({a, b, c, d, e, f, g, h, i}): continue
      
        # choose the row with mixed colours
        for row in ((d, e, f), (g, h, i)):
          # and choose two from that row to be red
          for red in subsets(row, size=2):
            if 8 in red: continue
            # so the reds are...
            reds = set((a, b, c) + red)
            # and if the observant red dancer is x...
            # we need 2(sum(reds) - x) = (t - x)
            x = 2 * sum(reds) - t
            if x not in reds: continue
            # so the blues are...
            blues = numbers.difference(reds)
            printf("{a} {b} {c} / {d} {e} {f} / {g} {h} {i}, reds={reds}, x={x}, blues={blues}")
      

      Solution: The other dancers wearing blue had numbers 1, 3 and 6.

      The dancers are arranged like this:

      (or mirrored about a vertical axis).

      Dancer number 9 notices that the sum of the other red dancers (2 + 4 + 7 + 5 = 18) is the same as the sum of the blue dancers (1 + 3 + 6 + 8 = 18).

      Like

    • Frits's avatar

      Frits 12:35 pm on 16 October 2020 Permalink | Reply

      # consider the square:
      #
      #   A B C
      #   D E F
      #   G H I
      #
      # where each latter stands for a number from 1 to 9
      #
      # the magic sum is: MS = (1 + 2 + ... + 9) / 3 = 15
      #
      # and E = MS / 3 = 5
      
      from enigma import  SubstitutedExpression, irange, is_prime
      
      p = SubstitutedExpression([
          # magic square
          "A+B+C == MS",
          "D+E+F == MS",
          "G+H+I == MS",
          "A+D+G == MS",
          "B+E+H == MS",
          "C+F+I == MS",
          "A+E+I == MS",
          "C+E+G == MS",
          # for red : pick 2 out of 1st row, 2 out of middle row
          # for blue: pick 1 out of middle row and 3 out of 3rd row 
          "a*A + b*B + c*C + (1-d)*D + (1-e)*E + (1-f)*F == G+H+I + d*D + e*E + f*F",
          # pick 1 out of middle row
          "d + e + f == 1",
          # pick 2 out of first row
          "a + b + c == 2",
       
          ],
          verbose=0,
          symbols="ABCDEFGHIMSabcdef",
          d2i=dict([(0, "ABCDEFGHI")]  + [(8, "ABCabcdef")] + [(k, "abcdef") for k in {2,3,4,5,6,7,9}]),
          answer="A,B,C,D,E,F,G,H,I, a, b, c, d, e, f, d*D + e*E + f*F  ",
          distinct=("ABCDEFGHI"),
          digits=range(0, 10)
      )
      
      # Print answers
      cnt = 0
      for (_, ans) in p.solve():
         sol = list(ans[6:9]) + [ans[15]]
         sol.remove(8) 
         print(f"Answer = {sol}\n")
         print(f"{ans[:3]} a,b,c {ans[9:12]}\n{ans[3:6]} d,e,f {ans[12:15]}\n{ans[6:9]}\n")
         
         
      # Answer = [6, 1, 3]
      # 
      # (2, 9, 4) a,b,c (1, 0, 1)
      # (7, 5, 3) d,e,f (0, 0, 1)
      # (6, 1, 8)
      # 
      # Answer = [1, 6, 3]
      # 
      # (4, 9, 2) a,b,c (1, 0, 1)
      # (3, 5, 7) d,e,f (1, 0, 0)
      # (8, 1, 6)
      

      Like

      • Jim Randell's avatar

        Jim Randell 11:58 am on 17 October 2020 Permalink | Reply

        @Frits: How do we know the mixed row is the second row and not the bottom row?

        Like

    • Frits's avatar

      Frits 2:57 pm on 17 October 2020 Permalink | Reply

      You are right, mixed row can be 2nd or 3rd row.

      # consider the square:
      #
      #   A B C 
      #   D E F    magic sum A + B + C
      #   G H I
      #
      # where each latter stands for a number from 1 to 9
      #
      # a, ..., i are bits
      
      from enigma import  SubstitutedExpression, irange, is_prime
      
      p = SubstitutedExpression([
          # magic square with magic sum A + B + C
          "D + E + F == A + B + C",
          "G + H + I == A + B + C",
          "A + D + G == A + B + C",
          "B + E + H == A + B + C",
          "C + F + I == A + B + C",
          "A + E + I == A + B + C",
          "C + E + G == A + B + C",
         
          # pick 4 out of 2nd row or 3rd row, including a whole row
          "d + e + f + g + h + i == 4",
          "d + e + f == 3 or g + h + i == 3",
          # pick 2 out of first row for 
          "a + b + c == 2",
          
          # for blue: pick 4 out of 2nd row and 3rd row, including a whole row 
          # for red : pick 2 out of 1st row, 2 out of 2nd row or 3rd row
          # (which is same as:  pick 2 out of 1st row plus
          #  2 times magic sum - the 4 items picked for blue)
          "(a+2)*A + (b+2)*B + (c+2)*C == 2 * (d*D + e*E + f*F + g*G + h*H + i*I)",
          
          # One of the dancers in blue was number 8
          "sum([d*D == 8, e*E == 8, f*F == 8, g*G == 8, h*H == 8, i*I == 8]) == 1",
          ],
          verbose=0,
          symbols="ABCDEFGHIMSabcdefghi",
          d2i=dict([(0, "ABCDEFGHI")]  + 
                   [(8, "ABCabcdefghi")] + 
                   [(k, "abcdefghi") for k in {2,3,4,5,6,7,9}]),
          answer="A, B, C, D, E, F, G, H, I, a, b, c, d, e, f, g, h, i, \
                  d*D + e*E + f*F + g*G + h*H + i*I",
          distinct=("ABCDEFGHI"),
          reorder=0
      )
      
      # Print answers
      for (_, ans) in p.solve():
        blue = [y[0] for (x,y) in enumerate(zip(ans[3:9], ans[12:18])) 
                if y[1] == 1 and y[0] != 8]
        print(f"Answer = {blue}\n")
        print(f"{ans[:3]} a,b,c: {ans[9:12]}\n{ans[3:6]} d,e,f: {ans[12:15]}\n"
              f"{ans[6:9]} g,h,i: {ans[15:18]}\n")
         
         
      # Answer = [3, 1, 6]
      #
      # (4, 9, 2) a,b,c: (1, 0, 1)
      # (3, 5, 7) d,e,f: (1, 0, 0)
      # (8, 1, 6) g,h,i: (1, 1, 1)
      #
      # Answer = [3, 6, 1]
      #
      # (2, 9, 4) a,b,c: (1, 0, 1)
      # (7, 5, 3) d,e,f: (0, 0, 1)
      # (6, 1, 8) g,h,i: (1, 1, 1)
      

      Like

      • Frits's avatar

        Frits 3:29 pm on 17 October 2020 Permalink | Reply

        @Jim,

        Instead of the sum() equation I could have used any() where it not for the fact that “any” contains an “a” which is a variable. Is there an easy way to use normal Python functions without having to worry about lowercase variables?

        It would be nice if the SubstitutedExpression solver could ignore consecutive lowercase characters as variables.

        Like

  • Unknown's avatar

    Jim Randell 9:58 am on 13 October 2020 Permalink | Reply
    Tags:   

    Teaser 1915: Rabbit, rabbit, rabbit, … 

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

    In each of the four houses in a small terrace lives a family with a boy, a girl and a pet rabbit. One of the children has just mastered alphabetical order and has listed them thus:

    I happen to know that this listing gave exactly one girl, one boy and one rabbit at her, his or its correct address. I also have the following correct information: neither Harry nor Brian lives at number 3, and neither Donna nor Jumper lives at number 1. Gail’s house number is one less than Mopsy’s house number, and Brian’s house number is one less than Cottontail’s.

    Who lives where?

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

    [teaser1915]

     
    • Jim Randell's avatar

      Jim Randell 9:58 am on 13 October 2020 Permalink | Reply

      See also: Teaser 2944.

      This Python program runs in 44ms.

      Run: [ @repl.it ]

      from enigma import subsets, printf
      
      # the names
      girls = "ADGK"
      boys = "BEHL"
      rabbits = "CFJM"
      
      # choose an ordering where exactly k of the original ordering is correct
      def choose(xs, k=1):
        for ys in subsets(xs, size=len, select="P"):
          if sum(x == y for (x, y) in zip(xs, ys)) == k:
            yield ys
      
      # choose an ordering for the boys
      for boy in choose(boys):
        # "neither H nor B live at number 3 [= index 2]"
        if boy[2] == 'H' or boy[2] == 'B': continue
      
        # choose an ordering for the rabbits
        for rabbit in choose(rabbits):
          # "J does not live at number 1 [=index 0]"
          if rabbit[0] == 'J': continue
          # "B's number is one less than C's"
          if boy.index('B') + 1 != rabbit.index('C'): continue
      
          # choose an ordering for the girls
          for girl in choose(girls):
            # "D does not live at number 1 [= index 0]"
            if girl[0] == 'D': continue
            # "G's number is one less than M's"
            if girl.index('G') + 1 != rabbit.index('M'): continue
      
            # output solution
            for (n, (g, b, r)) in enumerate(zip(girl, boy, rabbit), start=1):
              printf("{n}: {g} {b} {r}")
            printf()
      

      Solution: The occupants of each house are:

      1: Kelly, Harry, Flopsy
      2: Alice, Brian, Jumper
      3: Gail, Eric, Cottontail
      4: Donna, Laurie, Mopsy

      Like

    • Frits's avatar

      Frits 4:56 pm on 13 October 2020 Permalink | Reply

      from enigma import SubstitutedExpression
      
      girls   = ("", "Alice", "Donna", "Gail", "Kelly")
      boys    = ("", "Brian", "Eric", "Harry", "Laurie")
      rabbits = ("", "Cottontail", "Flopsy", "Jumper", "Mopsy")
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
         # "B's number is one less than C's"
         "B + 1 = C",
         # "G's number is one less than M's" 
         "G + 1 = M",
         # listing gave exactly one girl, one boy and one rabbit at
         # her, his or its correct address
         "sum((A == 1, D == 2, G == 3, K == 4)) == 1",
         "sum((B == 1, E == 2, H == 3, L == 4)) == 1",
         "sum((C == 1, F == 2, J == 3, M == 4)) == 1",
        ],
        answer="ADGK, BEHL, CFJM",   
        digits=range(1,5),
        distinct=('ADGK', 'BEHL', 'CFJM'),
        # "D does not live at number 1"
        # "J does not live at number 1"
        # "neither H nor B live at number 3"
        d2i={1 : "CMDJ", 3 : "BH", 4 : "BG"}, 
        verbose=0,
        #reorder=0,
      )
      
      # Print answers
      for (_, ans) in p.solve(): 
        for k in "1234":
          x = [j+1 for x in ans for j, y in enumerate(str(x)) if y == k]
          print(f"house {k}: {girls[x[0]]:<6} {boys[x[1]]:<7} {rabbits[x[2]]}")
        
      # house 1: Kelly  Harry   Flopsy
      # house 2: Alice  Brian   Jumper
      # house 3: Gail   Eric    Cottontail
      # house 4: Donna  Laurie  Mopsy
      

      Like

    • John Crabtree's avatar

      John Crabtree 6:34 pm on 13 October 2020 Permalink | Reply

      Flopsy must be at #1.
      Brian cannot be at #2, otherwise Jumper and Mopsy are both correct or both incorrect.
      And so Brian is at #1, Cottontail at #3, Jumper at #1, Mopsy at #4, and Gail at #3, etc

      Like

  • Unknown's avatar

    Jim Randell 12:03 pm on 11 October 2020 Permalink | Reply
    Tags: by: J. V. Sharp   

    Brain-Teaser 10: [Clock sync] 

    From The Sunday Times, 30th April 1961 [link]

    David’s train left at 11 am. The previous night David had set his bedroom clock and watch in such a way that they would read the correct time when he intended to leave the following morning. His clock lost regularly, while his watch gained regularly. The next morning he actually left when the clock said 8:15 and his watch said 8:10. When the train left the station (on time) David looked at his watch and noticed that it said 11:10. David had planned to leave at an exact number of minutes past 8 and he worked out afterwards that he had left at an exact number of minutes past 8.

    At what time did David intend to leave, and at what time did he actually leave?

    This puzzle was originally published with no title.

    [teaser10]

     
    • Jim Randell's avatar

      Jim Randell 12:04 pm on 11 October 2020 Permalink | Reply

      Let’s suppose David intended to leave at m minutes past 8:00am (so m = 0 .. 59).

      At this time both the clock (which runs slow) and the watch (which runs fast) would both say the correct time:

      clock(m) = m
      watch(m) = m

      If the watch runs at a rate of w (> 1), and the clock at a rate of c (< 1) we have:

      clock(t) = m + (t − m)c
      watch(t) = m + (t − m)w

      Now, he actually left at n minutes past 8:00am (so n = 0 .. (m − 1)).

      And at this time the clock read 8:15 and the watch read 8:10:

      clock(n) = 15
      watch(n) = 10

      (The fact the clock is ahead of the watch tells us that the actual time is earlier than the intended time).

      So:

      m + (n − m)c = 15 ⇒ c = (m − 15) / (m − n)
      m + (n − m)w = 10 ⇒ w = (m − 10) / (m − n)

      And at the moment the train left (11:00am) the watch read 11:10:

      watch(180) = 190
      m + (180 − m)w = 190
      (180 − m)(m − 10) = (190 − m)(m − n)
      n = 1800 / (190 − m)
      m = 190 − 1800 / n

      And we need c < 1 and w > 1:

      m − 15 < m − n
      m − 10 > m − n
      10 < n < 15

      So there are only 4 values to check, which we can do manually or with a program.

      from enigma import (irange, div, printf)
      
      # choose a value for n (actual leave time)
      for n in irange(11, 14):
        # calculate m (intended leave time)
        m = div(190 * n - 1800, n)
        if m is None: continue
        printf("n={n} -> m={m}")
      

      Solution: David intended to leave at 8:40am. He actually left at 8:12am.

      The watch runs at a rate of 15/14 (approx. 107%). So it gains 2 minutes every 28 minutes.

      The clock runs at a rate of 25/28 (approx. 89%). So it loses 3 minutes every 28 minutes.

      Like

    • John Crabtree's avatar

      John Crabtree 6:38 pm on 13 October 2020 Permalink | Reply

      If not leaving at the intended time, Dave’s actual time of departure must be between those indicated on the clock and watch. Assume that he left x minutes after 8:10 where x = 1, 2, 3 or 4.
      The watch would be correct (170 – x) * x / (x + 10) = … = 180 – x – 1800 / (x + 10) minutes later, which is an integer.
      And so x = 2, and the solution follows.

      Like

  • Unknown's avatar

    Jim Randell 4:38 pm on 9 October 2020 Permalink | Reply
    Tags:   

    Teaser 3029: Square jigsaws 

    From The Sunday Times, 11th October 2020 [link] [link]

    I chose a whole number and asked my grandson to cut out all possible rectangles with sides a whole number of centimetres whose area, in square centimetres, did not exceed my number. (So, for example, had my number been 6 he would have cut out rectangles of sizes 1×1, 1×2, 1×3, 1×4, 1×5, 1×6, 2×2 and 2×3). The total area of all the pieces was a three-figure number of square centimetres.

    He then used all the pieces to make, in jigsaw fashion, a set of squares. There were more than two squares and at least two pieces in each square.

    What number did I originally choose?

    [teaser3029]

     
    • Jim Randell's avatar

      Jim Randell 5:17 pm on 9 October 2020 Permalink | Reply

      (See also: Enigma 1251, Enigma 1491).

      If the squares are composed of at least two pieces, they must be at least 3×3.

      This Python program works out the only possible original number that can work, but it doesn’t demonstrate the the rectangles can be assembled into the required squares.

      It runs in 44ms.

      Run: [ @repl.it ]

      from enigma import irange, inf, printf
      
      # generate rectangles with area less than n
      def rectangles(n):
        for i in irange(1, n):
          if i * i > n: break
          for j in irange(i, n // i):
            yield (i, j)
      
      # decompose t into squares
      def decompose(t, m=1, ss=[]):
        if t == 0:
          yield ss
        else:
          for k in irange(m, t):
            s = k * k
            if s > t: break
            yield from decompose(t - s, k, ss + [k])
      
      # make squares from the rectangular pieces, with total area A
      def fit(rs, A):
        # determine the maximum dimension of the rectangles
        m = max(j for (i, j) in rs)
        for n in irange(m, A):
          if n * n > A: break
          # decompose the remaining area into squares
          for ss in decompose(A - n * n, 3):
            if len(ss) < 2: continue
            yield ss + [n]
      
      # consider the chosen number
      for n in irange(2, inf):
        # collect the rectangles and total area
        rs = list(rectangles(n))
        A = sum(i * j for (i, j) in rs)
        if A < 100: continue
        if A > 999: break
      
        # fit the rectangles in a set of squares
        for ss in fit(rs, A):
          printf("n={n}, A={A} -> ss={ss}")
      

      Solution: The original number was 29.

      The rectangles cut out are:

      1, 2, 3, …, 29
      2, 3, …, 14
      3, …, 9
      4, 5, 6, 7
      5

      With a total area of 882 cm².

      The 1×29 piece must fit into a square of size 29×29 or larger, but this is the largest square we can make, so there must be a 29×29 square, which leaves an area of 41 cm². This can be split into squares as follows:

      41 = 3² + 4² + 4²
      41 = 4² + 5²

      It turns out that it only possible to construct a packing using the second of these.

      I used the polyominoes.py library that I wrote for Teaser 2996 to assemble the rectangles into a viable arrangement of squares. It runs in 19 minutes. (The adapted program is here).

      Here is a diagram showing one way to pack the rectangles into a 4×4, 5×5 and a 29×29 square:

      Like

      • Frits's avatar

        Frits 2:27 pm on 10 October 2020 Permalink | Reply

        @Jim,

        Wouldn’t it be easier to use the chosen number n as maximum dimension (line 23)?

        Like

        • Jim Randell's avatar

          Jim Randell 11:51 am on 11 October 2020 Permalink | Reply

          @Frits: Probably. When I started writing the program I was thinking about a constructive solution, that would produce a packing of the rectangles into the required squares. This is a cut-down version of the program which finds possible sets of squares to investigate – fortunately the solutions found all correspond to the same original number, so if there is an answer we know what it must be.

          And it turns out that we don’t need to examine the set of rectangles to determine the maximum dimension, so we could just pass it in. In fact we don’t need to collect the rectangles at all. Here’s a better version of the cut-down program:

          from enigma import (irange, inf, divisors_pairs, printf)
          
          # decompose t into squares
          def decompose(t, m=1, ss=[]):
            if t == 0:
              yield ss
            else:
              for k in irange(m, t):
                s = k * k
                if s > t: break
                yield from decompose(t - s, k, ss + [k])
          
          # find at least 3 potential squares with total area A
          # that can accomodate a rectangle with max dimension m
          def fit(A, m):
            for n in irange(m, A):
              a = A - n * n
              if a < 18: break
              # decompose the remaining area into at least 2 squares
              # with minimum dimension 3
              for ss in decompose(a, 3):
                if len(ss) > 1:
                  yield ss + [n]
          
          # collect rectangles with increasing area
          A = 0
          for n in irange(1, inf):
            # add in rectangles of area n
            A += sum(i * j for (i, j) in divisors_pairs(n))
          
            # look for 3-digit total area
            if A < 100: continue
            if A > 999: break
          
            # find a set of squares with the same area
            for ss in fit(A, n):
              printf("n={n} A={A} -> ss={ss}")
          

          I did complete a program that finds a viable packing, but it takes a while to run (19 minutes).

          I’ll post the diagram with the answer later.

          Like

          • Frits's avatar

            Frits 5:59 pm on 11 October 2020 Permalink | Reply

            @Jim, a nice solution with the divisor pairs.
            I think we can only form one 3×3 square at most (out of 3 possibilities).

            Like

    • Frits's avatar

      Frits 1:29 pm on 10 October 2020 Permalink | Reply

      I did a manual check to see if the reported squares can be formed in 2-D with the rectangular pieces.

       
      # select numbers from list <li> so that sum of numbers equals <n>
      def decompose(n, li, sel=[]):
        if n == 0:
          yield sel
        else:
          for i in {j for j in range(len(li)) if li[j] <= n}:
            yield from decompose(n - li[i], li[i:], sel + [li[i]])     
              
      # squares under 1000, square 2x2 can't be formed from rectangles                 
      squares = [n*n for n in range(3, 32)]
      
      rectangles = lambda n: [(i, j) for i in range(1, n + 1) 
                              for j in range(i, n + 1) if i * j <= n]
      
      area = lambda li: sum(i * j for (i, j) in li)
      
      # candidates with three-figure number area; area must be
      # at least largest square (i*i) plus the 2 smallest squares 9 and 16
      cands = lambda: {i: area(rectangles(i)) for i in range(7, 32) 
                       if area(rectangles(i)) in range(100, 1000) and
                          area(rectangles(i)) >= i * i + 25}
      
      # check if solution exist for candidates
      for (n, a) in cands().items(): 
        # biggest square should be at least n x n due to 1 x n piece
        for big in range(n, 32):
          # list of squares to consider (excluding the biggest square)
          sq = [x for x in squares if x <= a - (big * big) - 9]
          # select squares which together with biggest square will sum to area <a>
          for s in decompose(a - (big * big), sq):
            if len(s) > 1:
              print(f"number chosen = {n}, area={a}, squares={s + [big * big]}")
      

      Like

  • Unknown's avatar

    Jim Randell 10:36 am on 8 October 2020 Permalink | Reply
    Tags:   

    Teaser 1908: Dunroamin 

    From The Sunday Times, 11th April 1999 [link]

    At our DIY store you can buy plastic letters of the alphabet in order to spell out your house name. Although all the A’s cost the same as each other, and all the B’s cost the same as each other, etc., different letters sometimes cost different amounts with a surprisingly wide range of prices.

    I wanted to spell out my house number:

    FOUR

    and the letters cost me a total of £4. Surprised by this coincidence I worked out the cost of spelling out each of the numbers from ONE to TWELVE. In ten out of the twelve cases the cost in pounds equalled the number being spelt out.

    For which house numbers was the cost different from the number?

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

    [teaser1908]

     
    • Jim Randell's avatar

      Jim Randell 10:37 am on 8 October 2020 Permalink | Reply

      (See also: Teaser 2756, Enigma 1602).

      This Python program checks to see if each symbol can be assigned a value that is a multiple of 25p to make the required 10 words correct (and also checks that the remaining two words are wrong).

      Instead of treating the letters G, H, R, U separately, we treat them as GH, HR, UR, which reduces the number of symbols to consider by 1.

      The program runs in 413ms.

      Run: [ @repl.it ]

      from enigma import (irange, subsets, update, unpack, diff, join, map2str, printf)
      
      # <value> -> <symbol> mapping
      words = {
        100: ("O", "N", "E"),
        200: ("T", "W", "O"),
        300: ("T", "HR", "E", "E"),
        400: ("F", "O", "UR"),
        500: ("F", "I", "V", "E"),
        600: ("S", "I", "X"),
        700: ("S", "E", "V", "E", "N"),
        800: ("E", "I", "GH", "T"),
        900: ("N", "I", "N", "E"),
        1000: ("T", "E", "N"),
        1100: ("E", "L", "E", "V", "E" ,"N"),
        1200: ("T" ,"W", "E", "L", "V", "E"),
      }
      
      # decompose t into k numbers, in increments of step
      def decompose(t, k, step, ns=[]):
        if k == 1:
          yield ns + [t]
        elif k > 1:
          k_ = k - 1
          for n in irange(step, t - k_ * step, step=step):
            yield from decompose(t - n, k_, step, ns + [n])
      
      # find undefined symbols and remaining cost
      def remaining(v, d):
        (us, t) = (set(), v)
        for x in words[v]:
          try:
            t -= d[x]
          except KeyError:
            us.add(x)
        return (us, t)
      
      # find values for vs, with increments of step
      def solve(vs, step, d=dict()):
        # for each value find remaining cost and undefined symbols
        rs = list()
        for v in vs:
          (us, t) = remaining(v, d)
          if t < 0 or bool(us) == (t == 0): return
          if t > 0: rs.append((us, t, v))
        # are we done?
        if not rs:
          yield d
        else:
          # find the value with the fewest remaining symbols (and lowest remaining value)
          (us, t, v) = min(rs, key=unpack(lambda us, t, v: (len(us), t)))
          # the remaining values
          vs = list(x for (_, _, x) in rs if x != v)
          # allocate the unused symbols
          us = list(us)
          for ns in decompose(t, len(us), step):
            # solve for the remaining values
            yield from solve(vs, step, update(d, us, ns))
      
      # choose the 2 equations that are wrong
      for xs in subsets(sorted(words.keys()), size=2):
        # can't include FOUR
        if 400 in xs: continue
        # find an allocation of values
        for d in solve(diff(words.keys(), xs), 25):
          xvs = list(sum(d[s] for s in words[x]) for x in xs)
          if all(x != v for (x, v) in zip(xs, xvs)):
            # output solution
            wxs = list(join(words[x]) for x in xs)
            printf("wrong = {wxs}", wxs=join(wxs, sep=", ", enc="[]"))
            printf("-> {d}", d=map2str(d, sep=" ", enc=""))
            for (x, xv) in zip(wxs, xvs):
              printf("  {x} = {xv}p")
            printf()
            break
      

      Solution: NINE and TEN do not cost an amount equal to their number.

      One way to allocate the letters, so that ONEEIGHT, ELEVEN, TWELVE work is:

      25p = F, H, N, O, T
      50p = E, X
      150p = W, R
      200p = I, U
      225p = V
      350p = S
      500p = G
      700p = L

      (In this scheme: NINE costs £3 and TEN costs £1).

      But there are many other possible assignments.

      You can specify smaller divisions of £1 for the values of the individual symbols, but the program takes longer to run.

      It makes sense that NINE and TEN don’t cost their value, as they are short words composed entirely of letters that are available in words that cost a lot less.


      Here’s a manual solution:

      We note that ONE + TWELVE use the same letters as TWO + ELEVEN, so if any three of them are correct so is the fourth. So there must be 0 or 2 of the incorrect numbers among these four.

      Now, if ONE and TEN are both correct, then any number with value 9 or less with a T in it must be wrong. i.e. TWO, THREE, EIGHT. Otherwise we could make TEN for £10 or less, and have some letters left over. And this is not possible.

      If ONE is incorrect, then the other incorrect number must be one of TWO, ELEVEN, TWELVE, and all the remaining numbers must be correct. So we could buy THREE and SEVEN for £10, and have the letters to make TEN, with EEEHRSV left over. This is not possible.

      So ONE must be correct, and TEN must be one of the incorrect numbers.

      Now, if NINE is correct, then any number with value 7 or less with an I in it must be incorrect. Which would mean FIVE and SIX are incorrect. This is not possible.

      Hence, the incorrect numbers must be NINE and TEN.

      All that remains is to demonstrate one possible assignment of letters to values where NINE and TEN are incorrect and the rest are correct. And the one found by the program above will do.

      Like

    • GeoffR's avatar

      GeoffR 6:36 pm on 8 October 2020 Permalink | Reply

      I used a wide range of upper .. lower bound values for letter variables.

      The programme would not run satisfactorily under the Geocode Solver, but produced a reasonable run time under the Chuffed Solver. It confirmed the incorrect numbers were NINE and TEN.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 10..900:O; var 10..900:N; var 10..900:E; var 10..900:T;
      var 10..900:W; var 10..900:H; var 10..900:R; var 10..900:F;
      var 10..900:U; var 10..900:V; var 10..900:I; var 10..900:L;
      var 10..900:G; var 10..900:S; var 10..900:X;
      
      constraint sum([
      O+N+E == 100, T+W+O == 200, T+H+R+E+E == 300, F+O+U+R == 400,
      F+I+V+E == 500, S+I+X == 600, S+E+V+E+N == 700,
      E+I+G+H+T == 800, N+I+N+E == 900, T+E+N == 1000,
      E+L+E+V+E+N == 1100, T+W+E+L+V+E == 1200]) == 10;
      
      solve satisfy;
      
      output [" ONE = " ++ show (O + N + E) ++
      "\n TWO = " ++ show (T + W + O ) ++
      "\n THREE = " ++ show (T + H + R + E + E) ++
      "\n FOUR = " ++ show (F + O + U + R) ++
      "\n FIVE = " ++ show (F + I + V + E) ++
      "\n SIX = " ++ show (S + I + X) ++
      "\n SEVEN = " ++  show (S + E + V + E + N) ++
      "\n EIGHT = " ++ show (E + I + G + H + T) ++
      "\n NINE = " ++ show (N + I + N + E) ++
      "\n TEN = " ++ show (T + E + N) ++
      "\n ELEVEN = " ++ show (E + L + E + V + E + N) ++
      "\n TWELVE = " ++ show (T + W + E + L + V + E) ++
      "\n\n [O N E T W H R F U V I L G S X = ]" ++ 
      show([O, N, E, T, W, H, R, F, U, V, I, L, G, S, X]) ];
      
      %  ONE = 100
      %  TWO = 200
      %  THREE = 300
      %  FOUR = 400
      %  FIVE = 500
      %  SIX = 600
      %  SEVEN = 700
      %  EIGHT = 800
      %  NINE = 171   << INCORRECT
      %  TEN = 101    << INCORRECT
      %  ELEVEN = 1100
      %  TWELVE = 1200
      %  Letter Values (One of many solutions)
      %  [O    N   E   T   W    H   R    F   U    V    I   L    G    S    X = ]
      %  [10, 71, 19, 11, 179, 13, 238, 10, 142, 461, 10, 511, 747, 130, 460]
      %  time elapsed: 0.04 s
      % ----------
      
      
      
      

      Like

      • Frits's avatar

        Frits 7:47 pm on 11 October 2020 Permalink | Reply

        @GeoffR, As Jim pointed out to me (thanks) that FOUR can not be an incorrect number your MiniZinc program can be adapted accordingly. Now both run modes have similar performance.

        Like

    • Frits's avatar

      Frits 4:56 pm on 11 October 2020 Permalink | Reply

      Pretty slow (especially for high maximum prizes).

      I combined multiple “for loops” into one loop with the itertools product function so it runs both under Python and PyPy.

      @ Jim, your code has some unexplained “# can’t include FOUR” code (line 62, 63).
      It is not needed for performance.

       
      from enigma import SubstitutedExpression
      from itertools import product
      
      mi = 25      # minimum prize
      step = 25    # prizes differ at least <step> pennies 
      ma = 551     # maximum prize + 1
      bits = set() # x1...x12 are bits, 10 of them must be 1, two them must be 0
      
      # return numbers in range and bit value 
      # (not more than 2 bits in (<li> plus new bit) may be 0)
      rng = lambda m, li: product(range(mi, m, step), 
                                 (z for z in {0, 1} if sum(li + [z]) > len(li) - 2))
      
      def report():
        print(f"ONE TWO THREE FOUR FIVE SIX "
        f"SEVEN EIGHT NINE TEN ELEVEN TWELVE"
        f"\n{O+N+E} {T+W+O}   {T+HR+E+E}  {F+O+UR}  {F+I+V+E} {S+I+X} "
        f"  {S+E+V+E+N}   {E+I+GH+T}  {N+I+N+E} {T+E+N}   {E+L+E+V+E+N}"
        f"   {T+W+E+L+V+E}"
        f"\n   O   N   E   T   W   L  HR   F  UR   I   V   S   X  GH"
        f"\n{O:>4}{N:>4}{E:>4}{T:>4}{L:>4}{W:>4}{HR:>4}{F:>4}{UR:>4}"
        f"{I:>4}{V:>4}{S:>4}{X:>4}{GH:>4}")
      
      for (O, N, E) in product(range(mi, ma, step), repeat=3):
        for x1 in {0, 1}:
          if x1 != 0 and O+N+E != 100: continue
          li1 = [x1]
          for (T, x10) in rng(ma, li1):
            if x10 != 0 and T+E+N != 1000: continue
            li2 = li1 + [x10]
            maxW = 201 - 2*mi if sum(li2) == 0 else ma
            for (W, x2) in rng(maxW, li2):
              if x2 != 0 and T+W+O != 200: continue
              li3 = li2 + [x2]
              for (I, x9) in rng(ma, li3):
                if x9 != 0 and N+I+N+E != 900: continue
                li4 = li3 + [x9]
                for L in range(mi, ma, step):
                  for (V, x12) in rng(ma, li4):
                    if x12 != 0 and T+W+E+L+V+E != 1200: continue
                    li5 = li4 + [x12]
                    for x11 in {0, 1}:
                      li6 = li5 + [x11]
                      if sum(li6) < 4 : continue
                      if x11 != 0 and E+L+E+V+E+N != 1100: continue
                      maxF = 501 - 3*mi if sum(li6) == 4 else ma
                      for (F, x5) in rng(maxF, li6):
                        if x5 != 0 and F+I+V+E != 500: continue
                        li7 = li6 + [x5]
                        maxSX = 601 - 2*mi if sum(li7) == 5 else ma
                        for (S, x7) in rng(maxSX, li7):
                          if x7 != 0 and S+E+V+E+N != 700: continue
                          li8 = li7 + [x7]
                          for (X, x6) in rng(maxSX, li8):
                            if x6 != 0 and S+I+X != 600: continue
                            li9 = li8 + [x6]
                            maxGH = 801 - 3*mi if sum(li9) == 7 else ma
                            for (GH, x8) in rng(maxGH, li9):
                              if x8 != 0 and E+I+GH+T != 800: continue
                              li10 = li9 + [x8]
                              maxHR = 301 - 4*mi if sum(li10) == 8 else ma
                              for (HR, x3) in rng(maxHR, li10):
                                if x3 != 0 and T+HR+E+E != 300: continue
                                li11 = li10 + [x3]
                                maxUR = 401 - 3*mi if sum(li11) == 9 else ma
                                for (UR, x4) in rng(maxUR, li11):
                                  if x4 != 0 and F+O+UR != 400: continue
      
                                  r = tuple(li11 + [x4])
                                  if sum(r) != 10: continue  
                                  # only report unique bits
                                  if r not in bits:
                                    report()
                                    bits.add(r)
                                    
      # ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE TEN ELEVEN TWELVE
      # 100 200   300  400  500 600   700   800  125 100   1100   1200
      #    O   N   E   T   W   L  HR   F  UR   I   V   S   X  GH
      #   25  25  50  25 550 150 175  50 325  25 375 200 375 700 
      

      Like

      • Jim Randell's avatar

        Jim Randell 5:17 pm on 11 October 2020 Permalink | Reply

        @Frits: The fact that FOUR cannot be one of the incorrect numbers is one of the conditions mentioned in the puzzle text.

        Like

    • GeoffR's avatar

      GeoffR 8:56 am on 12 October 2020 Permalink | Reply

      @Frits: I never used the fact that FOUR cannot be one of the incorrect numbers.
      I only spelt out a condition in the teaser as part of the programme ie F+O+U+R = 400.
      I do not think my programme needs adapting – is OK as the original posting.

      Like

  • Unknown's avatar

    Jim Randell 12:26 pm on 6 October 2020 Permalink | Reply
    Tags:   

    Teaser 2757: Sports quiz 

    From The Sunday Times, 26th July 2015 [link] [link]

    A sports quiz featured one footballer, one cricketer and one rugby player each week. Over the six-week series the footballers featured were (in order) Gerrard, Lambert, Lampard, Rooney, Smalling and Welbeck. The cricketers were (in some order) Carberry, Compton, Robson, Shahzad, Stokes and Tredwell. The rugby players (in some order) were Cipriani, Launchbury, Parling, Robshaw, Trinder and Twelvetrees. Each week, for any two of the three names, there were just two different letters of the alphabet that occurred in both names (with the letters possibly occurring more than once).

    List the cricketers in the order in which they appeared.

    [teaser2757]

     
    • Jim Randell's avatar

      Jim Randell 12:26 pm on 6 October 2020 Permalink | Reply

      Another puzzle by Graham Smithers that can be solved using the [[ grouping ]] routines from the enigma.py library.

      This Python program runs in 46ms.

      Run: [ @repl.it ]

      from enigma import grouping
      
      football = ('Gerrard', 'Lambert', 'Lampard', 'Rooney', 'Smalling', 'Welbeck')
      cricket = ('Carberry', 'Compton', 'Robson', 'Shahzad', 'Stokes', 'Tredwell')
      rugby = ('Cipriani', 'Launchbury', 'Parling', 'Robshaw', 'Trinder', 'Twelvetrees')
      
      grouping.solve([football, cricket, rugby], grouping.share_letters(2))
      

      Solution: The cricketers, in order of appearance are: Shahzad, Robson, Carberry, Tredwell, Compton, Stokes.

      Like

    • Frits's avatar

      Frits 11:36 am on 9 October 2020 Permalink | Reply

      A general solution based on logical rules, I also tested similar grouping puzzles.
      This time I have left the lines to print logical deductions and intermediate solutions.

      NB, does anyone know a solution in Python for splitting print statements (because of 80 chars) so that the code still looks nice? The enigma printf routine also has the same effect as print.

       
      from collections import defaultdict
      from enigma import flatten
      
      label = ["", "single", "pair", "triple", "quadruple", "", "", "", "", "", ""]
      for i in range(5, 11):
        label[i] = str(i) + "-tuple"
      
      # number of shared letters (case insensitive)
      nr_share = lambda a, b: sum(1 for x in set(a.lower()) if x in set(b.lower()))
      
      def report(txt, dict1_2, dict1_3, dict2_3, s1, s2, s3):
        #return
        if txt != "":
          print(f"\n{txt}")
        print(f"\n---------- {s1} - {s2} ------------------------")
        for k, vs in dict1_2.items(): print(f"{k:<12} {vs}")    
        print(f"---------- {s1} - {s3} ------------------------")
        for k, vs in dict1_3.items(): print(f"{k:<12} {vs}")  
        print(f"---------- {s2} - {s3} ------------------------")
        for k, vs in dict2_3.items(): print(f"{k:<12} {vs}")
        print()
        
        
      # check if all dict1_2 and dict1_3 entries have been solved 
      def solved():  
        for k1, vs1 in dict1_2.items():
          if len(vs1) != 1 or len(dict1_3[k1]) != 1:
            return False
        return True  
      
      # look for unique values in dictionary
      def value_unique(di, lst):
        # for each element in lst
        for li in lst:
          uniq = [k for k, vs in di.items() if li in vs]
         
          if len(uniq) == 1 and len(di[uniq[0]]) > 1:
            print(f"unique value {li} in list, \
      {uniq[0]}'s values {di[uniq[0]]} --> {li}")
            di[uniq[0]] = [li]
            reduce_for_nTuples(di)
          
      # look for entries with same dict1_3 and dict2_3 unique value
      def same_list2_list3_value():
        samevals = [(k1, k2) for k1, vs1 in dict1_3.items() 
                    for k2, vs2 in dict2_3.items() if vs1 == vs2 and len(vs1) == 1]
        for k1, k2 in samevals:
          if dict1_2[k1] != [k2]:
            print(f"same value {dict1_3[k1][0]} in 2 and 3, {k1} must be {k2}")
            dict1_2[k1] = [k2]
      
          
      # check for implications of direct relations in dict1_2 
      def reduce_further():
        for k1, vs1 in dict1_2.items():
          if len(vs1) == 1:
            # check other dictionaries
            if len(dict2_3[vs1[0]]) == 1:
              # k1 --> vs1[0], vs1[0] --> unique val2 so val2 belongs to k1
              if dict1_3[k1] != dict2_3[vs1[0]]:
                print(f"{k1} --> {vs1[0]}, {vs1[0]} --> {dict2_3[vs1[0]][0]}: \
      {k1} must be {dict2_3[vs1[0]][0]}")
                dict1_3[k1] = dict2_3[vs1[0]]
                reduce_for_nTuples(dict1_3)
            if len(dict1_3[k1]) == 1:
              if dict2_3[vs1[0]] != dict1_3[k1]:
                print(f"{k1} --> {vs1[0]}, {k1} --> {dict1_3[k1][0]}: \
      {vs1[0]} must be {dict1_3[k1][0]}")
                dict2_3[vs1[0]] = dict1_3[k1]
                reduce_for_nTuples(dict2_3)
                
            # if dict1_2 entry <key1> has only one value <val1>
            # then dict2_3[val1] and dict1_3[key1] must have same values 
            set3 = set(dict2_3[vs1[0]]) & set(dict1_3[k1])
            if len(set3) != len(dict2_3[vs1[0]]) or \
               len(set3) != len(dict1_3[k1]):
      
              #print(k1, vs1, "set3", set3, dict2_3[vs1[0]], dict1_3[k1])
              if len(set3) != len(dict2_3[vs1[0]]):
                print(f"{k1} --> {vs1[0]}, intersection {dict1_3[k1]} and \
      {dict2_3[vs1[0]]} = {set3}\n--> {k1} must be {set3}")
              if len(set3) != len(dict1_3[k1]):
                print(f"{k1} --> {vs1[0]}, intersection {dict1_3[k1]} and \
      {dict2_3[vs1[0]]} = {set3}\n--> {vs1[0]} must be {set3}")
              dict2_3[vs1[0]] = list(set3)
              dict1_3[k1] = list(set3)    
              
      # look for singles, pairs, triples, quads, etc (n-tuples)
      def reduce_for_nTuples(di1, extra=""):
        for i in range(1, len(di1)):
          for k1, vs1 in di1.items():
            if len(vs1) != i: continue
            set1 = set(vs1)
            klist = [k1]
            # collect indices in same <i>-tuple
            for k2, vs2 in di1.items():
              if k2 == k1: continue
              if len(set1 | set(vs2)) == len(set1):
                klist.append(k2)
            
            if len(klist) != i: continue
            
            # klist now contains indices of the <i>-tuple
            # remove <i>-tuple values from other entries in di1
            for k2, vs2 in di1.items():
              if k2 in klist: continue
              for s in set1:
                if s not in vs2: continue
               
                di1[k2].remove(s)
                if len(set1) > 1:  
                  print(f"{label[i]} {set1} for keys {klist}: remove {s} from {k2}")
              
            if extra != "" and i != 1: 
              # check if entries in <i>-tuple have <i> values in dict2_3    
              li = [dict2_3[j] for j in set1]
              # unique values
              set2 = {x for x in flatten(li)}
              if len(set2) != i: continue
      
              # remove values from dict1_3 which are not in set2
              for k in klist:
                for val in dict1_3[k]:
                  if val not in set2:
                    dict1_3[k].remove(val)
                    print(f"{label[i]} {set1} for keys {klist} has to use {set2} \
      \n--> remove {val} from entry {k}")
                        
                       
      # Teaser 2757 
      list1 = ('Gerrard', 'Lambert', 'Lampard', 'Rooney', 'Smalling', 'Welbeck')
      list2 = ('Carberry', 'Compton', 'Robson', 'Shahzad', 'Stokes', 'Tredwell')
      list3 = ('Cipriani', 'Launchbury', 'Parling', 'Robshaw', 'Trinder',
               'Twelvetrees')
         
      (sub1, sub2, sub3) = ("football", "cricket", "rugby")        
      '''
      # Teaser 2816
      list1 = ( 'Dimbleby', 'Evans', 'Mack', 'Marr', 'Norton', 'Peschardt', 'Ross')
      list2 = ( 'Arterton', 'Blunt', 'Carter', 'Jones', 'Knightley', 'Margolyes',
                'Watson')
      list3 = ( 'Bean', 'Caine', 'Cleese', 'Craig', 'De Niro', 'Neeson', 'Oldman')
      
      (sub1, sub2, sub3) = ("hosts", "actresses", "actors")
      
      # Teaser 2892
      
      list1 = ('MARR', 'NEIL', 'NEWMAN', 'PARKINSON', 'PAXMAN', 'POPPLEWELL', 
               'ROBINSON', 'SNOW')
      list2 = ('ABBOTT', 'ABRAHAMS', 'CHAKRABARTI', 'CORBYN', 'EAGLE', 'LEWIS', 
               'STARMER', 'WATSON')
      list3 = ('BRADLEY', 'GRAYLING', 'GREEN', 'HAMMOND', 'LEADSOM', 'LIDINGTON', 
               'MCLOUGHLIN', 'MUNDELL')
       
      (sub1, sub2, sub3) = ("presenters", "labs", "cons")
      
      # Teaser 2682 
      list1 = ('Drax', 'Jaws', 'Krest', 'Largo', 'Morant', 'Moth', 
               'Sanguinette', 'Silva')
      list2 = ('Earth', 'Jupiter', 'Mars', 'Mercury', 'Neptune', 'Saturn', 
               'Uranus', 'Venus')
      list3 = ('Brosnan', 'Casenove', 'Connery', 'Craig', 'Dalton',  'Dench', 
               'Lazenby', 'Moore')
      
      (sub1, sub2, sub3) = ("villain", "planet", "agent")
       '''
      
      dict1_2 = defaultdict(list)
      dict1_3 = defaultdict(list)
      dict2_3 = defaultdict(list)
      
      # initialize dictionaries
      for f in list1:
        for c in list2:
          if nr_share(f, c) == 2:
            dict1_2[f] += [c]
        for r in list3:
          if nr_share(f, r) == 2:
            dict1_3[f] += [r]   
            
      for c in list2:
        for r in list3:
          if nr_share(c, r) == 2:
            dict2_3[c] += [r]     
      
      loop = 0
      while(loop < 7):  
        loop += 1
        print(" ----------------------- loop", loop)
        
        # look for singles, pairs, triples, quads, etc
        reduce_for_nTuples(dict1_2, "extra")
        reduce_for_nTuples(dict1_3)
        reduce_for_nTuples(dict2_3)
      
        report("------ after reduce_for_nTuples", dict1_2, dict1_3, dict2_3, 
               sub1, sub2, sub3)
      
        # look for unique values in dictionary
        value_unique(dict1_2, list2)
        value_unique(dict1_3, list3)
        value_unique(dict2_3, list3)
      
        # check for implications of direct relations in dict1_2 
        reduce_further()
        
        # look for entries with same dict1_3 and dict2_3 unique value
        same_list2_list3_value()
      
      
        report("-- end loop", dict1_2, dict1_3, dict2_3, 
               sub1, sub2, sub3)
      
        if solved():
          print()
          for k1, vs1 in dict1_2.items():
            print(f"{k1:<12} {vs1[0]:<12} {dict1_3[k1][0]:<12}")
          print()  
          break
      

      Like

      • Jim Randell's avatar

        Jim Randell 1:35 pm on 9 October 2020 Permalink | Reply

        @Frits: In Python if two strings appear next to each other they are concatenated by the parser:

        print(
          "<part 1>"
          "<part 2>"
          "<part 3>"
        )
        

        Is the same as:

        print("<part 1><part 2><part 3>")
        

        Like

        • Frits's avatar

          Frits 2:00 pm on 10 October 2020 Permalink | Reply

          Thanks,

          Luckily we have the enigma printf function.

           
          print(f"a1 {sub1} a2")
          print(f"a1 "
                 "{sub1} a2")
          printf("a1 "
                 "{sub1} a2")      
                 
          # a1 villain a2
          # a1 {sub1} a2
          # a1 villain a2       
          

          Like

          • Jim Randell's avatar

            Jim Randell 2:20 pm on 10 October 2020 Permalink | Reply

            @Frits: The f"..." construct in Python 3 is also a string:

            >>> (x, y) = ("this", "that")
            >>> print(
              f"{x}"
              " and "
              f"{y}"
            )
            this and that
            

            Like

  • Unknown's avatar

    Jim Randell 10:20 am on 4 October 2020 Permalink | Reply
    Tags: by: A. J. Weight   

    Brain-Teaser 9: Whose daughter? 

    From The Sunday Times, 23rd April 1961 [link]

    Mrs. A, Mrs. B and Mrs. C and their three daughters bought materials for dresses. Each lady spent as many shillings per yard as she bought yards. Each mother spent £5 5s more than her daughter. Mrs. A bought 11 yards more than Florence, and Mrs. C spent £6 15s less than Patricia. The other daughter’s name was Mary.

    Whose daughter was each?

    Note: £1 = 20s.

    [teaser9]

     
    • Jim Randell's avatar

      Jim Randell 10:21 am on 4 October 2020 Permalink | Reply

      Working in shillings. (There are 20s to £1).

      For any of the ladies, if the price per yard of the material was x (shillings per yard), then that lady bought x yards, and so spent x² shillings.

      And each mother spent 105 shillings more than her daughter, so we are looking for solutions to:

      x² − y² = 105

      where x is the value for the mother and y is the value for the daughter.

      We can solve this equation for integer values, by considering divisors of 105:

      (x + y)(x − y) = 105

      This Python program runs in 47ms.

      Run: [ @repl.it ]

      from enigma import (divisors_pairs, div, Record, subsets, sq, printf)
      
      # find solutions to x^2 - y^2 = 105 (over positive integers)
      xys = list(Record(x=div(b + a, 2), y=div(b - a, 2)) for (a, b) in divisors_pairs(105))
      
      # choose (mother, daughter) pairs
      (A, B, C) = (0, 1, 2)
      for ss in subsets(xys, size=3, select='P'):
      
        # choose daughters to be F, M, P
        for (F, M, P) in subsets((A, B, C), size=len, select='P'):
      
          # "Mrs A bought 11 yards more than F"
          if not (ss[A].x - ss[F].y == 11): continue
      
          # "Mrs C spent 135s less than P"
          if not (sq(ss[C].x) + 135 == sq(ss[P].y)): continue
      
          ns = "ABC"
          printf("{F}+F {M}+M {P}+P [A={A} B={B} C={C}]", F=ns[F], M=ns[M], P=ns[P], A=ss[A], B=ss[B], C=ss[C])
      

      Solution: Patricia is Mrs. A’s daughter. Florence is Mrs. B’s daughter. Mary is Mrs. C’s daughter.

      The number of yards bought, and number of shillings spent is:

      Mrs. A = 19; P = 16
      Mrs. B = 13; F = 8
      Mrs. C = 11; M = 4

      There are only four solutions to the equation x² − y² = 105. The unused one is: (x = 53, y = 52).

      Like

    • Frits's avatar

      Frits 7:10 pm on 4 October 2020 Permalink | Reply

      A Solution in MiniZinc.

      I am not very happy with the output statements.
      Does anyone know a better way?

      include "globals.mzn";
       
      var  12..53: A;
      var  11..53: B;
      var  11..51: C;
      % daughters
      var  1..52: DA;
      var  1..52: DB;
      var  1..52: DC;
      
      var  1..52: M;
      var  12..52: P;
      var  1..42: F;
       
      % Mrs. A bought 11 yards more than Florence
      constraint F + 11 = A;
      % Mrs. C spent 6pnd 15s less than Patricia
      constraint C * C + 135 = P * P;
      
      % Each mother spent 5pnd 5s more than her daughter
      constraint  A * A == DA * DA + 105;
      constraint  B * B == DB * DB + 105;
      constraint  C * C == DC * DC + 105;
      % Florence is not Mrs. A's daughter
      constraint  DA != F;
      % Patricia is not Mrs. C's daughter
      constraint  DC != P;
      
      % DA, DB, DC is the same list as F, M, P
      constraint sort([DA, DB, DC]) = sort([F, M, P]);
       
      solve satisfy;
      
      output [if fix(DA)==fix(P) then "Mrs A's daughter is Patrica\n" 
             else if fix(DA)==fix(M) then "Mrs A's daughter is Mary\n"
             else "Mrs A's daughter is Florence\n" endif endif];
      
      output [if fix(DB)==fix(P) then "Mrs B's daughter is Patrica\n" 
             else if fix(DB)==fix(M) then "Mrs B's daughter is Mary\n"
             else "Mrs B's daughter is Florence\n" endif endif];
      
      output [if fix(DC)==fix(P) then "Mrs C's daughter is Patrica\n" 
             else if fix(DC)==fix(M) then "Mrs C's daughter is Mary\n"
             else "Mrs C's daughter is Florence\n" endif endif];
             
      % Mrs A's daughter is Patrica
      % Mrs B's daughter is Florence
      % Mrs C's daughter is Mary       
      

      Like

    • Brian Gladman's avatar

      Brian Gladman 10:34 am on 8 October 2020 Permalink | Reply

      @Frits As you have found, MiniZinc is great for certain types of problems but its output facilities are poor to say the least. But Jim has provided an excellent Python wrapper for MiniZInc and like Jim I pretty well always use this rather than MiniZinc. The wrapper also has the further advantage that multiple MiniZInc outputs can be further processed and this allows hybrid MiniZinc/Python solutions that can gain the best from both worlds.

      Like

      • Frits's avatar

        Frits 11:46 am on 9 October 2020 Permalink | Reply

        @Jim and Brian
        Thanks.

        I tried to make a user defined function in MiniZinc but that took me too much effort.
        I also have to get used to the way the MiniZinc documentation has been set up.

        Like

  • Unknown's avatar

    Jim Randell 4:49 pm on 2 October 2020 Permalink | Reply
    Tags:   

    Teaser 3028: Rainbow numeration 

    From The Sunday Times, 4th October 2020 [link] [link]

    Dai had seven standard dice, one in each colour of the rainbow (ROYGBIV). Throwing them simultaneously, flukily, each possible score (1 to 6) showed uppermost. Lining up the dice three ways, Dai made three different seven-digit numbers: the smallest possible, the largest possible, and the “rainbow” (ROYGBIV) value. He noticed that, comparing any two numbers, only the central digit was the same, and also that each number had just one single-digit prime factor (a different prime for each of the three numbers).

    Hiding the dice from his sister Di’s view, he told her what he’d done and what he’d noticed, and asked her to guess the “rainbow” number digits in ROYGBIV order. Luckily guessing the red and orange dice scores correctly, she then calculated the others unambiguously.

    What score was on the indigo die?

    I’ve changed the wording of the puzzle slightly to make it clearer.

    [teaser3028]

     
    • Jim Randell's avatar

      Jim Randell 5:17 pm on 2 October 2020 Permalink | Reply

      (Note: I’ve updated my program (and the puzzle text) in light of the comment by Frits below).

      This Python program runs in 49ms.

      Run: [ @repl.it ]

      from enigma import irange, subsets, nconcat, filter_unique, printf
      
      # single digit prime divisor
      def sdpd(n):
        ps = list(p for p in (2, 3, 5, 7) if n % p == 0)
        return (ps[0] if len(ps) == 1 else None)
      
      # if flag = 0, check all values are the same
      # if flag = 1, check all values are different
      check = lambda flag, vs: len(set(vs)) == (len(vs) if flag else 1)
      
      # all 6 digits are represented
      digits = list(irange(1, 6))
      
      # but one of them is repeated
      ans = set()
      for (i, d) in enumerate(digits):
        ds = list(digits)
        ds.insert(i, d)
        # make the smallest and largest numbers
        smallest = nconcat(ds)
        p1 = sdpd(smallest)
        if p1 is None: continue
        largest = nconcat(ds[::-1])
        p2 = sdpd(largest)
        if p2 is None or p2 == p1: continue
        printf("smallest = {smallest} ({p1}); largest = {largest} ({p2})")
      
        # find possible "rainbow" numbers
        rs = list()
        for s in subsets(ds[:3] + ds[4:], size=len, select="P", fn=list):
          s.insert(3, ds[3])
          # rainbow has only the central digit in common with smallest and largest
          if not all(check(i != 3, vs) for (i, vs) in enumerate(zip(s, ds, ds[::-1]))): continue
          rainbow = nconcat(s)
          p3 = sdpd(rainbow)
          if p3 is None or not check(1, (p1, p2, p3)): continue
      
          rs.append(tuple(s))
      
        # find rainbow numbers unique by first 2 digits
        for rainbow in filter_unique(rs, (lambda s: s[:2])).unique:
          n = nconcat(rainbow)
          printf("-> rainbow = {n} ({p3})", p3=sdpd(n))
          # record the indigo value
          ans.add(rainbow[5])
      
      # output solution
      printf("indigo = {ans}")
      

      Solution: The score on the indigo die is 4.

      Each of the digits 1-6 is used once, and there is an extra copy of one of them. So there is only one possible set of 7 digits used.

      The smallest number is: 1234456 (divisible by 2).

      And the largest number is: 6544321 (divisibly by 7).

      There are 17 possible values for the “rainbow” number, but only 3 of them are uniquely identified by the first 2 digits: 2314645, 3124645, 3614245 (and each is divisible by 5).

      The scores on the green, indigo and violet dice are the same for all three possible “rainbow” numbers: 4, 4, 5. So this gives us our answer.

      Like

    • Frits's avatar

      Frits 11:02 pm on 2 October 2020 Permalink | Reply

      “each number had just one prime factor under 10 (different for each number)”.

      The three numbers you report seem to have same prime factors under 10, maybe I have misunderstood.

      Like

      • Jim Randell's avatar

        Jim Randell 11:10 pm on 2 October 2020 Permalink | Reply

        @Frits: I think you could be right. I took it to mean that it wasn’t the same prime in each case (two of the numbers I originally found share a prime). But requiring there to be three different primes does also give a unique answer to the puzzle (different from my original solution). So it could well be the correct interpretation (and it would explain why we weren’t asked to give the rainbow number). Thanks.

        Like

    • Frits's avatar

      Frits 11:35 pm on 2 October 2020 Permalink | Reply

      @Jim: I hope to publish my program tomorrow (for three different prime numbers). I don’t have a clean program yet.

      Your solution also seems to be independent of the indigo question (it could have been asked for another colour). In my solution this specific colour is vital for the solution.

      Like

    • Frits's avatar

      Frits 11:26 am on 3 October 2020 Permalink | Reply

      Next time I try to use the insert function (list).

       
      from enigma import factor, irange, concat, peek 
      from itertools import permutations as perm
      from collections import defaultdict
      
      P = {2, 3, 5, 7}
      
      # number of same characters at same positions
      nr_common = lambda x, y: sum(1 for i, a in enumerate(x) if a == y[i])
      
      # prime factors under 10
      factund10 = lambda x: [p for p in P if int(x) % p == 0]
      
      # if list contains one entry then return possible values at position i
      charvals = lambda s, i: {x[0][i] for x in s if len(x) == 1}
      
      n = 6
      digits = concat([x for x in irange(1, n)])
      
      # for each digit i occuring twice in lowest and highest number
      for i in irange(1, n):
        str_i = str(i)
        # build lowest and highest number
        low = digits[:i] + str_i + digits[i:]
        hgh = low[::-1]
       
        # get prime factors under 10 
        u10low = factund10(low) 
        u10hgh = factund10(hgh) 
        
        # each number with one prime factor under 10 (different for each number)
        if len(u10low) != 1 or len(u10hgh) != 1 or u10low[0] == u10hgh[0]:
           continue
       
        rainbow = defaultdict(list)
       
        # for this combination of lowest and highest check the rainbow possibilities
        for pe in perm(low[:n//2] + low[n//2 + 1:]):
          # weave central digit into permutation pe at center position
          rb = concat(pe[:n//2] + tuple(low[n//2]) + pe[n//2:])
          
          # check rainbow number on only the central digit being the same
          if nr_common(rb, low) != 1 or nr_common(rb, hgh) != 1: 
            continue
      
          u10rb = factund10(int(rb)) 
          # all three prime factors under 10 are different
          if len(u10rb) == 1 and u10rb[0] != u10low[0] and u10rb[0] != u10hgh[0]:
            # store rainbow number with as key first 2 digits
            rainbow[rb[:2]].append(rb)
            
        # if first 2 digits rainbow number is unique then list indigo values
        indigo = charvals(rainbow.values(), 5)  
        if len(indigo) == 1:
          print(f"The score on the indigo die: {peek(indigo)}")
      

      Like

    • Frits's avatar

      Frits 2:58 pm on 4 October 2020 Permalink | Reply

      A more efficient program (without explaining the choices as this is a new puzzle).

       
      from enigma import factor, irange, concat, diff 
      from itertools import permutations as perm
      from collections import defaultdict
      
      # number of same characters at same positions
      nr_common = lambda x, y: sum(1 for i, a in enumerate(x) if a == y[i])
      
      # prime factors under 10
      factund10 = lambda x: [p for p in {2, 5, 7} if int(x) % p == 0]
      
      # if list contains one entry then return possible values at position i
      charvals = lambda s, i: {x[0][i] for x in s if len(x) == 1}
      
      n = 6
      digits = concat([x for x in irange(1, n)])
      
      # for each digit i occuring twice in lowest and highest number
      for i in irange(1, n):
        if i % 3 == 0: continue
        
        # build lowest and highest number
        low = digits[:i] + str(i) + digits[i:]
        hgh = low[::-1]
       
        # get prime factors under 10 
        u10low = factund10(low)       
        u10hgh = factund10(hgh)       
       
        if len(u10hgh) != 1 or u10hgh[0] != 7: continue
        
        # each number with one prime factor under 10 (different for each number)
        if len(u10low) != 1: continue
        
        rainbow = defaultdict(list)
         
        # for this combination of lowest and highest check the rainbow possibilities
        center = str(i)
        remaining = diff(low[:n//2] + low[n//2 + 1:], "5")
        # try possibilities for first digit  
        for d1 in diff(remaining, "1" + str(n)):
          # find values for positions without the edges and the center
          for pe2 in perm(diff(remaining, d1) , n - 2):
            # build rainbow number
            rb = d1 + concat(pe2[:(n-1)//2]) + center + \
                 concat(pe2[(n-1)//2:]) + "5"
            
            if int(rb) % u10hgh[0] == 0:      
              continue
           
            # check rainbow number on only the central digit being the same
            if nr_common(rb, low) != 1 or nr_common(rb, hgh) != 1: 
              continue
            
            # store rainbow number with as key first 2 digits
            rainbow[rb[:2]].append(rb)
        
        # if first 2 digits rainbow number is unique then list indigo values
        indigo = charvals(rainbow.values(), 5)  
        if len(indigo) == 1:
          print(f"The score on the indigo die: {indigo.pop()}")  
      

      Like

  • Unknown's avatar

    Jim Randell 8:55 am on 1 October 2020 Permalink | Reply
    Tags:   

    Teaser 2544: Neighbourly nonprimes 

    From The Sunday Times, 26th June 2011 [link] [link]

    I live in a long road with houses numbered 1 to 150 on one side. My house is in a group of consecutively numbered houses where the numbers are all nonprime, but at each end of the group the next house number beyond is prime. There are a nonprime number of houses in this group. If I told you the lowest prime number which is a factor of at least one of my two next-door neighbours’ house numbers, then you should be able to work out my house number.

    What is it?

    [teaser2544]

     
    • Jim Randell's avatar

      Jim Randell 8:55 am on 1 October 2020 Permalink | Reply

      I implemented the [[ chop() ]] function, which chops a sequence into contiguous segments that give the same value for a function. This can then be used to select the segments of contiguous non-primes.

      We can use a little shortcut to check if a prime p divides one of the neighbours of n. If we consider the product of the neighbours ((n − 1) and (n + 1)), then if the prime divides the product, it must divide one of the neighbours. Now:

      (n − 1)(n + 1) = n² − 1

      So, if the residue of n² modulo p is 1, then p divides one of the neighbours of n.

      The following Python program runs in 47ms.

      from enigma import (irange, primes, peek, filter_unique, printf)
      
      # chop sequence s into segments that give the same value for function f
      # return pairs of (f-value, segment)
      def chop(f, s):
        (fv, ss) = (None, [])
        for x in s:
          v = f(x)
          if v == fv:
            ss.append(x)
          else:
            if ss: yield (fv, ss)
            (fv, ss) = (v, [x])
        if ss: yield (fv, ss)
      
      # primes up to 150
      primes.expand(150)
      
      # consider segments of non-primes
      for (fv, ss) in chop(primes.is_prime, irange(1, 150)):
        if fv: continue
        # the length of the segment is non-prime (but > 1)
        n = len(ss)
        if n < 2 or n in primes: continue
      
        # the lowest prime number that is a factor of at least one of my neighbours
        # house numbers uniquely identifies my house number
        f = lambda n: peek(p for p in primes if (n * n) % p == 1)
        hs = filter_unique(ss, f).unique
      
        # output solution
        printf("group = {ss}; house = {hs}")
      

      Solution: Your house number is 144.

      It turns out there is only one segment of non-primes (in the specified range) that has a (non-unity) non-prime length:

      (140, 141, 142, 143, 144, 145, 146, 147, 148)

      It contains 9 numbers.

      And the lowest prime factor of the neighbours uniquely identifies one of the numbers. So it must be an even number, and they all have a smallest-neighbour-factor of 3, except for 144 which has a smallest-neighbour-factor of 5.

      Like

      • Jim Randell's avatar

        Jim Randell 12:57 pm on 1 October 2020 Permalink | Reply

        Or even simpler:

        Run: [ @replit ]

        from enigma import (irange, primes, tuples, peek, filter_unique, printf)
        
        # consider 2 consecutive primes
        for (a, b) in tuples(primes.between(2, 150), 2):
          # with a non-prime number of non-primes in between
          n = b - a - 1
          if n < 2 or n in primes: continue
          (a, b) = (a + 1, b - 1)
        
          # the lowest prime number that is a factor of at least one of my neighbours
          # house numbers uniquely identifies my house number
          f = lambda n: peek(p for p in primes if (n * n) % p == 1)
          hs = filter_unique(irange(a, b), f).unique
        
          # output solution
          printf("group = [{a} .. {b}]; house = {hs}")
        

        Like

        • Frits's avatar

          Frits 5:05 pm on 1 October 2020 Permalink | Reply

          Nice,

          The output is a little different from the previous program.
          (the definition of a group excludes prime numbers)

          filter_unique also works with a+1 and b-1.

          Like

          • Jim Randell's avatar

            Jim Randell 5:23 pm on 1 October 2020 Permalink | Reply

            @Frits: Yes, you are right. The range should exclude the primes. I’ve fixed it up now.

            Like

    • Frits's avatar

      Frits 10:31 am on 1 October 2020 Permalink | Reply

      [reorder=0] clause was necessary for a decent run time (100 ms).

       
      from enigma import SubstitutedExpression, is_prime
          
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ 
          "A < 2",  # speed up process
          "ABC < 148",
          "D < 2 and D >= A",  # speed up process
          "DEF > ABC + 2",
          "DEF < 151",
          # Find 2 prime numbers
          "is_prime(ABC)",
          "is_prime(DEF)",
          # There are a nonprime number of houses in this group
          "not is_prime(DEF - ABC - 1)",
          # with no other prime numbers between ABC and DEF
          "sum(1 for i in range(ABC + 1, DEF) if is_prime(i)) == 0",
          # my house number MNO lies in such a group
          "M < 2 and M >= A",  # speed up process
          "MNO > ABC",
          "MNO < DEF",
          # my neighbour on one side
          "MNO - 1 = UVW",
          # my neighbour on the other side
          "MNO + 1 = XYZ",
          # lowest prime number which is a factor of at least one of my 
          # neighbours' house numbers
          "min([x for x in range(2, 150) if UVW % x == 0 and is_prime(x)]) = JKL",
          "min([x for x in range(2, 150) if XYZ % x == 0 and is_prime(x)]) = GHI",
        ],
        answer="(min(GHI, JKL), MNO)",
        verbose=0,
        d2i="",        # allow number representations to start with 0
        distinct="",   # allow variables with same values
        reorder=0,
      )
       
      # solve puzzle
      answs = [y for _, y in p.solve()]
      
      # only print answers with a unique first element
      if len(answs) > 0:
        print("My house number:", [a[1] for a in answs 
              if [x[0] for x in answs].count(a[0]) == 1][0])
      
      # My house number: 144
      

      Like

  • Unknown's avatar

    Jim Randell 9:10 am on 29 September 2020 Permalink | Reply
    Tags:   

    Teaser 1904: Neat answer 

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

    I have started with a four-figure number with its digits in decreasing order. I have reversed the order of the four digits to give a smaller number. I have subtracted the second from the first to give a four-figure answer, and I have seen that the answer uses the same four digits — very neat!

    Substituting letters for digits, with different letters being consistently used for different digits, my answer was NEAT!

    What, in letters, was the four-figure number I started with?

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

    [teaser1904]

     
    • Jim Randell's avatar

      Jim Randell 9:19 am on 29 September 2020 Permalink | Reply

      We can use the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this puzzle.

      The following run file executes in 91ms.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "WXYZ - ZYXW = NEAT"
      "ordered(N, E, A, T) == (Z, Y, X, W)"
      
      --distinct="WXYZ,NEAT"
      --answer="translate({WXYZ}, str({NEAT}), 'NEAT')"
      

      Solution: The initial 4-figure number is represented by: ANTE.

      So the alphametic sum is: ANTEETNA = NEAT.

      And the corresponding digits: 7641 − 1467 = 6174.

      Like

    • Hugh Casement's avatar

      Hugh Casement 1:37 pm on 29 September 2020 Permalink | Reply

      If we had not been told the digits were in decreasing order there would have been three other solutions:
      2961 – 1692 = 1269, 5823 – 3285 = 2538, 9108 – 8019 = 1089.

      Like

      • Finbarr Morley's avatar

        Finbarr Morley 10:58 am on 24 November 2020 Permalink | Reply

        I’m not sure that’s right:
        2961-1692= 1269
        ANTE-ETNA=EATN
        It’s true, but it’s not NEAT!

        Like

        • Jim Randell's avatar

          Jim Randell 8:57 am on 25 November 2020 Permalink | Reply

          The result of the sum is NEAT by definition.

          Without the condition that digits in the number you start with are in descending order, we do indeed get 4 different solutions. Setting the result to NEAT, we find they correspond to four different alphametic expressions:

          9108 − 8019 = 1089 / TNEAAENT = NEAT
          5823 − 3285 = 2538 / ETNAANTE = NEAT
          7641 − 1467 = 6174 / ANTEETNA = NEAT
          2961 − 1692 = 1269 / ETANNATE = NEAT

          Like

    • GeoffR's avatar

      GeoffR 6:30 pm on 29 September 2020 Permalink | Reply

      
      from itertools import permutations
      from enigma import nreverse, nsplit
      
      for p1 in permutations((9, 8, 7, 6, 5, 4, 3, 2, 1, 0), 4):
        W, X, Y, Z = p1
        if W > X > Y > Z:
          WXYZ = 1000 * W + 100 * X + 10 * Y + Z
          ZYXW = nreverse(WXYZ)
          NEAT = WXYZ - ZYXW
          N, E, A, T = nsplit(NEAT)
          # check sets of digits are the same
          if {W, X, Y, Z} == {N, E, A, T}:
            print(f"Sum is {WXYZ} - {ZYXW} = {NEAT}")
      
      # Sum is 7641 - 1467 = 6174
      
      

      Like

    • GeoffR's avatar

      GeoffR 8:56 am on 25 November 2020 Permalink | Reply

      I checked Hugh’s assertion, changing my programme to suit, and it looks as though he is correct.
      My revised programme gave the original correct answer and the three extra answers, as suggested by Hugh.
      @Finnbarr:
      If we take NEAT = 1269, then ETAN – NATE = NEAT – (Sum is 2961 – 1692 = 1269)

      
      from itertools import permutations
      from enigma import nreverse, nsplit
       
      for p1 in permutations((9, 8, 7, 6, 5, 4, 3, 2, 1,0), 4):
          W, X, Y, Z = p1
          #if W > X > Y > Z:
          WXYZ = 1000 * W + 100 * X + 10 * Y + Z
          ZYXW = nreverse(WXYZ)
          NEAT = WXYZ - ZYXW
          if NEAT > 1000 and WXYZ > 1000 and ZYXW > 1000:
              N, E, A, T = nsplit(NEAT)
              # check sets of digits are the same
              if {W, X, Y, Z} == {N, E, A, T}:
                print(f"Sum is {WXYZ} - {ZYXW} = {NEAT}")
       
      # Sum is 9108 - 8019 = 1089
      # Sum is 7641 - 1467 = 6174  << main answer
      # Sum is 5823 - 3285 = 2538
      # Sum is 2961 - 1692 = 1269
      
      

      Like

    • Finbarr Morley's avatar

      Finbarr Morley 9:41 am on 30 November 2020 Permalink | Reply

      A maths student would view this as 4 unknowns (A,N,T, & E) and 4 Equations.
      So it can be uniquely solved with simultaneous equations.

      And there’s a neat ‘trick’ recognising the pairs of letters:

      A E
      E A

      And

      N T
      T N

      The ANSWER is the easy bit:

      ANTE
      7641

      The SOLUTION is as follows:

      Column 4321
             ANTE
           - ETNA
           = NEAT
      

      From the 1,000’s column 4 we seen that A>E, otherwise the answer would be negative.
      ∴ in the 1’s column, (where E is less than A) E must borrow 1 from the T in the 10’s column:

      Col.   2        1
             T-1   10+E
             N        A
             A        T  
      

      There are 2 scenarios:
      (A) N>T
      (B) N<T

      As before, T in the 10’s column must borrow 1 from the N in the 100’s column.

          4    3     2       1
          A  N-1  10+T-1  10+E             
        - E    T     N       A
        = N    E     A       T
      

      The equations for each of the 4 columns are:

      (1) 10+E-A = T
      (2) 10+T-1-N = A
      (3) N-1-T = E
      (4) A-E = N

      Rearrange to:

      (1) A-E = 10-T
      (4) A-E = N

      (2) N-T = 9-A
      (3) N-T = E+1

      (1-4) 10-T = N (because they both equal A-E) rearrange to N+T = 10
      (2-3) 9-A = E+1 (because they both equal N-T) rearrange to A+E = 8

      From here either solve by substituting numbers, or with algebra.

      SUBSTITUTION:

      A+E=8 and A>E. So A = 5,6,7 or 8

      A   E     A-E=N     N+T=10
      5   3         2       8  N>T
      6   2         4       6  N>T
      7   1         6       4  *** THIS IS THE ONLY SOLUTION ***
      8   0         8	      2 can’t have 2 numbers the same
      

      ALGERBRA:

                N+T=10             A+E=8
           (2)-(N-T=9-A)      (1)+(A-E=10-T)	
                 2T=1+A  			                    2A    =(18-T)
       (x2)      4T=2+(2A)
      
      ∴   4T=2+(18-T)   
          5T=20
           T=4
      ∴    N=6 (N+T=10)
      ∴    E=1  (N-T=1+E)
      ∴    A=7  (A+E=8)
      

      Check assumption (A) N>T TRUE (6>4)

      ANSWER:
      ANTE 7641
      - ETNA – 1467
      = NEAT = 6174

      Like

      • Finbarr Morley's avatar

        Finbarr Morley 9:43 am on 30 November 2020 Permalink | Reply

        Now repeat for Assumption (B) N < T, to see if it is valid.

        Equations are similar but different:

        This time the N in the 100’s column must borrow 1 from the A in the 1000’s column.

           4      3      2      1
        
          A-1   10+N    T-1   10+E             
        
        -  E      T      N      A
        
        =  N      E      A      T
        

        The equations for each of the 4 columns are:

        (1) 10+E-A=T (exactly the same as before)

        (2) T-1-N=A

        (3) 10+N-T=E

        (4) A-1-E=N

        Rearrange to:

        (1) A-E = 10-T

        (4) A-E = N+1

        (2) T-N = A+1

        (3) T-N = 10-E

        (1-4) 10-T=N+1 (because they both equal A-E) rearrange to T+N=9

        (2-3) A+1=10-E (because they both equal T-N) rearrange to A+E=9

        From here either solve by substituting numbers, or with algebra.

        SUBSTITUTION:

        A+E=9 and A>E.

        So A = 5,6,7 or 8 or 9

        A   E     A-E=N+1     N+T=9      T-N=10-E
        5   4         0         9           no           
        6   3         2         7           no
        7   2         4         5           no
        8   1         6         3           N<T
        9   0         8         1           N<T
        

        There are no valid answers with N<T.

        ALGEBRA:

                  T+N = 9               A+E = 9
              2) +T-N = A+1        1) +(A-E = 10-T)	
                 2T   = 10+A           2A   = 19-T
             x2) 4T   = 20+2A
        
        ∴   4T = 20+19-T   
            3T = 39
             T = 13
        

        Since this is not 0 to 9, the assumption (B) N<T is invalid.

        Assumption A is the only valid solution.

        There can be only one – William Shakespeare

        Like

        • Jim Randell's avatar

          Jim Randell 11:55 am on 30 November 2020 Permalink | Reply

          @Finbarr: Thanks for your comments. (And I hope I’ve tidied them up OK).

          Although I think we can take a bit of a shortcut:

          If we start with:

          WXYZ + ZYXW = NEAT
          where: W > X > Y > Z

          Then considering the columns of the sum we have:

          (units) T = 10 + ZW
          (10s) A = 9 + YX
          (100s) E = X − 1 − Y
          (1000s) N = WZ

          Then, (units) + (1000s) and (10s) + (100s) give:

          N + T = 10
          A + E = 8

          which could be used to shorten your solution.

          Like

          • Jim Randell's avatar

            Jim Randell 1:53 pm on 30 November 2020 Permalink | Reply

            Or we can extend it into a full solution by case analysis:

            Firstly, we see Z ≠ 0, as ZYXW is a 4-digit number.

            And W > 5, as 5 + 4 + 3 + 2 < 18.

            So considering possible values for W:

            [W = 6]

            There is only one possible descending sequence that sums to 18, i.e. (6, 5, 4, 3)

            So the sum is:

            6543 − 3456 = 3087

            which doesn’t work.

            [W = 7]

            W must be paired with 3 (to make 10) or 1 (to make 8).

            If it is paired with 3, then the other pair is 2+6. So the sum is:

            7632 − 2367 = 5265

            which doesn’t work.

            If it is paired with 1, then the other pair is either 2+8 or 4+6, which gives us the following sums:

            8721 − 1278 = 7443
            7641 − 1467 = 6174

            The first doesn’t work, but the second gives a viable solution: ANTEETNA = NEAT.

            And the following cases show uniqueness:

            [W = 8]

            W must be paired with 2, and the other pair is either 1+7 or 3+5, and we have already looked at (8, 7, 2, 1)

            8532 − 2358 = 6174

            This doesn’t work.

            [W = 9]

            W must be paired with 1, and the other pair either 2+6 or 3+5:

            9621 − 1269 = 8352
            9531 − 1359 = 8173

            Neither of these work.

            Like

    • Finbarr Morley's avatar

      Finbarr Morley 3:14 pm on 30 November 2020 Permalink | Reply

      I’m curious about the properties of these Cryptarithmetics.
      How is it that ANTE-ETNA=NEAT has 1 unique solution out of 10,000.
      But ANTE-ETNA=NAET has none?

      A Google search reveals some discussion:
      http://cryptarithms.awardspace.us/primer.html
      https://www.codeproject.com/Articles/176768/Cryptarithmetic
      https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1949-8594.1987.tb11711.x

      The latter seems to be heading for a discussion on the rules, but it’s ony the first page of an extract.
      Any thoughts from the collective on what the natural rules are?

      Like

  • Unknown's avatar

    Jim Randell 8:55 am on 27 September 2020 Permalink | Reply
    Tags:   

    Teaser 1892: Puzzling present 

    From The Sunday Times, 20th December 1998 [link]

    I have received an astonishing letter from a fellow puzzle-setter. He writes:

    “I am sending you a puzzle in the form of a parcel consisting of an opaque box containing some identical marbles, each weighing a whole number of grams, greater than one gram. The box itself is virtually weightless. If I told you the total weight of the marbles, you could work out how many there are.”

    He goes on:

    “To enable you to work out the total weight of the marbles I am also sending you a balance and a set of equal weights, each weighing a whole number of grams, whose total weight is two kilograms. Bearing in mind what I told you above, these weights will enable you to calculate the total weight of the marbles and hence how many marbles there are.”

    I thought that he was sending me these items under seperate cover, but I had forgotten how mean he is. He went on:

    “I realise that I can save on the postage. If I told you the weight of each weight you would still be able to work out the number of marbles. Therefore I shall not be sending you anything.”

    How many marbles were there?

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

    [teaser1892]

     
    • Jim Randell's avatar

      Jim Randell 8:55 am on 27 September 2020 Permalink | Reply

      (See also: Enigma 1606).

      There must be a prime number of balls, each weighing the same prime number of grams.

      This Python program uses the same approach I took with Enigma 1606 (indeed if given a command line argument of 4000 (= the 4kg total of the weights) it can be used to solve that puzzle too).

      It runs in 48ms.

      Run: [ @repl.it ]

      from enigma import Primes, isqrt, divisor, group, arg, printf
      
      # total weights
      T = arg(2000, 0, int)
      
      # find prime squares up to T
      squares = list(p * p for p in Primes(isqrt(T)))
      
      printf("[T={T}, squares={squares}]")
      
      # each weight is a divisor of T
      for w in divisor(T):
      
        # make a "by" function for weight w
        def by(p):
          (k, r) = divmod(p, w)
          return (k if r > 0 else -k)
      
        # group the squares into categories by the number of weights required
        d = group(squares, by=by)
        # look for categories with only one weight in
        ss = list(vs[0] for vs in d.values() if len(vs) == 1)
        # there should be only one
        if len(ss) != 1: continue
      
        # output solution
        p = ss[0]
        printf("{n}x {w}g weights", n=T // w)
        printf("-> {p}g falls into a {w}g category by itself")
        printf("-> {p}g = {r} balls @ {r}g each", r=isqrt(p))
        printf()
      

      Solution: There are 37 marbles.

      There are 4× 500g weights (2000g in total), and when these are used to weigh the box we must find that it weighs between 1000g (2 weights) and 1500g (3 weights). The only prime square between these weights is 37² = 1369, so there must be 37 balls each weighing 37g.

      Telling us that “if I told you the weight of the weights you would be able to work out the number of marbles” is enough for us to work out the number of marbles, without needing to tell us the actual weight. (As there is only one set of weights that gives a category with a single prime square in). And we can also deduce the set of weights.

      However, in a variation on the puzzle where the set of weights total 2200g, we find there are two possible sets of weights that give a category with a single square in (8× 275g and 4× 550g), but in both cases it is still 1369 that falls into a category by itself, so we can determine the number (and weight) of the marbles, but not the weight (and number) of the weights. So the answer to the puzzle would still be the same.

      Likewise, if the set of weights total 3468g, there are 3 possible sets of weights that give a category with a single square in (6× 578g, 4× 867g and 3× 1156g). However in each of these cases 2809 falls into a category by itself, and so the solution would be 53 marbles each weighing 53g. Which is the answer to Enigma 1606.

      Like

    • Frits's avatar

      Frits 11:36 pm on 27 September 2020 Permalink | Reply

       
      from enigma import SubstitutedExpression
      from collections import defaultdict
      
      # list of prime numbers
      P = {2, 3, 5, 7}
      P |= {x for x in range(11, 45, 2) if all(x % p for p in P)}
      # list of squared prime numbers
      P2 = [p * p for p in P]
      
      # check if only one total lies uniquely between k weights and k+1 weights
      def uniquerange(weight):
        group = defaultdict(list)
        for total in P2:
          # total lies between k weights and k+1 weights
          k = total // weight
          if group[k]:
            group[k].append(total)
          else:
            group[k] = [total]
      
        singles = [group[x][0] for x in group if len(group[x]) == 1]
        if len(singles) == 1:
          return singles[0]
          
        return 0  
            
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # ABCD is a weight and a divisor of 2000
          "2000 % ABCD == 0",
          "uniquerange(ABCD) != 0",
        ],
        answer="(ABCD, uniquerange(ABCD))",
        env=dict(uniquerange=uniquerange), # external functions
        verbose=0,
        d2i="",        # allow number representations to start with 0
        distinct="",   # allow variables with same values
      )
       
      # Print answers
      for (_, ans) in p.solve():
        print(f"{int(2000/ans[0])} x {ans[0]}g weigths")
        print(f"-> {ans[1]}g falls into a {ans[0]}g category by itself")
        print(f"-> {ans[1]}g = {ans[1] ** 0.5} balls @ {ans[1] ** 0.5}g each")
      

      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