Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 8:46 am on 9 November 2021 Permalink | Reply
    Tags:   

    Teaser 2834: Degrees of freedom 

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

    I bought an odd thermometer from an old curiosity shop. On its linear scale the freezing point and boiling point of water were higher than they are on the centigrade scale. In fact the freezing point was a prime number and, higher up the scale, the boiling point was a perfect square. There was only one number on the scale where it actually agreed with the corresponding centigrade temperature. That number was the negative of an odd prime (and not the same prime as the one mentioned earlier).

    On this new scale, what are the freezing and boiling points of water?

    There are now 2500 puzzles available between the Enigmatic Code and S2T2 sites. See [link].

    [teaser2834]

     
    • Jim Randell's avatar

      Jim Randell 8:46 am on 9 November 2021 Permalink | Reply

      A straightforward program finds the answer. The following Python program runs in 48ms.

      Run: [ @replit ]

      from enigma import (primes, irange, inf, div, printf)
      
      def solve():
      
        # consider possible squares (for the boiling point)
        for n in irange(11, inf):
          b = n * n
      
          # consider possible primes (for the freezing point)
          for f in primes.between(2, b - 100):
      
            # compute when the temperature scales coincide
            d = b - f - 100
            if not (d > 0): continue
            v = div(100 * f, d)
            if v is None or not (v > 2 and v != f and v in primes): continue
      
            yield (n, b, f, v)
      
      # find the first solution
      for (n, b, f, v) in solve():
        printf("n={n} b={b} f={f} v={v}")
        break
      

      Solution: The freezing point is at 41. The boiling point is at 961.

      The scales coincide at −5.

      To convert from Celsius you can use:

      y = (46/5)x + 41

      However, if the scales were allowed to coincide at −2, there would be further solutions:

      f = 31, b = 1681 (= 41²), y = (33/2)x + 31, coincide at −2
      f = 71, b = 3721 (= 61²), y = (73/2)x + 71, coincide at −2


      A bit of analysis gives a shorter program:

      The scales coincide at −v where:

      v = 100f / (b − f − 100)

      And v is an odd prime number different from f.

      The prime factorisation of the numerator is: 2 × 2 × 5 × 5 × f.

      So we immediately see:

      v = 5
      b = 21f + 100

      And a short program provides the answer:

      from enigma import (primes, inf, is_square, printf)
       
      for f in primes.irange(2, inf):
        b = 21 * f + 100
        if is_square(b):
          printf("f={f} b={b}")
          break
      

      Alternatively, we can further analyse the expression for b, which is a square, say n²:

      n² = 21f + 100
      n² − 100 = 21f
      (n − 10)(n + 10) = 3 × 7 × f

      Considering the factor that does not contain f:

      n − 10 = 1 ⇒ n = 11, f = 1 [not prime]
      n + 10 = 1 ⇒ n = −9, f = −19/21 [non-integral]
      n − 10 = 3 ⇒ n = 13, f = 23/7 [non-integral]
      n + 10 = 3 ⇒ n = −7, f = −17/7 [non-integral]
      n − 10 = 7 ⇒ n = 17, f = 9 [not prime]
      n + 10 = 7 ⇒ n = −3, f = −13/3 [non-integral]
      n − 10 = 21 ⇒ n = 31, f = 41 [*** SOLUTION ***]
      n + 10 = 21 ⇒ n = 11, f = 1 [not prime]

      Like

  • Unknown's avatar

    Jim Randell 9:08 am on 7 November 2021 Permalink | Reply
    Tags: by: H. G. ApSimon   

    A Brain-Teaser: [Boxes and ladders] 

    From The Sunday Times, 12th April 1957 [link]

    A schoolmaster set a problem of the following type to each of four pupils:

    “A ladder of length ___ rests squarely, and more steeply than, 45 degrees, against a (vertical) wall, with its foot on the (horizontal) ground distant ___ from the base of the wall. A cubical box fits flush into the angle of the wall and the ground, and just touches the ladder. What is the length of side of the box?”

    and he filled in the gaps in the problem as set out with Integral numbers of inches such that the answer would also be an integral number of inches.

    To each pupil, however, he gave different values for the length of ladder (all less than 150 ft.) and for the distance of its foot from the base of the wall. The answers submitted to him by the four pupils were all the same, and all were correct.

    What were the four different pairs of values he gave to his pupils, and what was the common answer implied by them?

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of £5 was offered.

    This puzzle was originally published with no title.

    [teaser-1957-04-12] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 7 November 2021 Permalink | Reply

      The distance along the ground, x, the height up the wall, y, and the length of the ladder, z, form a Pythagorean Triple (x, y, z).

      And the side of the cube, k, is then given by:

      k = xy / (x + y)

      The following Python program runs in 48ms.

      Run: [ @replit ]

      from enigma import (pythagorean_triples, div, group, unpack, printf)
      
      # generate (x, y, z, k) solutions
      def generate(Z):
        for (x, y, z) in pythagorean_triples(Z):
          k = div(x * y, x + y)
          if k is not None:
            yield (x, y, z, k)
      
      # collect (x, y, z, k) solutions by the value of k
      # z < 150 ft = 1800 in
      d = group(generate(1799), by=unpack(lambda x, y, z, k: k))
      
      # look for k values with 4 (or more) solutions
      for (k, vs) in d.items():
        if len(vs) < 4: continue
        printf("k={k} [{n} triangles]", n=len(vs))
        for (x, y, z, k) in vs:
          printf("  x={x} y={y} z={z}")
        printf()
      

      Solution: The (length, distance) pairs given to the students were (in inches): (1189, 820), (1225, 735), (1547, 595), (1739, 564). The common answer was 420 inches.

      Like

    • GeoffR's avatar

      GeoffR 11:01 am on 10 November 2021 Permalink | Reply

      Using the pythagorean_triples function from enigma.py, this function uses x < y < z, so there is no need to check the ladder angle is greater than 45 degrees, as y is always greater than x.

      Let cube side = c and ladder angle with ground = A
      Let y = p + c and x = c + q

      Tan A = p / c = c / q, so (y – c) / c = c /(x – c)
      This simplifies to c = (y * x) / (y + x)

      
      from enigma import pythagorean_triples
      from collections import defaultdict
      
      BT1 = defaultdict(list)
      
      for x, y, z in pythagorean_triples(1799):
          # find integer value of cube side
          q, r = divmod(x * y, x + y)
          if q > 0 and r == 0:
              BT1[q] += [(x, y, z)]
      
      for k, v  in BT1.items():
          # Looking for four sets of values for (x, y, z)
          if len(v) > 3:
              print(f"Cube side = {k}")
              print(f"Triangles: {v}")
      

      Like

    • John Crabtree's avatar

      John Crabtree 3:57 pm on 11 November 2021 Permalink | Reply

      I think that this brain teaser is very hard to tackle manually. Hugh ApSimon presents a parameter method for generating the primitive solutions in his book “Mathematical Byways in Ayling, Beeling and Ceiling” which was published in 1984. See Chapter 2 on pages 7-12. Chapter 1 on pages 3-6 is a lead in.

      Like

  • Unknown's avatar

    Jim Randell 4:34 pm on 5 November 2021 Permalink | Reply
    Tags:   

    Teaser 3085: Lucky progression 

    From The Sunday Times, 7th November 2021 [link] [link]

    I wrote down a number which happened to be a multiple of my lucky number. Then I multiplied the written number by my lucky number to give a second number, which I also wrote down. Then I multiplied the second number by my lucky number again to give a third, which I also wrote down. Overall, the three numbers written down used each of the digits 0 to 9 exactly once.

    What were the three numbers?

    [teaser3085]

     
    • Jim Randell's avatar

      Jim Randell 4:44 pm on 5 November 2021 Permalink | Reply

      The following Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import irange, inf, nsplit, flatten, printf
      
      # consider lucky numbers, n
      for n in irange(2, inf):
        # and the initial multiple
        for k in irange(2, inf):
          # the three numbers
          ns = list(k * n ** x for x in (1, 2, 3))
          # collect the digits in the numbers
          ds = flatten(nsplit(n) for n in ns)
          if len(ds) > 10: break
          if len(ds) == 10 and len(set(ds)) == 10:
            printf("ns = {ns} [n = {n}; k = {k}]")
        if k == 2: break
      

      Solution: The three numbers are: 306, 918, 2754.

      The lucky number is 3.

      So we have:

      102 × 3 = 306
      306 × 3 = 918
      918 × 3 = 2754

      Like

    • GeoffR's avatar

      GeoffR 5:34 pm on 5 November 2021 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 2..10: LN; % my lucky number
      
      var 1..9: A; var 0..9: B; var 0..9: C;
      var 1..9: D; var 0..9: E; var 0..9: F;
      var 1..9: G; var 0..9: H; var 0..9: I; var 0..9: J;
      
      % the three numbers written down have different digits
      constraint all_different ([A, B, C, D, E, F, G, H, I, J]);
      
      % My three numbers
      var 100..999: ABC = 100*A + 10*B + C;
      var 100..999: DEF = 100*D + 10*E + F;
      var 1000..9999: GHIJ = 1000*G + 100*H + 10*I + J;
      
      % First number is a multiple of my lucky number
      constraint ABC mod LN == 0;
      
      % Second and third lucky numbers
      constraint ABC * LN == DEF /\ DEF * LN == GHIJ;
      
      solve satisfy;
      
      output ["My three numbers are " ++show(ABC) ++ ", " ++ show(DEF) ++ 
      " and " ++ show(GHIJ)
      ++ "\n" ++ "Lucky number is " ++ show(LN) ];
      
      
      

      Like

    • NigelR's avatar

      NigelR 5:51 pm on 5 November 2021 Permalink | Reply

      First number must be less than 1000 or the three numbers would have more than 10 digits between them. Code below runs in 39ms:

      for lucky in range(1,100):
          for first in range(1,1000):
              if first%lucky!=0:
                  continue
              second = first*lucky
              third = second*lucky
              res=str(first)+str(second)+str(third)
              if len(set(res)) == 10 and len(res) == 10:
                  print (‘first: ‘, first,’  second: ‘, second,’  third:’, third,’  lucky:’, lucky)
                  exit()
      

      Like

    • GeoffR's avatar

      GeoffR 8:52 am on 6 November 2021 Permalink | Reply

      # Assume a digit split of 3:3:4 for 10 digits for three numbers.
      # The multiplier is less than 10 between two increasing 3-digit numbers
      # The three numbers are ABC, DEF and GHIJ and the lucky number is LN
      
      from enigma import nsplit, all_different
      
      from enigma import Timer
      timer = Timer('ST3085')
      timer.start()
      
      for LN in range(2, 10):
        for ABC in range(102, 987):
          if ABC % LN != 0:
            continue
          A, B, C = nsplit(ABC)
          if not all_different(A, B, C):
            continue
          DEF = ABC * LN
          if DEF < 100 or DEF > 999:
            continue
          D, E, F = nsplit(DEF)
          if not all_different(A, B, C, D, E, F):
            continue
          GHIJ = DEF * LN
          if GHIJ < 1000 or GHIJ > 9999:
            continue
          G, H, I, J = nsplit(GHIJ)
          if not all_different(A, B, C, D, E, F, G, H, I, J):
            continue
          print(f"My three numbers are {ABC}, {DEF} and {GHIJ}")
          timer.stop()
          timer.report()  # 4.98ms (I9 processor)
          
      
      
      
      

      Like

      • Frits's avatar

        Frits 10:33 am on 9 November 2021 Permalink | Reply

        @GeoffR, a digit split of 2:3:5 is also possible.

        Like

    • GeoffR's avatar

      GeoffR 11:09 am on 9 November 2021 Permalink | Reply

      @Frits:
      I decided to try a 3:3:4 digit split first as this seemed a more likely candidate for a solution. As this digit split gave a single answer, I did not try a 2:3:5 digit split.

      Like

  • Unknown's avatar

    Jim Randell 11:15 am on 4 November 2021 Permalink | Reply
    Tags:   

    Teaser 2832: A New Year reminiscence 

    From The Sunday Times, 1st January 2017 [link] [link]

    Whilst filing away last year’s diary this morning I came across an old diary from my teenage years. In it I can see that in one particular month I went to four parties, three of them being on Saturdays and the other on a Sunday. I wrote down the four dates of the parties in words (in the format “January first” etc.) and found that each of the dates used a different prime number of letters.

    What were the four dates that I wrote down?

    [teaser2832]

     
    • Jim Randell's avatar

      Jim Randell 11:16 am on 4 November 2021 Permalink | Reply

      This Python program finds the first (most recent) year and month satisfying the conditions.

      It runs in 55ms.

      Run: [ @replit ]

      from datetime import date
      from enigma import (irange, int2words, catch, is_prime, subsets, join, sprintf as f, cached, printf)
      
      # map month numbers to English names
      months = dict(enumerate([
          'January', 'February', 'March', 'April', 'May', 'June',
          'July', 'August', 'September', 'October', 'November', 'December',
        ], start=1)
      )
      
      # ordinals that aren't cardinal + "th", or cardinal - "y" + "ieth"
      _ordinal = {
        1: 'first',
        2: 'second',
        3: 'third',
        5: 'fifth',
        8: 'eighth',
        9: 'ninth',
        12: 'twelfth',
      }
      
      # return the ordinal of a number (0 < n < 100)
      @cached
      def ordinal(n):
        if n in _ordinal:
          return _ordinal[n]
        if n < 20:
          return int2words(n) + 'th'
        (t, r) = divmod(n, 10)
        if r == 0:
          return int2words(n)[:-1] + 'ieth'
        else:
          return int2words(n - r) + ' ' + ordinal(r)
      
      # format dates
      fmt = lambda x: join((f('"{d}" {n}') for (d, n) in x), sep=", ", enc="[]")
      
      # find solutions (going back in time)
      def solve(month=12, year=2016):
      
        while year > 0:
          # find saturdays and sundays in the specified month
          sats = list()
          suns = list()
          for day in irange(1, 31):
            d = catch(date, year, month, day)
            if d is None: break
            wd = d.weekday()
            if not (wd == 5 or wd == 6): continue
            s = months[month] + " " + ordinal(day)
            n = sum(1 for x in s if x.isalpha())
            if is_prime(n):
              (sats if wd == 5 else suns).append((s, n))
      
          # choose three saturdays
          for sat in subsets(sats, size=3):
            ps = set(p for (s, p) in sat)
            if len(ps) != 3: continue
            sun = list((s, p) for (s, p) in suns if p not in ps)
            if sun:
              yield (month, year, sat, sun)
      
          # move back a month
          month -= 1
          if month == 0: (month, year) = (12, year - 1)
      
      # find the first solution
      for (month, year, sat, sun) in solve():
        printf("month={month}, year={year}, sat={sat}, sun={sun}", sat=fmt(sat), sun=fmt(sun))
        break
      

      Solution: The four dates are: August fifth, August twelfth, August twenty sixth, August twenty seventh.

      With 11, 13, 17, and 19 letters respectively.

      The first viable year (counting back from 2016) is 2006.

      If we suppose the setter was 16 when attending the parties that gives an age in 2016 of 26.

      But there are further solutions going back in time (but they all give the same answer to the puzzle):

      year = 2000; current age = 32
      year = 1995; current age = 37
      year = 1989; current age = 43
      year = 1978; current age = 54
      year = 1972; current age = 60
      year = 1967; current age = 65
      year = 1961; current age = 71
      year = 1950; current age = 82
      year = 1944; current age = 88
      year = 1939; current age = 93
      year = 1933; current age = 99
      year = 1922; current age = 110
      year = 1916; current age = 116
      year = 1911; current age = 121
      year = 1905; current age = 127

      I won’t speculate on the probable age of the setter.

      Like

    • Frits's avatar

      Frits 2:31 pm on 4 November 2021 Permalink | Reply

      Some constants have been taken from Brian Gladman’s site.
      I didn’t bother to put in code for leap years (28/29 in February, the month always has to be August anyway).

        
      import datetime
      
      MONTHS = ( "January", "February", "March", "April", "May", 
                 "June", "July", "August", "September", "October", 
                 "November", "December" )
       
      DAYS = ( "first", "second", "third", "fourth", "fifth", "sixth", 
               "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", 
               "thirteenth", "fouteenth", "fifteenth", "sixteenth", 
               "seventeenth", "eighteenth","nineteenth", "twentieth", 
               "twentyfirst", "twentysecond", "twentythird","twentyfourth",
               "twentyfifth", "twentysixth", "twentyseventh", "twentyeighth", 
               "twentyninth", "thirtieth", "thirtyfirst" )
               
      WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", 
                  "Saturday", "Sunday"]
               
      P = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
       
      # days in month (29 for February)
      DIM = ( 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 )
      
      minm = min(len(m) for m in MONTHS)
      maxm = max(len(m) for m in MONTHS)
      
      mind = min(len(d) for d in DAYS)
      maxd = max(len(d) for d in DAYS)
      
      primes = [x for x in range(minm + mind, maxm + maxd + 1) if x in P]
      
      if len(primes) < 4: 
        print("no solution")
        exit()
      
      # skip months that are too short to make the 4th prime
      ms = [m for m in MONTHS if len(m) + maxd >= primes[3]]
      
      # skip months that are too long to make the 1st prime
      ms = [m for m in ms if len(m) + mind <= primes[-4]]
      
      # pick one value from each entry of a <k>-DIMensional list <ns>
      def pickOneFromEach(k, ns, s=[]):
        if k == 0:
           s = sorted(s)
           # first entry must be either a day later than the rest or same week day
           if tuple(sorted((s[0] - x) % 7 for x in s[1:])) in \
                   {(1, 1, 1), (0, 0, 6)}:
             yield s
        else:
          for n in ns[k-1]:
            # day numbers must be equal or one apart
            if not s or all((n - x) % 7 in {0, 1, 6} for x in s):
              yield from pickOneFromEach(k - 1, ns, s + [n])
      
      # process all viable months
      for m in ms:
        lenm = len(m)
        mno = MONTHS.index(m) + 1
        
        # make list of different month+day lengths
        lst = [[] for _ in range(4)]  
        for i, d in enumerate(DAYS[:DIM[mno] - 1], start=1):
          totlen = lenm + len(d)
          if totlen not in primes: continue
          lst[primes.index(totlen)].append(i)
        
        # check whether we can find a combination of <lst> entries with
        # three same week days and one on the following day
        cands = list(pickOneFromEach(4, lst))
        for c in cands:
          for y in range(2016, 1900, -1):    
            wdays = [WEEKDAYS[datetime.date(year=y, month=mno, day=d).weekday()] 
                     for d in c]
      
            if sorted(wdays) == ['Saturday', 'Saturday', 'Saturday', 'Sunday']:
              print(f"{y}, {m} {c}")
              break # stop at first solution
      

      Like

  • Unknown's avatar

    Jim Randell 5:45 pm on 2 November 2021 Permalink | Reply
    Tags: ,   

    Brainteaser 1816: Polls apart 

    From The Sunday Times, 6th July 1997 [link]

    A TV station has commissioned a survey of a small sample of its viewers. One quarter said they watched boxing, one third said they watched tennis, and one half said they watched football. All watched at least one sport. When analysed, the results showed that the number of those questioned who watched just one of the sports equalled the square of the number watching all three. The TV people were unconvinced and asked for a second poll with the number of people questioned increased by 50%. Surprisingly, all that was said above about the first poll was still true for the second.

    How many people were questioned in the first poll?

    [teaser1816]

     
    • Jim Randell's avatar

      Jim Randell 5:46 pm on 2 November 2021 Permalink | Reply

      See also: Brain-Teaser 25.

      If the total number of participants is N, and the 7 regions of the Venn Diagram are: B, T, F, BT, BF, TF, BTF, then we have:

      N = B + T + F + BT + BF + TF + BTF
      N/4 = B + BT + BF + BTF
      N/3 = T + BT + TF + BTF
      N/2 = F + BF + TF + BTF
      B + T + F = BTF²

      Summing the middle three, and then replacing (B + T + F) using the last equation:

      (13/12)N = (B + T + F) + 2(BT + BF + TF) + 3BTF
      (13/12)N = BTF² + 2(BT + BF + TF) + 3BTF
      ⇒ 2(BT + BF + TF) = (13/12)N − 3BTF − BTF²

      And from the first equation:

      (BT + BF + TF) = N − (B + T + F) − BTF
      ⇒ (BT + BF + TF) = N − BTF − BTF²

      equating these:

      2N − 2BTF − 2BTF² = (13/12)N − 3BTF − BTF²
      (11/12)N = BTF² − BTF
      ⇒ N = (12/11)BTF(BTF − 1)

      So for a given value of BTF we can calculate the corresponding value for N.

      This Python program considers increasing (BTF, N) values, and looks for an example viable arrangement by calculating values for the remaining areas of the Venn diagram. When it finds an N value that is 50% more than a previously determined value it stops.

      It runs in 54ms.

      Run: [ @replit ]

      from enigma import (irange, inf, div, decompose, sq, Matrix, as_int, printf)
      
      # generate viable configurations
      def solve():
        # consider values for the number that watch all 3 sports
        for BTF in irange(2, inf):
          # calculate N
          N = div(12 * BTF * (BTF - 1), 11)
          if N is None: continue
          BTF2 = sq(BTF)
          if BTF + BTF2 > N: continue
          
          # choose values for BT, BF, TF
          for (BT, BF, TF) in decompose(N - BTF - BTF2, 3, increasing=0, sep=0, min_v=0):
      
            # determine the remaining variables
            eqs = [
              # B  T  F BT BF TF BTF = k
              ((1, 1, 1, 1, 1, 1, 1), N), # B + T + F + BT + BF + TF + BTF = N
              ((4, 0, 0, 4, 4, 0, 4), N), # B + BT + BF + BTF = N/4
              ((0, 3, 0, 3, 0, 3, 3), N), # T + BT + TF + BTF = N/3
              ((0, 0, 2, 0, 2, 2, 2), N), # F + BF + TF + BFT = N/2
              ((1, 1, 1, 0, 0, 0, 0), BTF2), # B + T + F = BTF^2
              ((0, 0, 0, 0, 0, 0, 1), BTF), # BTF
              ((0, 0, 0, 1, 0, 0, 0), BT), # BT
              ((0, 0, 0, 0, 1, 0, 0), BF), # BF
              ((0, 0, 0, 0, 0, 1, 0), TF), # TF
            ]
      
            try:
              (B, T, F, BT, BF, TF, BTF) = Matrix.linear(eqs, valid=(lambda x: as_int(x, "0+")))
            except ValueError:
              continue
      
            yield (N, B, T, F, BT, BF, TF, BTF)
            break # only need one example for any N
      
      # record results by N
      seen = set()
      for (N, B, T, F, BT, BF, TF, BTF) in solve():
        N_ = 2 * N // 3
        printf("N={N}; B={B} T={T} F={F} BT={BT} BF={BF} TF={TF} BTF={BTF} [(2/3)N={N_}]")
        if N_ in seen: break
        seen.add(N)
      

      Solution: The number of participants in the first poll was 2160.

      And there were 3240 in the second poll.

      Here are example arrangements:

      N=2160; B=495 T=585 F= 945 BT=0 BF=0 TF= 90 BTF=45
      N=3240; B=755 T=865 F=1405 BT=0 BF=0 TF=160 BTF=55

      (There are many possibilities for (BT, BF, TF), but for each (N, BTF) pair they sum to the same value).


      There are many possible arrangements that satisfy the conditions, but there are fewer that have an N value that is 1.5× a smaller N value.

      The program stops when it finds the first solution, but there are further solutions, although they don’t really qualify as “a small sample”:

      N=211680; B=52479 T=53361 F=88641 BT=0 BF=0 TF=16758 BTF=441
      N=317520; B=78840 T=79920 F=132840 BT=0 BF=0 TF=25380 BTF=540

      N=2032553520; B=508095215 T=508181545 F=846940465 BT=0 BF=0 TF=169293130 BTF=43165
      N=3048830280; B=762154704 T=762260436 F=1270398816 BT=0 BF=0 TF=253963458 BTF=52866

      N=199169502480; B=49791948335 T=49792802905 F=82987719985 BT=0 BF=0 TF=16596603970 BTF=427285
      N=298754253720; B=74688040115 T=74689086745 F=124481462365 BT=0 BF=0 TF=24895141180 BTF=523315

      Like

    • Hugh Casement's avatar

      Hugh Casement 6:30 pm on 2 November 2021 Permalink | Reply

      BTF must be a multiple of 11, or 1 more than that, for N to be an integer.

      For any BTF, N pair there are many possible values for BT, BF, and TF.
      But the sum of those three is determined, as shown in the third grey block of Jim’s analysis,
      and it also equals N/12 – 2BTF.

      It turns out to be negative is BTF is too small.

      Like

    • GeoffR's avatar

      GeoffR 8:02 pm on 2 November 2021 Permalink | Reply

      It is easy just to use Jim’s five basic equations in a MiniZinc solution as constraints, although fixing the ranges of variables was not easy to pre-determine.

      The programme took under 2 sec to find the answer, but nearly 2 minutes to exhaust all the subsequent searching with the Chuffed Solver.

      % A Solution in MiniZinc
      include "globals.mzn";
      % re-using Jim's basic equations
      % using upper case variables for the first survey
      % ..and lower case variables for the second survey
      
      var 1..1000:B; var 1..1000:b;
      var 1..1000:T; var 1..1000:t;
      var 1..2000:F; var 1..2000:f;
      var 0..500:BT; var 0..500:bt;
      var 0..500:BF; var 0..500:bf;
      var 0..1000:BTF; var 0..1000:btf;
      var 0..500:TF; var 0..500:tf;
      var 3..5000:N; var 3..5000:n;
      
      % First Survey - main equations
      constraint N == B + T + F + BT + BF + TF + BTF;
      constraint N == 4 * (B + BT + BF + BTF);
      constraint N == 3 * (T + BT + TF + BTF);
      constraint N == 2 * (F + BF + TF + BTF);
      constraint B + T + F == BTF * BTF;
      
      % Second Survey
      % For second survey,  people questioned increased by 50%
      constraint 2 * n == 3 * N;
      
      % Second survey - main equations
      constraint n == b + t + f + bt + bf + tf + btf;
      constraint n == 4 * (b + bt + bf + btf);
      constraint n == 3 * (t + bt + tf + btf);
      constraint n == 2 * (f + bf + tf + btf);
      constraint b + t + f == btf * btf;
      
      solve satisfy;
      
      output [ "People questioned in the first poll = " ++ show(N) ];
      
      % People questioned in the first poll = 2160
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:01 am on 2 November 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 914: Advancing numeracy 

    From The Sunday Times, 27th January 1980 [link]

    My mathematics class is much more reliable than formerly. I can now be certain that each pupil will always follow a completely true statement by a false one, and vice versa.

    It is clear that some difficulties still arise, but when I asked three of them to select one five-figure number between them, and to make two statements each about it, it was possible to deduce the number they had in mind.

    They used a, b, c, d and e to indicate the positions of the successive digits, which were not necessarily all different.

    Anne said:
    1. The sum of the number and 969 Is divisible by 714.
    2. The sum of c and e is one third of the sum of the five
    digits.

    Bill said:
    1. The sum of the number and 798 is divisible by 693.
    2. The sum of the number and 987 is divisible by 658.

    Carol said:
    1. The sum of b and d equals three times the first digit.
    2. The sum of the number and 988 is divisible by 741.

    What was the number?

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

    [teaser914]

     
    • Jim Randell's avatar

      Jim Randell 9:02 am on 2 November 2021 Permalink | Reply

      This Python program runs in 60ms.

      Run: [ @replit ]

      from enigma import irange, nsplit, printf
      
      # consider the 5-digit number
      for n in irange(10000, 99999):
      
        # consider Bill's statements (can't both have the same truth value)
        if ((n + 798) % 693 == 0) == ((n + 987) % 658 == 0): continue
      
        # and Anne's
        (a, b, c, d, e) = nsplit(n)
        if ((n + 969) % 714 == 0) == (3 * (c + e) == a + b + c + d + e): continue
      
        # and Carol's
        if (b + d == 3 * a) == ((n + 988) % 741 == 0): continue
      
        printf("{n}")
      

      Solution: The number was: 32571.

      Anne’s statements are: False, True.

      Bill’s statements are: False, True.

      Carol’s statements are: True, False.

      Liked by 1 person

      • Jim Randell's avatar

        Jim Randell 10:27 am on 3 November 2021 Permalink | Reply

        Or, checking fewer candidates:

        from enigma import irange, nsplit, printf
        
        # exactly one of Bill's statements must be true
        b1 = set(irange(10290, 99999, step=693))
        b2 = set(irange(10199, 99999, step=658))
        for n in b1.symmetric_difference(b2):
          (a, b, c, d, e) = nsplit(n)
          if (
            # Anne
            ((n + 969) % 714 == 0) != (3 * (c + e) == a + b + c + d + e) and
            # Carol
            (b + d == 3 * a) != ((n + 988) % 741 == 0)
          ):
            printf("{n}")
        

        Like

    • GeoffR's avatar

      GeoffR 8:52 am on 13 January 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % the five digits - assuming no digit is zero
      var 1..9:a; var 1..9:b; var 1..9:c; var 1..9:d;  var 1..9:e;
      
      % the five digit number
      var 10000..99999:num = 10000*a + 1000*b + 100*c + 10*d + e;
      
      % Anne - one of two statements is true
      constraint ((num + 969) mod 714 == 0) 
      \/ (3 *(c + e) = (a  + b + c + d + e));
      
      % Bill - one of two statements is true
      constraint ((num + 798) mod 693 == 0) 
      \/ ((num + 987) mod 658 == 0);
      
      % Carol - one of two statements is true
      constraint ((b + d) == 3 * a) 
      \/ ((num  + 988) mod 741 == 0);
      
      solve satisfy;
      output ["Number is " ++  show(num) ];
      
      % Number is 32571
      % ----------
      % ==========
      
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 12:51 pm on 13 January 2022 Permalink | Reply

        @GeoffR: This model checks that at least one of the two statements for A, B, C is true. Rather than exactly one.

        But changing \/ to xor (or !=) will do the job.

        Like

    • GeoffR's avatar

      GeoffR 2:31 pm on 13 January 2022 Permalink | Reply

      @ Jim: Ok, I see what you mean. This programme confirms it gets the same answer.

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      % the five digits - assuming no digit is zero
      var 1..9:a; var 1..9:b; var 1..9:c; var 1..9:d;  var 1..9:e;
       
      % the five digit number
      var 10000..99999:num = 10000*a + 1000*b + 100*c + 10*d + e;
       
      % Anne - one of two statements is true
      constraint ((num + 969) mod 714 == 0) 
      != (3 *(c + e) = (a  + b + c + d + e));
       
      % Bill - one of two statements is true
      constraint ((num + 798) mod 693 == 0) 
      != ((num + 987) mod 658 == 0);
       
      % Carol - one of two statements is true
      constraint ((b + d) == 3 * a) 
      != ((num  + 988) mod 741 == 0);
       
      solve satisfy;
      output ["Number is " ++  show(num) ];
       
      % Number is 32571
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:35 am on 31 October 2021 Permalink | Reply
    Tags: by: R. Crosbie-Hill   

    A Brain-Teaser: [Multiples of 11] 

    From The Sunday Times, 9th June 1957 [link]

    In how many different ways can the following groups of digits be arranged so that the resultant number is a multiple of 11?

    (a) 1, 2, 3, 4, 5.
    (b) 1, 2, 3, 4, 5, 6.
    (c) 1, 2, 3, 4, 5, 6, 7.

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of £5 was offered.

    This puzzle was originally published with no title.

    [teaser-1957-06-09] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:36 am on 31 October 2021 Permalink | Reply

      Again, the advent of computers means that this problem can be tackled with a very simple program, that constructs all possible rearrangements of the digits, and counts those that are divisible by 11.

      The following Python program runs in 56ms.

      Run: [ @replit ]

      from enigma import (subsets, nconcat, icount, printf)
      
      # find numbers that are multiples of 11
      def m11s(ds):
        for ss in subsets(ds, size=len, select="P"):
          n = nconcat(ss)
          if n % 11 == 0:
            yield n
      
      for ds in [(1, 2, 3, 4, 5), (1, 2, 3, 4, 5, 6), (1, 2, 3, 4, 5, 6, 7)]:
        t = icount(m11s(ds))
        printf("{ds} -> {t}")
      

      Solution: (a) 0; (b) 0; (c) 576.

      We can be a bit cleverer though.

      One test for divisibility by 11 is to calculate the alternating sum of the digits (i.e. 1st digit − 2nd digit + 3rd digit − 4th digit + …), and if this is divisible by 11, then the original number is divisible by 11. And it also means that any number formed by rearrangement of the digits, providing the digits originally in odd positions are still in odd positions (and the digits in even positions are still in even positions), will also be divisible by 11.

      The following Python program is a longer, but more efficient method of counting the numbers. (And indeed the internal runtime decreases from 9.03ms to 151µs).

      from enigma import (subsets, factorial, printf)
      
      # count the number of multiples of 11 that can be made from digits ds
      def count_m11s(ds):
        r = 0
        t = sum(ds)
        # consider the 2nd, 4th, ... digits
        n = len(ds)
        k = n // 2
        for ss in subsets(ds, size=k):
          if (t - 2 * sum(ss)) % 11 == 0:
            r += factorial(k) * factorial(n - k)
        return r
      
      for ds in [(1, 2, 3, 4, 5), (1, 2, 3, 4, 5, 6), (1, 2, 3, 4, 5, 6, 7)]:
        t = count_m11s(ds)
        printf("{ds} -> {t}")
      

      In the case of the digits (1, 2, 3, 4, 5, 6, 7), we find that the following numbers in (odd) and (even) positions will form multiples of 11:

      (2, 3, 4, 5) + (1, 6, 7)
      (1, 3, 4, 6) + (2, 5, 7)
      (1, 2, 5, 6) + (3, 4, 7)
      (1, 2, 4, 7) + (3, 5, 6)

      Each collection of digits contributes factorial(4) × factorial(3) = 144 numbers to the total.

      So the answer is: 4 × 144 = 576.

      Like

    • GeoffR's avatar

      GeoffR 12:37 pm on 31 October 2021 Permalink | Reply

      from itertools import permutations
      
      cnt5, cnt6, cnt7 = 0, 0, 0
      
      # 5 consecutive digits - divisibility by 11
      for p1 in permutations((1,2,3,4,5)):
          a,b,c,d,e = p1
          num1 = 10000*a + 1000*b + 100*c + 10*d + e
          if num1 % 11 == 0: cnt5 += 1
      
      print('5 digits divisibility count = ', cnt5)
      
      # 6 consecutive digits - divisibility by 11
      for p2 in permutations((1,2,3,4,5,6)):
          a,b,c,d,e,f = p2
          num2 = 100000*a + 10000*b + 1000*c + 100*d + 10*e + f
          if num2 % 11 == 0: cnt6 += 1
      
      print('6 digits divisibility count = ', cnt6)
      
      # 7 consecutive digits - divisibility by 11
      for p3 in permutations((1,2,3,4,5,6,7)):
          a,b,c,d,e,f,g = p3
          num3 = 1000000*a + 100000*b + 10000*c + 1000*d + 100*e + 10*f + g
          if num3 % 11 == 0: cnt7 += 1
      
      print('7 digits divisibility count = ', cnt7)
      
      # 5 digits divisibility count =  0
      # 6 digits divisibility count =  0
      # 7 digits divisibility count =  576
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:05 pm on 29 October 2021 Permalink | Reply
    Tags:   

    Teaser 3084: Face value 

    From The Sunday Times, 31st October 2021 [link] [link]

    Plato: I have written a different whole number (chosen from 1 to 9 inclusive) on each of the faces of one of my regular solids and have labelled each vertex with the product of the numbers on its adjacent faces. If I tell you the sum of those eight vertex labels, you can’t deduce my numbers, but if I rearrange the numbers on the faces and tell you the new sum, then you can deduce the numbers.

    Eudoxus: Tell me the new sum then.

    Plato: No, but I’ll tell you it’s a 10th power.

    Eudoxus: Aha! I know your numbers now.

    Plato: Yes, that’s right. But if I now told you the original sum, you couldn’t work out which numbers were originally opposite each other.

    What was the original sum?

    [teaser3084]

     
    • Jim Randell's avatar

      Jim Randell 5:01 pm on 29 October 2021 Permalink | Reply

      The cube is the only Platonic Solid [@wikipedia] with 8 vertices.

      This Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (irange, inf, subsets, partitions, group, unpack, peek, powers, printf)
      
      # generate face pairs and vertex sum for a set of numbers
      def arrange(ns):
        for fs in partitions(ns, 2, distinct=1):
          ((U, D), (L, R), (F, B)) = fs
          # calculate the values on the vertices
          vs = (
            U * L * F, U * R * F, U * L * B, U * R * B,
            D * L * F, D * R * F, D * L * B, D * R * B,
          )
          yield (ns, fs, sum(vs))
      
      # generate all possible face pairs and vertex sums
      def generate():
        for ns in subsets(irange(1, 9), size=6):
          yield from arrange(ns)
      
      # map vertex sum to sets of numbers
      d = group(generate(), by=unpack(lambda ns, fs, t: t), f=unpack(lambda ns, fs, t: ns), fn=set)
      
      # look for vertex sums that are 10th powers
      m = max(d.keys())
      for k in powers(1, inf, 10):
        if k > m: break
        vs = d.get(k, None)
        if vs is None: continue
        printf("{k} -> {vs}")
        assert len(vs) == 1
      
        # map vertex sums (for this set of numbers) to face pairs
        r = group(arrange(peek(vs)), by=unpack(lambda ns, fs, t: t), f=unpack(lambda ns, fs, t: fs))
      
        # look for sums with multiple face pairs (and multiple number sets)
        for (t, fs) in r.items():
          if not (len(fs) > 1 and len(d[t]) > 1): continue
          printf("  {t} -> {fs}")
      

      Solution: Plato’s original labelling gave a vertex sum of 1188.

      There are many ways to achieve this, so we couldn’t tell which set of numbers he used:

      (1+8)(2+9)(5+7)
      (1+8)(3+9)(4+7)
      (1+8)(3+9)(5+6)
      (2+9)(3+6)(4+8)
      (2+7)(3+9)(5+6)
      (2+9)(3+6)(5+7)
      (2+7)(4+8)(5+6)

      But he then rearranged his numbers to give a vertex sum of 1024, which can only be done in one way:

      (2+6)(3+5)(7+9)

      So we know he used the numbers (2, 3, 5, 6, 7, 9).

      He then tells us that even though we know the numbers, we still can’t tell his original arrangement.

      This is because there are two ways to achieve 1188 using this set of numbers:

      (2+7)(3+9)(5+6)
      (2+9)(3+6)(5+7)

      And this is the only set of numbers for 1188 that has more than one arrangement.


      Manually, noting:

      (U + D)(L + R)(F + B) = ULF + ULB + URF + URB + DLF + DLB + DRF + DRB

      gives us a quick way to calculate the vertex sum. It is the same as the product of the three sums of opposite face pairs.

      And this lets us simplify the arrange() function above:

      from enigma import multiply
      def arrange(ns):
        for fs in partitions(ns, 2, distinct=1):
          yield (ns, fs, multiply(x + y for (x, y) in fs))
      

      And we see that the only possible value for the 10th power is 2^10 = 1024.

      So the values on opposite faces must sum to powers of 2:

      (1, 3) = 2^2
      (1, 7) = 2^3
      (2, 6) = 2^3
      (3, 5) = 2^3
      (7, 9) = 2^4

      The only arrangement that gives 1024 is:

      (2, 6) (3, 5) (7, 9)

      We can then look for 2 different arrangements of these numbers into pairs that give the same vertex sum. And 1188 is the only possibility.

      Like

      • Frits's avatar

        Frits 11:54 am on 30 October 2021 Permalink | Reply

        @Jim, len(vs) is likely to be 1 but I am not sure if it can be used for checking if the numbers can be deduced.

        Like

        • Jim Randell's avatar

          Jim Randell 12:03 pm on 30 October 2021 Permalink | Reply

          Yes, we can check the values in vs all use the same numbers:

          vs = set(tuple(sorted(flatten(v))) for v in vs)
          assert len(vs) == 1
          

          And then ns is just this single value.

          I’ll update my code.

          (In fact, I’ve rewritten it to be a bit faster and more compact).

          Like

    • Frits's avatar

      Frits 11:19 am on 30 October 2021 Permalink | Reply

        
      from enigma import SubstitutedExpression, tri
      
      ndigits = 9
      nfaces = 6
      npairs = nfaces // 2
      # highest possible sum
      h = ((tri(ndigits) - tri(ndigits - nfaces)) / npairs) ** npairs
      powers = [i for i in range(2, int(h ** 0.1) + 1)]
      
      # based on the entries in <powers> we can easily deduct the digits used
      # in the new sum (in this case the highest pair sum must be a 4th power)
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # find 2 different arrangements with same sum
          "A < C < E",                    # order the pairs
          "A < B and C < D and E < F",    # order within the pairs
          "G < I < K",                    # order the pairs
          "G < H and I < J and K < L",    # order within the pairs
          
          # different arrangements
          "(A, B, C, D, E, F) < (G, H, I, J, K, L)",
          
          # same sum
          "(A + B) * (C + D) * (E + F) == (G + H) * (I + J) * (K + L)",
        ],
        answer="((A, B), (C, D), (E, F)), ((G, H), (I, J), (K, L)), \
                (A + B) * (C + D) * (E + F)",
        # from manual analysis        
        digits=(2, 3, 5, 6, 7, 9),
        distinct=("ABCDEF","GHIJKL"),
        verbose=0,
      )
      
      # print answers
      for (_, ans) in p.solve():
        print(f"the original sum was {ans[2]}")
      

      Like

      • Jim Randell's avatar

        Jim Randell 1:11 pm on 30 October 2021 Permalink | Reply

        Or you could do:

        from enigma import (partitions, multiply, filter_unique, printf)
        
        # possible face pairs
        pairs = partitions((2, 3, 5, 6, 7, 9), 2, distinct=1)
        
        # calculate vertex sum
        fn = lambda fs: multiply(x + y for (x, y) in fs)
        
        # find vertex sums with multiple face pairs
        for fs in filter_unique(pairs, fn).non_unique:
          printf("{t} -> {fs}", t=fn(fs))
        

        Like

    • GeoffR's avatar

      GeoffR 9:31 am on 5 November 2021 Permalink | Reply

      from enigma import all_different
      from itertools import combinations, permutations
      from collections import defaultdict
      
      D3084 = defaultdict(list)
      
      # If the cube sides are (L1, L2, R1, R2, U, D), the sum
      # of eight vertices = (L1 + L2) * (R1 + R2) * (U + D)
      
      # Since 2 ^ 10 = 1024 and 3 ^ 10 = 59049 and the maximum
      # vertices sum for face digits (1..9) = 2805 (17*15*11)
      # ... the 10th power must be 2 ^ 10 = 1024
      
      # find cube face values for 10th power = 1024
      for p1 in permutations(range(1, 10), 6):
        L1, L2, R1, R2, U, D = p1
        if (L1 + L2) * (R1 + R2) * (U + D) == 1024:
          set_faces = set((L1, L2, R1, R2, U, D))
      
      face_pairs = list(combinations(set_faces, 2))
      
      # map six cube face values to sum of eight vertices
      for A, B, C in combinations(face_pairs, 3):
        if all_different(A[0], A[1], B[0], B[1], C[0], C[1]):
          # digits must be same as 10th power of 1024
          if {A[0], A[1], B[0], B[1], C[0], C[1]} == set_faces:
            VSum = (A[0] + A[1]) * (B[0] + B[1]) * (C[0] + C[1])
            D3084[VSum].append(((A[0], A[1]), (B[0], B[1]), (C[0], C[1])))
      
      for k, v in D3084.items():
        # original number must have non-unique face values
        if len(v) > 1:
          print(f"The original number was {k}")
          print(f"Face Values are {v}")
      
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 9:53 am on 8 November 2021 Permalink | Reply

      This programme ran in 300ms with the Chuffed Solver, but took much longer with the Geocode Solver. I had a constraint to limit the set length of 15 values to 14 (i.e. 1 No. duplicated), but it proved not necessary in practice, and increased the run time substantially to 9.5s. This constraint is commented out in the programme.

      The tenth power of two is shown in the output after the code, as is the original sum of 1188 (i.e. the answer), for two instances.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % min vertex sum = (1+2) * (3+4) * (5+6) = 231
      % max vertex sum = (9+8) * (7+6) * (5+4) = 1989
      % ..as 2^10 = 1024 and 3^10 = 59049, therefore 1024 is the only
      % possible 10th power within min and max sum limits
      
      % the cube has six faces with different digits 1..9
      var 1..9: a; var 1..9: b; var 1..9: c;
      var 1..9: d; var 1..9: e; var 1..9: f;
      
      constraint all_different([a, b, c, d, e, f]);
      
      % N1..N15 are all the cube possible vertex totals
      var 231..1989: N1; var 231..1989:N2; var 231..1989:N3;
      var 231..1989: N4; var 231..1989:N5; var 231..1989:N6;
      var 231..1989: N7; var 231..1989:N8; var 231..1989:N9;
      var 231..1989: N10; var 231..1989:N11; var 231..1989:N12;
      var 231..1989: N13; var 231..1989:N14; var 231..1989:N15;
      
      % the fifteen possible vertex totals,
      % from six of the possible nine digits 1..9
      constraint N1 == (a+b) * (c+d) * (e+f);
      constraint N2 == (a+b) * (c+e) * (d+f);
      constraint N3 == (a+b) * (c+f) * (d+e);
      
      constraint N4 == (a+c) * (b+d) * (e+f);
      constraint N5 == (a+c) * (b+e) * (d+f);
      constraint N6 == (a+c) * (b+f) * (d+e);
      
      constraint N7 == (a+d) * (b+c) * (e+f);
      constraint N8 == (a+d) * (b+e) * (c+f);
      constraint N9 == (a+d) * (b+f) * (c+e);
      
      constraint N10 == (a+e) * (b+c) * (d+f);
      constraint N11 == (a+e) * (b+d) * (c+f);
      constraint N12 == (a+e) * (b+f) * (c+d);
      
      constraint N13 == (a+f) * (b+c) * (d+e);
      constraint N14 == (a+f) * (b+d) * (c+e);
      constraint N15 == (a+f) * (b+e) * (c+d);
      
      % One of the vertex totals is a 10th power i.e.2 ^ 10 = 1024
      % ...this constraint fixes the six digits for the solution
      constraint sum([
      N1 == 1024, N2 == 1024, N3 == 1024, N4 == 1024, N5 == 1024,
      N6 == 1024, N7 == 1024, N8 == 1024, N9 == 1024, N10 == 1024,
      N11 == 1024, N12 == 1024, N13 == 1024, N14 == 1024, N15 == 1024
      ]) == 1;
                      
      % constraint card ({N1, N2, N3, N4, N5, N6, N7, N8,
      % N9, N10, N11, N12, N13, N14,  N15}) == 14;
                      
      solve satisfy;
      
      output["N1 = " ++ show(N1) ++ ", sides: " ++ show([a,b]) 
      ++ ", " ++ show([c,d]) ++ ", " ++ show([e,f]) 
      ++ "\nN2 = " ++ show(N2) ++ ", sides: " ++ show([a,b]) 
      ++ ", " ++ show([c,e]) ++ ", " ++ show([d,f])
      ++ "\nN3 = " ++ show(N3) ++ ", sides: " ++ show([a,b]) 
      ++ ", " ++ show([c,f]) ++ ", " ++ show([d,e])
      
      ++ "\nN4 = " ++ show(N4) ++ ", sides: " ++ show([a,c]) 
      ++ ", " ++ show([b,d]) ++ ", " ++ show([e,f])
      ++ "\nN5 = " ++ show(N5) ++ ", sides: " ++ show([a,c]) 
      ++ ", " ++ show([b,e]) ++ ", " ++ show([d,f])
      ++ "\nN6 = " ++ show(N6) ++ ", sides: " ++ show([a,c])
       ++ ", " ++ show([b,f]) ++ ", " ++ show([d,e])
      
      ++ "\nN7 = " ++ show(N7) ++ ", sides: " ++ show([a,d]) 
      ++ ", " ++ show([b,c]) ++ ", " ++ show([e,f])
      ++ "\nN8 = " ++ show(N8) ++ ", sides: " ++ show([a,d]) 
      ++ ", " ++ show([b,e]) ++ ", " ++ show([c,f])
      ++ "\nN9 = " ++ show(N9) ++ ", sides: " ++ show([a,d]) 
      ++ ", " ++ show([b,f]) ++ ", " ++ show([c,e])
      
      ++ "\nN10 = " ++ show(N10) ++ ", sides: " ++ show([a,e])
      ++ ", " ++ show([b,c]) ++ ", " ++ show([d,f])
      ++ "\nN11 = " ++ show(N11) ++ ", sides: " ++ show([a,e])
      ++ ", " ++ show([b,d]) ++ ", " ++ show([c,f])
      ++ "\nN12 = " ++ show(N12) ++ ", sides: " ++ show([a,e])
      ++ ", " ++ show([b,f]) ++ ", " ++ show([c,d])
      
      ++ "\nN13 = " ++ show(N13) ++ ", sides: " ++ show([a,f]) 
      ++ ", " ++ show([b,c]) ++ ", " ++ show([d,e])
      ++ "\nN14 = " ++ show(N14) ++ ", sides: " ++ show([a,f]) 
      ++ ", " ++ show([b,d]) ++ ", " ++ show([c,e])
      ++ "\nN15 = " ++ show(N15) ++ ", sides: " ++ show([a,f]) 
      ++ ", " ++ show([b,e]) ++ ", " ++ show([c,d])
      ];
      
      % 15 Possible vertex totals for 6 sides of a cube
      % N1 = 1210, sides: [3, 7], [9, 2], [5, 6]
      % N2 = 1120, sides: [3, 7], [9, 5], [2, 6]
      % N3 = 1050, sides: [3, 7], [9, 6], [2, 5]
      % N4 = 1188, sides: [3, 9], [7, 2], [5, 6]   <<< original sum = 1188 (answer)
      % N5 = 1152, sides: [3, 9], [7, 5], [2, 6]
      % N6 = 1092, sides: [3, 9], [7, 6], [2, 5]
      % N7 = 880, sides: [3, 2], [7, 9], [5, 6]
      % N8 = 900, sides: [3, 2], [7, 5], [9, 6]
      % N9 = 910, sides: [3, 2], [7, 6], [9, 5]
      % N10 = 1024, sides: [3, 5], [7, 9], [2, 6]   <<< 10th power = 1024 (2^10)
      % N11 = 1080, sides: [3, 5], [7, 2], [9, 6]
      % N12 = 1144, sides: [3, 5], [7, 6], [9, 2]
      % N13 = 1008, sides: [3, 6], [7, 9], [2, 5]
      % N14 = 1134, sides: [3, 6], [7, 2], [9, 5]
      % N15 = 1188, sides: [3, 6], [7, 5], [9, 2]   <<< original sum = 1188 (answer)
      % ----------
      
      
      
      

      Like

      • Frits's avatar

        Frits 12:47 pm on 9 November 2021 Permalink | Reply

        The all_different clause has been replaced by a < b < c < d < e < f.
        Also the card statement has been changed to less equal to 14.

        Now the Geocode solver takes less than half a second (printing all solutions) .
        Chuffed still takes 11 seconds.

         
        % A Solution in MiniZinc
        include "globals.mzn";
         
        % min vertex sum = (1+2) * (3+4) * (5+6) = 231
        % max vertex sum = (9+8) * (7+6) * (5+4) = 1989
        % ..as 2^10 = 1024 and 3^10 = 59049, therefore 1024 is the only
        % possible 10th power within min and max sum limits
         
        % the cube has six faces with different digits 1..9
        var 1..4: a; var 2..5: b; var 3..6: c;
        var 4..7: d; var 5..8: e; var 6..9: f;
         
        constraint a < b /\ b < c /\ c < d /\ d < e /\ e < f;
         
        % N1..N15 are all the cube possible vertex totals
        var 231..1989: N1; var 231..1989:N2; var 231..1989:N3;
        var 231..1989: N4; var 231..1989:N5; var 231..1989:N6;
        var 231..1989: N7; var 231..1989:N8; var 231..1989:N9;
        var 231..1989: N10; var 231..1989:N11; var 231..1989:N12;
        var 231..1989: N13; var 231..1989:N14; var 231..1989:N15;
         
        % the fifteen possible vertex totals,
        % from six of the possible nine digits 1..9
        constraint N1 == (a+b) * (c+d) * (e+f);
        constraint N2 == (a+b) * (c+e) * (d+f);
        constraint N3 == (a+b) * (c+f) * (d+e);
         
        constraint N4 == (a+c) * (b+d) * (e+f);
        constraint N5 == (a+c) * (b+e) * (d+f);
        constraint N6 == (a+c) * (b+f) * (d+e);
         
        constraint N7 == (a+d) * (b+c) * (e+f);
        constraint N8 == (a+d) * (b+e) * (c+f);
        constraint N9 == (a+d) * (b+f) * (c+e);
         
        constraint N10 == (a+e) * (b+c) * (d+f);
        constraint N11 == (a+e) * (b+d) * (c+f);
        constraint N12 == (a+e) * (b+f) * (c+d);
         
        constraint N13 == (a+f) * (b+c) * (d+e);
        constraint N14 == (a+f) * (b+d) * (c+e);
        constraint N15 == (a+f) * (b+e) * (c+d);
         
        % One of the vertex totals is a 10th power i.e.2 ^ 10 = 1024
        % ...this constraint fixes the six digits for the solution
        constraint sum([
        N1 == 1024, N2 == 1024, N3 == 1024, N4 == 1024, N5 == 1024,
        N6 == 1024, N7 == 1024, N8 == 1024, N9 == 1024, N10 == 1024,
        N11 == 1024, N12 == 1024, N13 == 1024, N14 == 1024, N15 == 1024
        ]) == 1;
                         
        constraint card ({N1, N2, N3, N4, N5, N6, N7, N8,
         N9, N10, N11, N12, N13, N14,  N15}) <= 14;
                         
        solve satisfy;
         
        output["N1 = " ++ show(N1) ++ ", sides: " ++ show([a,b]) 
        ++ ", " ++ show([c,d]) ++ ", " ++ show([e,f]) 
        ++ "\nN2 = " ++ show(N2) ++ ", sides: " ++ show([a,b]) 
        ++ ", " ++ show([c,e]) ++ ", " ++ show([d,f])
        ++ "\nN3 = " ++ show(N3) ++ ", sides: " ++ show([a,b]) 
        ++ ", " ++ show([c,f]) ++ ", " ++ show([d,e])
         
        ++ "\nN4 = " ++ show(N4) ++ ", sides: " ++ show([a,c]) 
        ++ ", " ++ show([b,d]) ++ ", " ++ show([e,f])
        ++ "\nN5 = " ++ show(N5) ++ ", sides: " ++ show([a,c]) 
        ++ ", " ++ show([b,e]) ++ ", " ++ show([d,f])
        ++ "\nN6 = " ++ show(N6) ++ ", sides: " ++ show([a,c])
         ++ ", " ++ show([b,f]) ++ ", " ++ show([d,e])
         
        ++ "\nN7 = " ++ show(N7) ++ ", sides: " ++ show([a,d]) 
        ++ ", " ++ show([b,c]) ++ ", " ++ show([e,f])
        ++ "\nN8 = " ++ show(N8) ++ ", sides: " ++ show([a,d]) 
        ++ ", " ++ show([b,e]) ++ ", " ++ show([c,f])
        ++ "\nN9 = " ++ show(N9) ++ ", sides: " ++ show([a,d]) 
        ++ ", " ++ show([b,f]) ++ ", " ++ show([c,e])
         
        ++ "\nN10 = " ++ show(N10) ++ ", sides: " ++ show([a,e])
        ++ ", " ++ show([b,c]) ++ ", " ++ show([d,f])
        ++ "\nN11 = " ++ show(N11) ++ ", sides: " ++ show([a,e])
        ++ ", " ++ show([b,d]) ++ ", " ++ show([c,f])
        ++ "\nN12 = " ++ show(N12) ++ ", sides: " ++ show([a,e])
        ++ ", " ++ show([b,f]) ++ ", " ++ show([c,d])
         
        ++ "\nN13 = " ++ show(N13) ++ ", sides: " ++ show([a,f]) 
        ++ ", " ++ show([b,c]) ++ ", " ++ show([d,e])
        ++ "\nN14 = " ++ show(N14) ++ ", sides: " ++ show([a,f]) 
        ++ ", " ++ show([b,d]) ++ ", " ++ show([c,e])
        ++ "\nN15 = " ++ show(N15) ++ ", sides: " ++ show([a,f]) 
        ++ ", " ++ show([b,e]) ++ ", " ++ show([c,d])
        ];
        

        Like

      • Frits's avatar

        Frits 12:50 pm on 9 November 2021 Permalink | Reply

        @Jim/GeoffR,

        Please let me know how you post minizinc programs.

        Like

        • Jim Randell's avatar

          Jim Randell 2:08 pm on 9 November 2021 Permalink | Reply

          @Frits: Details on using [code] ... [/code] tags are here [link].

          For languages that don’t have a supported syntax highlighter you can use [[ language="text" ]].

          Like

  • Unknown's avatar

    Jim Randell 10:16 am on 28 October 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 905: Prime square 

    From The Sunday Times, 25th November 1979 [link]

    “Here”, said Uncle Henry to the twins, “is a magic square which I’ve started for you”:

    “You must complete it by putting eight different prime numbers in the eight empty squares, so that the [rows], the columns and the diagonals add up to the same total; and it must be the smallest possible total under the conditions.”

    There followed half an hour of comparative peace… after which the twins could bear it no longer.

    “Oh. Uncle”, complained Betty, “It looks so easy and yet it’s much too difficult”. And Brian fervently agreed.

    “All right”, said Uncle Henry, “I’ll tell you a couple more things: there is one such magic square where the number in the middle square is the average of the two numbers directly above and below it; the third largest number is not in the right-hand column; and each square contains one or two digits”.

    After a further ten minutes, the twins managed to produce the right solution.

    Can you complete the magic square?

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

    It is also included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser905]

     
    • Jim Randell's avatar

      Jim Randell 10:17 am on 28 October 2021 Permalink | Reply

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

      The following run file executes in 64ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      #  AB  CD  EF
      #  GH  IJ  KL
      #  MN  01  PQ
      
      SubstitutedExpression
      
      --distinct=""
      --invalid=""
      
      # missing numbers are different 1 or 2 digit primes
      "is_prime(AB)"
      "is_prime(CD)"
      "is_prime(EF)"
      "is_prime(GH)"
      "is_prime(IJ)"
      "is_prime(KL)"
      "is_prime(MN)"
      "is_prime(PQ)"
      "all_different(AB, CD, EF, GH, IJ, KL, MN, PQ)"
      
      # in an additive magic square the magic constant is 3 times the centre value (= 3 * IJ)
      "2 * IJ - 1 = CD"
      "2 * IJ - AB = PQ"
      "2 * IJ - EF = MN"
      "2 * IJ - GH = KL"
      "3 * IJ - AB - CD = EF"
      "3 * IJ - MN - 1 = PQ"
      "3 * IJ - AB - GH = MN"
      "3 * IJ - EF - KL = PQ"
      
      # third largest number is _not_ in the RH column
      "ordered(1, AB, CD, EF, GH, IJ, KL, MN, PQ)[-3] not in {EF, KL, PQ}"
      
      --template="(AB CD EF) (GH IJ KL) (MN 01 PQ)"
      --solution=""
      

      Solution: Here is a diagram of the completed square:

      Like

      • Mark Valentine's avatar

        Mark Valentine 1:21 pm on 28 October 2021 Permalink | Reply

        Third largest number can’t be in right-hand column. Need to flip it.

        Like

        • Mark Valentine's avatar

          Mark Valentine 1:23 pm on 28 October 2021 Permalink | Reply

          Apologies. Misread it. Yours is correct.

          Like

      • Frits's avatar

        Frits 3:41 pm on 28 October 2021 Permalink | Reply

        Using “3 * IJ – AB – MN = GH” instead of “3 * IJ – AB – GH = MN” you don’t have to internally loop over G and H.

        Like

      • Jim Randell's avatar

        Jim Randell 12:35 pm on 29 October 2021 Permalink | Reply

        And here is a Python program that can be used to generate magic squares with larger magic constants:

        from enigma import primes, ordered, arg, printf
        
        # the magic square is:
        #
        #  A B C
        #  D E F
        #  G 1 H
        #
        # so the magic constant k = 3E
        
        # generate magic squares
        def generate():
          # consider values for E
          for E in primes.range(3):
            k = 3 * E
            B = k - E - 1
            if B not in primes: continue
        
            # choose a value for A
            for A in primes.range(3, k - E):
              # calculate the remaining values
              H = k - A - E
              if H not in primes: continue
              C = k - A - B
              if C not in primes: continue
              F = k - C - H
              if F not in primes: continue
              G = k - C - E
              if G + 1 + H != k or G not in primes: continue
              D = k - E - F
              if A + D + G != k or D not in primes: continue
              # check conditions
              if len({A, B, C, D, E, F, G, H}) != 8: continue
              ns = ordered(A, B, C, D, E, F, G, 1, H)
              if ns[-3] in {C, F, H}: continue
              # valid layout
              yield (A, B, C, D, E, F, G, H)
        
        N = arg(1, 0, int)
        for (n, (A, B, C, D, E, F, G, H)) in enumerate(generate(), start=1):
          printf("[ {A} {B} {C} | {D} {E} {F} | {G} 1 {H} ] {k}", k=3 * E)
          if n == N: break
        

        Like

    • Hugh Casement's avatar

      Hugh Casement 2:47 pm on 28 October 2021 Permalink | Reply

      It’s superfluous information (though perhaps helpful) that the middle column is an arithmetic progression.
      Rather than say “the third largest number is not in the right-hand column” it would have been simpler to tell us that the smallest prime is in the left-hand column (otherwise we find a reflection in the vertical axis).
      Without the 1 we would need at least two 3-digit primes to make a magic square.

      Like

    • GeoffR's avatar

      GeoffR 4:12 pm on 28 October 2021 Permalink | Reply

      My solution shows that the 3rd highest number must not be in the right hand column is a definite requirement, as shown in the note at the end of the programme output. I initially wrote a programme without this constraint. I am not sure how this constraint would be programmed in MiniZinc.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      %  A  B  C
      %  D  E  F
      %  G  H  I
      
      var 2..97:A; var 2..97:B; var 2..97:C; 
      var 2..97:D; var 2..97:E; var 2..97:F;
      var 2..97:G; var 2..97:I;
      
      int: H == 1;
      constraint all_different([A, B, C, D, E, F, G, H, I]);
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
      
      % All values of the grid (except H) are primes
      constraint is_prime(A) /\ is_prime(B) /\ is_prime(C)
      /\ is_prime(D) /\ is_prime(E) /\ is_prime(F)
      /\ is_prime(G) /\ is_prime(I);
      
      % All rows, columns and diagonals add to the same total
      constraint 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;
      
      % the middle square is the average of the two numbers directly above and below it
      constraint 2 * E == B + H \/ 2 * D == A + G \/ 2 * F == C + I;
      
      %  the smallest possible total
      solve minimize(A + B + C);
      
      output [ "[A,B,C,D,E,F,G,H,I] = " ++ show([A,B,C,D,E,F,G,H,I] ) ];
      
      % [A, B, C, D, E, F, G, H, I] = [31, 73, 7, 13, 37, 61, 67, 1, 43]
      % ----------
      % ==========
      % Analysis of this solution
      % -------------------------
      % the third highest number (61) must not be in the third column
      % it is moved to the left hand column by transposing left and right columns
      % 31  73  7   =>   7  73  31
      % 13  37  61  =>  61  37  13
      % 67   1  43  =>  43   1  67
      
      
      
      

      Like

      • Frits's avatar

        Frits 10:28 am on 29 October 2021 Permalink | Reply

        @GeoffR, you can declare an array, fill it with same elements as the original array and add an “increasing” constraint.

          
        % A Solution in MiniZinc
        include "globals.mzn";
         
        %  A  B  C
        %  D  E  F
        %  G  H  I
         
        var 2..97:A; var 2..97:B; var 2..97:C; 
        var 2..97:D; var 2..97:E; var 2..97:F;
        var 2..97:G; var 2..97:I;
         
        int: H == 1;
        constraint all_different([A, B, C, D, E, F, G, H, I]);
        
        array[1..9] of var 1..97: nbrs = [A, B, C, D, E, F, G, H, I];
        array[1..9] of var 1..97: sorted;
        
        % make sure both arrays contain same elements
        constraint forall(X in nbrs)(
                      exists(i in 1..9) ( X = sorted[i])
        );
        
        % make sure array "sorted" is sorted
        constraint increasing(sorted);
         
        predicate is_prime(var int: x) = x > 1 /\ 
        forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
         
        % All values of the grid (except H) are primes
        constraint is_prime(A) /\ is_prime(B) /\ is_prime(C)
        /\ is_prime(D) /\ is_prime(E) /\ is_prime(F)
        /\ is_prime(G) /\ is_prime(I);
         
        % All rows, columns and diagonals add to the same total
        constraint 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;
         
        % the middle square is the average of the two numbers
        % directly above and below it
        constraint 2 * E == B + H;
        
        % third largest number is not in the RH column
        constraint C != sorted[7] /\ F != sorted[7] /\ I != sorted[7];
         
        %  the smallest possible total
        solve minimize(A + B + C);
         
        output [show([A,B,C]) ++ "\n" ++ show([D,E,F]) ++ "\n" ++ show([G, H, I])];
        

        Like

    • GeoffR's avatar

      GeoffR 11:00 am on 29 October 2021 Permalink | Reply

      @Frits:

      Yes, that is a neat solution to ensure that the third largest number is not in the right-hand column.

      Like

  • Unknown's avatar

    Jim Randell 9:31 am on 26 October 2021 Permalink | Reply
    Tags:   

    Teaser 2833: Celebrity dogs 

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

    Six celebrities appeared on television with their dogs. Each celebrity brought two dogs and between them they had twelve different breeds.

    The celebrities were Clooney, Hathaway, Jackman, Palermo, Rossum and Seyfried. The breeds of dog were Akita, Basenji, Basset, Bull Terrier, Chihuahua, Dalmation, Foxhound, Keeshond, Plott, Poodle, Rottweiler and Setter.

    For the name and breeds in each trio of celebrity plus their two dogs, if you look at any two out of the three then there are just two letters of the alphabet that occur (once or more) in both.

    In alphabetical order of the breeds, please list the initials of the owners (e.g. C, S, A, C, …)

    [teaser2833]

     
    • Jim Randell's avatar

      Jim Randell 9:32 am on 26 October 2021 Permalink | Reply

      I used the standard spelling of “Dalmatian”, but it doesn’t change the answer if you use the version given in the puzzle text.

      This is another puzzle that can be solved using the [[ grouping ]] functionality from the enigma.py library.

      The following Python program runs in 45ms.

      Run: [ @replit ]

      from enigma import (grouping, join, printf)
      
      # categories for this puzzle (using the normal spelling of "Dalmatian")
      names = ( 'Clooney', 'Hathaway', 'Jackman', 'Palermo', 'Rossum', 'Seyfried' )
      dogs = (
        'Akita', 'Basenji', 'Basset', 'Bull Terrier', 'Chihuahua', 'Dalmatian',
        'Foxhound', 'Keeshond', 'Plott', 'Poodle', 'Rottweiler', 'Setter'
      )
      
      # form the 2-gangs
      for gs in grouping.gangs(2, names, dogs, grouping.share_letters(2)):
        # output the gangs
        grouping.output_gangs(names, gs)
      
        # map breeds to their owners
        m = dict()
        for (n, ds) in zip(names, gs):
          m.update((d, n) for d in ds)
        # output owner initials by breed
        printf("-> {s}", s=join((m[d][0] for d in dogs), sep=" "))
      

      Solution: The initials of the owners are: J P H C J H S R C S R P.

      Ownership is as follows:

      Clooney: Bull Terrier, Plott
      Hathaway: Basset, Dalmatian
      Jackman: Akita, Chihuahua
      Palermo: Basenji, Setter
      Rossum: Keeshond, Rottweiler
      Seyfried: Foxhound, Poodle

      Like

  • Unknown's avatar

    Jim Randell 4:18 pm on 22 October 2021 Permalink | Reply
    Tags:   

    Teaser 3083: Village signposts 

    From The Sunday Times, 24th October 2021 [link] [link]

    Inside a shed I saw where the council was preparing signposts for seven neighbouring villages. Between any two villages there is at most one direct road, and the roads don’t meet except at village centres, where the signposts are to stand. The arms were all affixed, and labelled except for one name to be completed on each. The names in clockwise order on each were as follows:

    Barton, Aston, ?
    Barton, Grafton, ?
    Carlton, Forton, Eaton, ?
    Grafton, Aston, ?
    Dalton, Grafton, Eaton, ?
    Barton, Forton, Carlton, ?
    Dalton, Aston, ?

    Starting at Dalton, I walked along roads, taking the road furthest rightwards at each village and returning to Dalton. I chose the first road so that I visited as many other villages as possible with this method.

    In order, what were the other villages I visited?

    [teaser3083]

     
    • Jim Randell's avatar

      Jim Randell 10:18 pm on 22 October 2021 Permalink | Reply

      I’m not quite sure what “furthest rightwards” means in this context. At the moment I’m assuming it means when I arrive in a village I take the “first right” (i.e. the road that is anti-clockwise from that which I entered the village on).

      And using this interpretation I think there is a unique layout and solution.

      I determined the layout manually (but with programmatic assistance, a fully programmatic determination is given below). The following Python program finds the longest “first right” journey on this layout.

      from enigma import (Accumulator, join, printf)
      
      # the layout is determined by teaser3083c.py
      graph = dict(D='BAC', E='BGF', G='CFEB', F='GAE', B='DGEA', A='BFCD', C='DAG')
      
      # make a journey on the graph, taking the "first right" at each village
      def journey(vs):
        while True:
          (v0, v1) = vs[-2:]
          # are we done?
          if v1 == vs[0]:
            return vs
          # choose the next destination
          ds = graph[v1]
          i = ds.index(v0)
          vs.append(ds[i - 1])
      
      # format a journey
      fmt = lambda vs: join(vs, sep=" -> ")
      
      # find maximal length journeys
      r = Accumulator(fn=max, collect=1)
      
      # start at D, and choose initial destination
      for v in graph['D']:
        vs = journey(['D', v])
        printf("{vs}", vs=fmt(vs))
        r.accumulate_data(len(vs), vs)
      
      for vs in r.data:
        printf("longest: {vs}", vs=fmt(vs))
      

      Solution: The other villages visited were: Carlton, Grafton, Barton.

      Here is a diagram that shows the layout of the roads:

      The possible “first right” journeys from D are:

      D → A → C → D
      D → B → A → D
      D → C → G → B → D

      Like

      • Jim Randell's avatar

        Jim Randell 10:34 pm on 23 October 2021 Permalink | Reply

        And here is a fully programmed solution that determines the layout of the graph (as used in the program above).

        It considers possible ways to complete the signs that give 12 roads, with a signpost at each end, and then assembles them into an acceptable layout with no crossing roads. It does this by turning each town into a “molecule” with the specified roads leading out of it, and then allows one molecule to gradually absorb each of the others so that the roads form a viable layout.

        It runs in 603ms.

        from enigma import (union, multiset, ordered, unpack, irange, find, seq_all_different, Accumulator, map2str, printf)
        
        # the given signposts (with known destinations)
        signs = [ 'BA', 'BG', 'CFE', 'GA', 'DGE', 'BFC', 'DA' ]
        
        # determine the labels used
        labels = union(signs)
        assert len(labels) == len(signs)
        
        # choose a distinct element from each set
        def choose(ss, s=[]):
          # are we done?
          if not ss:
            yield s
          else:
            for x in ss[0]:
              if x not in s:
                yield from choose(ss[1:], s + [x])
        
        # look for the longest match between s1 and s2
        def align(s1, s2):
          r = Accumulator(fn=max)
          n = len(s2)
          for j in irange(1, n):
            # does the first element of s2 match s1?
            i = find(s1, s2[0])
            if i != -1:
              # bring it to the front of s1
              if i > 0: s1 = s1[i:] + s1[:i]
              k = 1
              # extend the match if possible
              while k < n and s1[-1] == s2[k]:
                s1.insert(0, s1.pop(-1))
                k += 1
              # all matches must be used
              if seq_all_different(s1[k:] + s2[k:]):
                # save the length of the match and the aligned sequences      
                r.accumulate_data(k, (list(s1), list(s2)))
              if k == n: break
            if j < n:
              # rotate s2
              s2.append(s2.pop(0))
          if r.value: return (r.value,) + (r.data)
          return (None, None, None)
        
        # can we merge the blobs into a single blob?
        def merge(blobs, blob=None):
          rev = lambda s: s[::-1]
          # initial blob?
          if blob is None:
            (i, blob) = max(enumerate(blobs), key=unpack(lambda i, x: len(x)))
            blob = list(rev(x) for x in blob)
            blobs.pop(i)
          # process the remaining blobs
          while blobs:
            # find the most matching of the remaining blobs
            r = Accumulator(fn=max)
            for (j, b) in enumerate(blobs):
              (k, blob_, b_) = align(blob, b)
              if k:
                r.accumulate_data((k, k - len(b)), (j, b_, blob_))
            if not r.value: return False
            ((k, _), (j, b, blob)) = (r.value, r.data)
            # merge blobs
            blob = blob[k:] + list(rev(x) for x in b[k:])
            blobs.pop(j)
          # did everything match up?
          return (not blob)
        
        # check the graph can be laid out (could check graph planarity)
        def check(g):
          # each blob is a list roads
          blobs = list(list(k + v for v in vs) for (k, vs) in g.items())
          # can the blobs be merged?
          return merge(blobs)
        
        # choose a location for each signpost
        for locs in choose(list(labels.difference(s) for s in signs)):
          # choose the missing destinations
          for dests in choose(list(labels.difference(s + v) for (v, s) in zip(locs, signs))):
            # make the graph
            g = dict((v, s + d) for (v, s, d) in zip(locs, signs, dests))
            # count the roads
            rs = multiset()
            for (s, ds) in g.items():
              rs.update_from_seq(ordered(s, d) for d in ds)
            # each road should have 2 ends
            if not all(v == 2 for v in rs.values()): continue
            # can the graph be laid out?
            if not check(g): continue
            # output the graph
            printf("graph = {g}", g=map2str(g))
        

        Without the [[ check() ]] function at line 90, we find there are 8 candidate layouts, which originally I drew out manually, and the “correct” one was immediately obvious. But it was quite fun to come up with an algorithm to determine which of the layouts is viable.

        Like

  • Unknown's avatar

    Jim Randell 9:07 am on 21 October 2021 Permalink | Reply
    Tags: by: Alfred Moritz   

    Brain-Teaser 882: Another magic class 

    From The Sunday Times, 2nd July 1978 [link]

    This puzzle concerns a class of twenty-five pupils whose first names happen to have different initials (and none begins with X). The teacher makes them sit in alphabetical order in five neat rows of five with Arthur sitting to the left of Barry and in front of Freda.

    When their homework was returned (marked, as usual, out of a maximum of 25 without using fractional marks) they found that each pupil had a different mark and that, surprisingly enough, the total of marks in each row of five, each column of five and each diagonal of five was identical.

    Yvonne came top, followed by Harry and Jane in that order. Ursula scored more marks than Zena, and Richard was beaten by Charles. Victor had twice as many marks as George, and Ivor had four times as many as Freda. Susan beat Michael by the same margin by which Michael beat George (who, incidentally, scored an odd number of marks). This was also the margin by which Walter beat his left-hand neighbour and by which his right-hand neighbour beat him.

    Kenneth beat Olga. By how many marks?

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

    [teaser882]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 21 October 2021 Permalink | Reply

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

      The following run file executes in 73ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # values are 0 to 25, so let's use base 26
      --base=26
      
      # X is the unused value, so: k = (tri(25) - X) / 5, tri(25) = 325
      # so X is a multiple of 5
      "X % 5 == 0"
      
      # each row/column/diagonal sums to the magic constant
      "5 * (A + B + C + D + E) + X == 325"
      "5 * (F + G + H + I + J) + X == 325"
      "5 * (K + L + M + N + O) + X == 325"
      "5 * (P + Q + R + S + T) + X == 325"
      "5 * (U + V + W + Y + Z) + X == 325"
      
      "5 * (A + F + K + P + U) + X == 325"
      "5 * (B + G + L + Q + V) + X == 325"
      "5 * (C + H + M + R + W) + X == 325"
      "5 * (D + I + N + S + Y) + X == 325"
      "5 * (E + J + O + T + Z) + X == 325"
      
      "5 * (A + G + M + S + Z) + X == 325"
      "5 * (E + I + M + Q + U) + X == 325"
      
      # Y is the highest mark
      "(25 if X != 25 else 24) = Y"
      
      # H is the second highest mark
      "(Y - 1 if X != Y - 1 else Y - 2) = H"
      
      # J is the third highest mark
      "(H - 1 if X != H - 1 else H - 2) = J"
      
      # Y beat W by the same margin that W beat V
      "Y - W == W - V"
      
      # and this is the same margin that S beat M and M beat G
      "S - M == Y - W"
      "M - G == Y - W"
      
      # V = 2G, G's score was odd
      "2 * G = V"
      "G % 2 == 1"
      
      # I = 4F
      "4 * F = I"
      
      # U beat Z, C beat R, K beat O
      "U > Z"
      "C > R"
      "K > O"
      
      # [optional]
      --template=""
      --denest
      

      Solution: Kenneth beat Olga by 16 marks.

      The complete layout is:

      [A 21] [B 14] [C  7] [D  0] [E 18]
      [F  2] [G  5] [H 23] [I  8] [J 22]
      [K 20] [L 15] [M 12] [N  9] [O  4]
      [P 11] [Q 16] [R  1] [S 19] [T 13]
      [U  6] [V 10] [W 17] [Y 24] [Z  3]
      [X 25]

      So the marks awarded were 0-24, no-one scored 25.

      Like

    • Frits's avatar

      Frits 12:54 pm on 22 October 2021 Permalink | Reply

       
      # Y + V = 2 * W  \
      # Y = 24 or 25    > V is even so Y is also even
      # V = 2G         /
      #
      # (X, Y, H, J) = (25, 24, 23, 22)
      # W = 17
      #
      # F + G + I = 15 \   G + 5 * F = 15
      # G is odd        >  so G = 5, F = 2, I = 8
      # 4 * F = I      /
      #
      # V = 10 (2 * G)
      # center M = 60 / 12
      # S = 19 (S - M == Y - W)
      #
      # C + H + M + R + W = 60 \  C + R = 8
      #                         >
      # C > R                  /  so only option R = 1, C = 7
      #
      # A + G + M + S + Z = 60 \  A + Z = 24  so Z > 2
      # U > Z                   >
      # U + V + W + Y + Z = 60 /  U + Z = 9 only option Z = 3, U = 6, A = 21
      #
      # D + I + N + S + Y = 60 --> D + N = 9, only options (D, N): (0, 9) or (9, 0)
      #
      # only candidates for 4 are O and T -- > O + T = y + 4  (y is O or T)
      # E + J + O + T + Z = 60 --> E + O + T = 35 --> E + y = 31 --> E > 10
      #
      # B + G + L + Q + V = 60 \  B + L + Q = 45 so B > 6 and also B > 10
      #                         >
      # A + B + C + D + E = 60 /  B + D + E = 32
      #
      # minimal(B + E) = 24 --> D <= 8 --> D = 0 and N = 9
      #
      # E + O + T = 35 \
      #                 > Q = O + T - 1 = y + 3
      # E + Q = 34     /
      #
      # B + E = 32, only options (B, E, Q): (14, 18, 16) or (18, 14, 20)
      # last option implies y = 17 which is not possible
      # so B = 14, E = 18, Q = 16, L = 15
      #
      # A + F + K + P + U = 60 --> K + P = 31
      # only options (K, P): (20, 11) or (11, 20)
      #
      # K + L + M + N + O = 60 --> K + O = 24 with K > O
      # only option (K, O): (20, 4)
      # K = 20, O = 4, T = 13, P = 11 
      

      Like

  • Unknown's avatar

    Jim Randell 9:38 am on 19 October 2021 Permalink | Reply
    Tags:   

    Teaser 2831: Nine ladies dancing 

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

    On the ninth day of Christmas the ladies Anne, Bella, Cary, Debbie, Eileen, Fran, Gill, Honor and Iris entered a dancing competition. In some order they had labels ‘One’, ‘Two’, ‘Three’, ‘Four’, ‘Five’, ‘Six’, ‘Seven’, ‘Eight’ and ‘Nine’ on their backs. They each danced a different dance chosen from cha-cha, charleston, foxtrot, jive, polka, quickstep, samba, twist and waltz.

    For any lady’s name and label there was just one letter of the alphabet that occurred (once or more) in both. The same was true for any lady’s name and their dance, and also for any lady’s label and dance.

    In alphabetical order of their names, what are the numbers of the nine ladies?

    This puzzle completes the archive of Teaser puzzles from 2016. There are now 568 Teaser puzzles available on the site, originally published between 1957 and 2021.

    I have 32 puzzles remaining to post (all from 2017) that I solved at the time, and after that all following puzzles posted will be new to me.

    [teaser2831]

     
    • Jim Randell's avatar

      Jim Randell 9:38 am on 19 October 2021 Permalink | Reply

      This is another puzzle that can be solved using the [[ grouping ]] functionality in the enigma.py library.

      Normally this type of puzzle is by Graham Smithers, but this one is by Victor Bryant.

      The following Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import grouping
      
      # categories for this puzzle
      names = ( 'Anne', 'Bella', 'Cary', 'Debbie', 'Eileen', 'Fran', 'Gill', 'Honor', 'Iris' )
      labels = ( 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine' )
      dances = ( 'cha-cha', 'charleston', 'foxtrot', 'jive', 'polka', 'quickstep', 'samba', 'twist', 'waltz' )
      
      # match the categories into groups such that each pair in each group shares exactly 1 letter
      grouping.solve([names, labels, dances], grouping.share_letters(1))
      

      Solution: The numbers are: eight, one, four, six, three, seven, nine, two, five.

      Like

    • Frits's avatar

      Frits 1:47 pm on 19 October 2021 Permalink | Reply

      With a different version of grouping.groups().

        
      from enigma import grouping
      from itertools import product
      
      # categories for this puzzle
      names = ( 'Anne', 'Bella', 'Cary', 'Debbie', 'Eileen', 'Fran', 'Gill', 
                'Honor', 'Iris' )
      labels = ( 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 
                 'Nine' )
      dances = ( 'cha-cha', 'charleston', 'foxtrot', 'jive', 'polka', 'quickstep',
                 'samba', 'twist', 'waltz' )
       
      def groups(vs, fn, s=[]):
        """
        group the lists of elements in <vs> into groups (one element from each list)
        such that the values in the groups satisfy the selection function <fn>
      
        returns a sequence of groups.
        """
        # are we done?
        if not vs[0]:
          yield tuple(s)
        else:
          # make list of items which together with vs[0][0] satisfy 
          # the selection function <fn> 
          cands = [li for v in vs[1:] 
                   if (li := [x for x in v if fn(*(vs[0][0], x))])]
          if len(cands) > 1:
            for v in product(*(x for x in cands)):
              # the full group is
              group = (vs[0][0],) + v
              # check the group
              if fn(*group):
                # solve for the remaining elements 
                yield from groups([vs[0][1:]] + [[y for y in x if y != v[i]]
                           for i, x in enumerate(vs[1:])], fn, s + [group])
              
      # match the categories into groups such that each pair in each group 
      # shares exactly 1 letter
      for g in groups([names, labels, dances], grouping.share_letters(1)):
        for items in g:
          print(", ".join(items))
      

      Like

    • Frits's avatar

      Frits 2:36 pm on 19 October 2021 Permalink | Reply

      @Jim, do you have grouping puzzles with more than 3 groups?

      Like

      • Jim Randell's avatar

        Jim Randell 2:55 pm on 19 October 2021 Permalink | Reply

        They seem to be a speciality of Sunday Times Teasers (and particularly Graham Smithers). I don’t think I’ve come across any Teasers that use more that three lists of values. (i.e. The first argument to [[ grouping.solve() ]] is a collection of 3 sequences).

        Like

        • Frits's avatar

          Frits 3:05 pm on 19 October 2021 Permalink | Reply

          Using lnames = ( 'Red', 'Lurr', 'Fuss', 'Bun', 'Fix', 'Gav', 'Bax', 'Fox', 'Heg' ) as fourth group:

           
          Anne, Eight, cha-cha, Gav
          Bella, One, jive, Heg
          Cary, Four, quickstep, Red
          Debbie, Six, charleston, Bax
          Eileen, Three, waltz, Lurr
          Fran, Seven, samba, Bun
          Gill, Nine, twist, Fix
          Honor, Two, polka, Fox
          Iris, Five, foxtrot, Fuss
          

          Like

  • Unknown's avatar

    Jim Randell 11:25 am on 17 October 2021 Permalink | Reply
    Tags: by: A. D. Denton   

    A Holiday Brain Teaser: [Diophantine quadruples] 

    From The Sunday Times, 4th August 1957 [link]

    The series below is peculiar in that if any two of the numbers are multiplied together and added to unity, the result is a perfect square:

    1, 3, 8

    Let A[4], be a fourth number in this series. There is a further series with the same characteristic:

    8, B[2], 190, B[4]

    in which not only is B[1], the same as A[3], but B[2] is also the same as A[4].

    What is B[4]?

    (Note: zero is excluded)

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of £5 was offered for a solution to the puzzle, and a further prize of £25 for finding A[5] or B[5].

    This puzzle was originally published with no title.

    [teaser-1957-08-04] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 11:28 am on 17 October 2021 Permalink | Reply

      I am assuming we are looking for a sequence of increasing integers.

      With computers we can easily calculate the next terms.

      Even a simple program runs in less than a second:

      from enigma import (irange, inf, is_square, printf)
      
      # find the smallest number which when multiplied by any of ns, and 1
      # is added to the result, give a square number
      def extend(ns):
        for x in irange(1, inf):
          if all(is_square(x * n + 1) for n in ns):
            return x
      
      # extend the series 1, 3, 8, ...
      A4 = extend([1, 3, 8])
      printf("A4 = {A4}")
      
      # extend the series 8, A4, 190, ...
      B4 = extend([8, A4, 190])
      printf("B4 = {B4}")
      

      But we can be a little bit cleverer, and this gives us a program that runs in just a few milliseconds:

      Run: [ @replit ]

      from enigma import (irange, inf, div, is_square, printf)
      
      # find the smallest number which when multiplied by any of ns, and 1
      # is added to the result, give a square number
      def extend(ns):
        n0 = ns[0]
        # look for squares, z^2, such that: z^2 = n0 * x + 1
        for z in irange(1, inf):
          x = div(z * z - 1, n0)
          if x and all(is_square(n * x + 1) for n in ns[1:]):
            return x
      
      # extend the series 1, 3, 8, ...
      A4 = extend([1, 3, 8])
      printf("A4 = {A4}")
      
      # extend the series 8, A4, 190, ...
      B4 = extend([8, A4, 190])
      printf("B4 = {B4}")
      

      Solution: A[4] = 120. B[4] = 730236.

      See OEIS A030063 [@oeis], which says that the sequence cannot be extended further (although there is a further term in rationals).


      The following was published in the 18th August 1957 edition of The Sunday Times [link]:

      Competitors were asked, in effect, to find fourth terms to two series of numbers such that if any two are multiplied together and added to unity the result is a perfect square.

      The two series were: 1, 3, 8, 120; and: 8, 120, 190, 730236.

      The number of solutions received was 131, of which most were correct: the first correct one opened was from Mr. D. W. Mardle of Bishop’s Cleeve, who will receive £5.

      The fourth number was not easy to find, but Diophantine Equations could be used. A prize of £25 would have been awarded to any competitor who found a fifth term, which the propounder of the problem had been unable to find though he could not prove its non-existence. Many suggested that the fifth number must be very large and one competitor used the electronic brain Deuce, but only up to 10 digits: human brains were better and said that it had more than 10, 20 or 100 digits. No competitor bettered G. Hopkins of Kettering who, prior to publication, had found no number with less than 1,000 digits.

      Some doubted whether it existed and others asserted that it did not. Two competitors said they were working on the solution and would send a fifth number within a few days! Another — more realistically said he would spend next winter looking for it. Some competitors quoted A[5] and B[5] as 777480/8288641 and 194612421699066228/17740920385231762867201 but these were not integral and were not the fifth numbers.

      One competitor quoted the great mathematician Euler (circa 1760), who, it is understood, showed that there was a fifth fractional number but left the existence of a fifth integral number as a matter of inquiry. The competition examined numbers far greater than any mentioned by the well-known mathematicians and in that sense came nearer to a proof than they.

      The following competitors will receive consolation prizes of £2 each as they sent in correct solutions and also made valiant attempts to find the fifth number or a general solutlon: H. McANDREW, J. WILMERS, H. G. APSIMON, A. K. BOLLAND, MISS R. GILBERT, D. C. LESLIE, A. W. JOSEPH, P. A. SAMET, N. H. FANSHAWE, E. J. WATSON.

      Like

      • Jim Randell's avatar

        Jim Randell 12:10 pm on 17 October 2021 Permalink | Reply

        Euler found an infinite family of such 4-tuples:

        {a, b, a + b + 2r, 4r(a + r)(b + r)}; where r² = a⋅b + 1

        (Which means that any pair of values from one quadruple can be used to seed another).

        The A sequence of the puzzle is given by: a = 1; b = 3; r = 2.

        And the B sequence is given by: a = 8; b = 120; r = 31.

        from enigma import (div, printf)
        
        def extend(ns):
          (a, b, c) = ns
          r = div(c - a - b, 2)
          if r is not None and r * r == a * b + 1:
            return 4 * r * (r + a) * (r + b)
        
        # extend the series 1, 3, 8, ...
        A4 = extend([1, 3, 8])
        printf("A4 = {A4}")
        
        # extend the series 8, A4, 190, ...
        B4 = extend([8, A4, 190])
        printf("B4 = {B4}")
        

        In 1969 it was shown that the {1, 3, 8, 120} quadruple cannot be extended with another value, and in 2016 (a mere 59 years after the puzzle was set) it was proved that no Diophantine quintuple exists (see: [ @wikipedia ]). So the £25 prize was safe.

        Like

      • Jim Randell's avatar

        Jim Randell 5:48 pm on 20 October 2021 Permalink | Reply

        And here is a solution using Pell’s equations [revised to use the pells.py module from the py-enigma-plus repository] (see: Teaser 2994), which means fewer candidate numbers are considered when looking for solutions.

        The following Python program runs in 63ms. (Internal runtime is 334µs).

        from enigma import (div, is_square, peek, subsets, diff, printf)
        import pells
        
        # in general to extend a pair (a, b) we need x, such that:
        #
        #  a.x + 1 = A^2
        #  b.x + 1 = B^2
        #
        # so, eliminating x:
        #
        #  B^2 = (b/a)(A^2 - 1) + 1
        #  B^2 = (b/a)A^2 - (b/a) + 1
        #  B^2 - (b/a)A^2 = 1 - b/a
        #
        # which, if b/a is an integer, is a form of Pell's equation
        
        # extend diophantine tuple where b/a is an integer
        def extend(a, b, *rest):
          D = div(b, a)
          for (B, A) in pells.diop_quad(1, -D, 1 - D):
            x = div(A * A - 1, a)
            if x and all(is_square(x * n + 1) for n in rest):
              yield x
        
        # extend 1, 3, 8 ...
        A4 = peek(extend(1, 3, 8))
        printf("A4 = {A4}")
        
        # choose a viable (a, b) pair
        ns = (8, A4, 190)
        (a, b) = peek((a, b) for (a, b) in subsets(sorted(ns), size=2) if b % a == 0)
        B4 = peek(extend(a, b, *diff(ns, [a, b])))
        printf("B4 = {B4}")
        

        Like

    • GeoffR's avatar

      GeoffR 3:09 pm on 17 October 2021 Permalink | Reply

      The best run-time I could get was 12.81 sec on an I9 processor. The Geocode solver took much longer. I had to use the answer to fix variable upper bounds.

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      predicate is_sq(var int: y) =
      exists(z in 1..ceil(sqrt(int2float(ub(y))))) (
              z*z = y);
      
      % 1st series A1, A2, A3, A4
      int: A1 == 1; 
      int: A2 == 3; 
      int: A3 == 8;
      var 1..200: A4;
      
      constraint is_sq(A1 * A2 + 1) /\ is_sq(A1 * A3 + 1) /\ is_sq(A1 * A4 + 1)
      /\ is_sq(A2 * A3 + 1) /\ is_sq(A2 * A4 + 1) /\ is_sq(A3 * A4 + 1);
      
      % 2nd series B1, B2, B3, B4
      int: B1 == 8; 
      var 9..200: B2; 
      int: B3 == 190;
      var 191..800000: B4;
      
      constraint B2 == A4;
      
      constraint is_sq(B1 * B2 + 1) /\ is_sq(B1 * B3 + 1) /\ is_sq(B1 * B4 + 1)
      /\ is_sq(B2 * B3 + 1) /\ is_sq(B2 * B4 + 1) /\ is_sq(B3 * B4 + 1);
      
      solve satisfy;
      
      output [ "A1, A2, A3, A4 = " ++ show([A1, A2, A3, A4]) ++
      "\n" ++ "B1, B2, B3, B4 = " ++ show([B1, B2, B3, B4])];
      
      % A1, A2, A3, A4 = [1, 3, 8, 120]
      % B1, B2, B3, B4 = [8, 120, 190, 730236]
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 10:38 pm on 17 October 2021 Permalink | Reply

      A Python solution ran in 258 msec.

      
      from enigma import is_square as is_sq
      
      # 1st sequence is 1, 3, 8, A4
      for A4 in range (9, 200):
          if not is_sq(A4 * 1 + 1): continue
          if not is_sq(A4 * 3 + 1): continue
          if not is_sq(A4 * 8 + 1): continue
          print('A4 = ', A4)
          # 2nd sequence is 8, A4, 190, B4
          for B4 in range(191, 800000):
              if not is_sq(B4 * 8 + 1): continue
              if not is_sq(B4 * A4 + 1):continue
              if not is_sq(B4 * 190 + 1): continue
              print('B4 = ', B4)
              
      # A4 =  120
      # B4 =  730236
      
      

      Like

    • Frits's avatar

      Frits 12:33 am on 18 October 2021 Permalink | Reply

        
      from enigma import div, first, inf, irange, is_square as is_sq
      
      # sqrt(8 * 190 + 1) = 39
      A4s = [A4 for i in range(3, 40) if is_sq((A4 := i * i - 1) * 8 + 1) and 
                                         is_sq(A4 * 3 + 1)]   
      # 1st sequence is 1, 3, 8, A4
      for A4 in A4s:
        # 2nd sequence is 8, A4, 190, B4
        # sqrt(190 * 190 + 1) = 190.xx
        B4 = first(B4 for i in irange(191, inf) if (B4 := div(i * i - 1, 190)) and 
                   is_sq(B4 * A4 + 1) and is_sq(B4 * 8 + 1) )
        print(f"A4 = {A4}, B4 = {B4[0]}")
      

      Like

    • GeoffR's avatar

      GeoffR 9:17 am on 18 October 2021 Permalink | Reply

      @Frits:

      Neat solution.

      The combined use of first() in an iterator and inf from the enigma.py library solves the problem I had in fixing the upper bound in a search for value B4.

      Like

      • Frits's avatar

        Frits 9:57 am on 18 October 2021 Permalink | Reply

        @GeoffR: the other way of achieving it is to break out of a while True loop for the first correct B4.

        Also possible is using a wheel [2, 40, 152, 190] so that (i * i – 1) always is a multiple of 190.

          
        B4 = first(B4 for k in irange(0, inf) 
                   for i in [189 + (190 * k) + x for x in [2, 40, 152,  190]] 
                   if is_sq((B4 := (i * i - 1) // 190) * A4 + 1) and 
                      is_sq(B4 * 8 + 1))
        

        Like

  • Unknown's avatar

    Jim Randell 5:04 pm on 15 October 2021 Permalink | Reply
    Tags:   

    Teaser 3082: In the swim 

    From The Sunday Times, 17th October 2021 [link] [link]

    Expert mathematicians Al Gritham, Ian Tadger, Carrie Over and Tessa Mole-Poynte have a swimming race for a whole number of lengths. At some point during each length (so not at the ends) they calculate that they have completed a fraction of the distance, and at the finish they compare their fractions (all in the lowest terms). Al says, “My fractions have prime numerators, the set of numerators and denominators has no repeats, their odd common denominator has two digits, and the sum of my fractions is a whole number”. Tessa says, “Everybody’s fractions have all the properties of Al’s, but one of mine is closer to an end of the pool than anybody else’s”.

    What were Tessa’s fractions?

    [teaser3082]

     
    • Jim Randell's avatar

      Jim Randell 10:00 pm on 15 October 2021 Permalink | Reply

      The puzzle doesn’t say anything about the sets of fractions being different, but I think we need to assume that to find a unique solution. (Otherwise 3 of the 4 sets could be the same, with the largest measure, and Tessa’s could be one of the three possible sets that have a smaller measure).

      The program finds all possible sets, collected by the number of lengths in the race, and finds which has the smallest measure.

      I turns out there is only one possible number of lengths, and there are four possible sets of fractions, so if each is assigned to one of the participants Tessa’s must be the one with the minimum measure.

      This Python program runs in 67ms.

      Run: [ @replit ]

      from enigma import (Primes, irange, fraction, mlcm, group, fdiv, Accumulator, printf)
      
      # primes up to 100
      primes = Primes(100)
      
      # select fractions with different numerators and denominators, that sum to a whole number
      def select(ss, rs=[], ns=set(), t=(0, 1)):
        # are we done?
        if not ss:
          if t[1] == 1:
            yield rs
        else:
          # choose the next fraction
          for (a, b) in ss[0]:
            if not (a in ns or b in ns):
              yield from select(ss[1:], rs + [(a, b)], ns.union((a, b)), fraction(a, b, *t))
      
      # generate sets of fractions that satisfy the conditions
      def generate():
      
        # consider the 2-digit, odd common denominator
        for d in irange(11, 99, step=2):
          # must be more than one possible denominator
          if d in primes: continue
      
          # calculate the possible fractions
          fs = list()
          for n in irange(1, d - 1):
            (a, b) = fraction(n, d)
            if a in primes:
              fs.append((a, b))
      
          # consider number of lengths (each denominator must be different)
          for k in irange(2, len(set(b for (a, b) in fs))):
      
            # put the fractions into the corresponding lengths
            lengths = list(list() for _ in irange(k))
            for (a, b) in fs:
              (i, r) = divmod(a * k, b)
              if r > 0:
                lengths[i].append((a, b))
      
            # return possible sets of fractions
            if all(lengths):
              for ss in select(lengths):
                # check the common denominator
                d = mlcm(*(b for (a, b) in ss))
                if 10 < d < 100 and d % 2 == 1:
                  yield ss
      
      # group possible sets of fractions by number of lengths
      d = group(generate(), by=len)
      
      # consider number of lengths
      for k in sorted(d.keys()):
        rs = d[k]
        if len(rs) > 1:
          printf("{k} lengths -> {n} sets", n=len(rs))
          # output sets, and measure
          Q = fdiv
          r = Accumulator(fn=min, collect=1)
          for ss in rs:
            m = min(min(Q(a, b) - Q(i, k), Q(i + 1, k) - Q(a, b)) for (i, (a, b)) in enumerate(ss))
            r.accumulate_data(m, ss)
            printf("-> {ss} [{m:.2%}]")
          # output minimum measure sets
          for ss in r.data:
            printf("min = {ss}")
          printf()
      

      Solution: Tessa’s fractions were: 13/75, 7/15, 3/5, 19/25.

      So the race was over 4 lengths.

      Tessa’s fourth fraction 19/25 = 0.76, so is 1% of the total distance after the start of the final length.

      The other participants must have the following sets of fractions:

      13/63, 3/7, 5/9, 17/21
      7/75, 11/25, 3/5, 13/15
      23/99, 3/11, 5/9, 31/33

      Each set of fractions (including Tessa’s) sum to 2.


      We can show that the number of lengths must be even:

      If we consider a k length race. Then the fractions are:

      0/k < a < 1/k
      1/k < b < 2/k
      2/k < c < 3/k

      (k − 1)/k < z < k/k

      From which we see:

      T(k − 1)/k < a + b + c + … + z < T(k)/k
      (1/2)(k − 1) < sum < (1/2)(k + 1)

      If k is even, k = 2x:

      (1/2)(2x − 1) < sum < (1/2)(2x + 1)
      x − 1/2 < sum < x + 1/2

      and as the sum is an integer we have: sum = x.

      If k is odd, k = 2x + 1:

      (1/2)(2x) < sum < (1/2)(2x + 2)
      x < sum < x + 1

      And there are no integer solutions.

      So k must be even (and the sum of the fractions must be k/2).

      And k cannot be 2, as then the fractions would need to be (a / b) and 1 − (a / b) = (b − a) / b which have the same denominator.

      We could use this observation to reduce the number of cases considered by the program, line 34 becomes:

      for k in irange(4, len(set(b for (a, b) in fs)), step=2):
      

      Then only denominators of 45, 63, 75, 81, 99 are considered, and only races consisting of 4 lengths.

      Like

    • Frits's avatar

      Frits 2:04 pm on 16 October 2021 Permalink | Reply

      Using Jim’s approach as base but trying to do it a little bit different.

      Quite a complex puzzle this week.

        
      from enigma import fraction
      from collections import defaultdict
      from itertools import compress
      
      def primesbelow(n):
        # rwh_primes1v2(n):
        """ Returns a list of primes < n for n > 2 """
        sieve = bytearray([True]) * (n // 2 + 1)
        for i in range(1, int(n ** 0.5) // 2 + 1):
          if sieve[i]:
            sieve[2 * i * (i + 1)::2 * i + 1] = \
            bytearray((n // 2 - 2 * i * (i + 1)) // (2 * i + 1) + 1)
        return [2, *compress(range(3, n, 2), sieve[1:])]
      
      P = primesbelow(100)
                                
      # select fractions with different numerators and denominators, 
      # that sum to a whole number
      def select(ss, k, rs=[], ns=set(), t=(0, 1)):
        nr_processed = len(rs)
        # are we done?
        if nr_processed == k:
          if t[1] == 1:
            yield rs
        else:
          low = nr_processed / k
          hgh = (nr_processed + 1) / k
          for i, (a, b) in enumerate(ss):
            # only select fractions in the same length 
            f = a / b
            if f <= low: continue
            if f >= hgh: break
            
            # choose the next fraction
            if not(a in ns or b in ns):
              yield from select(ss[i + 1:], k, rs + [(a, b)], ns.union((a, b)), 
                            fraction(a, b, *t))   
      
      d = defaultdict(list)
      
      # odd 2-digit common denominators
      for cd in range(11, 100, 2):
        # must be more than one possible denominator
        if cd in P: continue
        
        # generate sets of fractions with prime numerators
        fs = [f for n in range(1, cd) if (f := fraction(n, cd))[0] in P]
        
        k = 2
        while True:
          # break if there is a gap in the set of length numbers
          if len(set((k * a) // b for a, b in fs)) != k:
            break
         
          # select different fractions that sum to a whole number
          for s in select(fs, k):
            # check on 2-digit common denominator, no odd check needed
            if max(b for a, b in s) < 10: continue
            
            d[k].append(s)
          k += 1  
      
      for k, vs in d.items():
        # we need enough data for 4 mathematicians
        if len(vs) < 4: continue
        
        # calculate difference from whole number      
        diffs = [(min((diff := ((k * a) / b) % 1), 1 - diff), v) 
                  for v in vs for a, b in v]
        
        # one of Tessa's fractions is the closest to an end of the pool         
        tessa = min(diffs)
        
        print(f"Tessa's fractions are: {tessa[1]}")      
      

      Like

      • Frits's avatar

        Frits 2:18 pm on 16 October 2021 Permalink | Reply

        Probably no such 1-digit common denominator can ever happen (line 58).

        Like

    • Hugh Casement's avatar

      Hugh Casement 1:44 pm on 24 October 2021 Permalink | Reply

      I don’t understand this puzzle. In fact, I don’t believe it can be understood.
      “Their odd common denominator has two digits” implies that the denominators are all the same, which does not tally with “the set of numerators and denominators has no repeats”.

      “… have completed a fraction of the distance” means to me a fraction of the current length.
      Tessa said that one of her fractions was the closest to an end of the pool; the largest of all Jim’s fractions is 31/33, not 19/25 ! Alternatively, 7/75 is the smallest.

      Altogether the setters of puzzles should say what they mean (and mean what they say, even if that is not the same thing, as the Mad Hatter protested).

      Like

  • Unknown's avatar

    Jim Randell 7:15 am on 14 October 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 879: Seven criminals 

    From The Sunday Times, 11th June 1978 [link]

    The instructor at the police training college spoke to the six constables in his class in these words:

    “You have been studying full-face photographs of seven criminals whom we are calling P, Q, R, S, T, U and V. Now l am going to show you one photograph, taken in profile, of each criminal, and you have to write down their names in the
    order in which I present them.”

    This was done and the constables handed in the following
    six answers:

    1. P Q R S T U V
    2. P Q R U T S V
    3. P S U V R T Q
    4. P S Q U R T V
    5. P U R V T S Q
    6. R P U Q T S V

    “I am pleased to see that each criminal has been correctly identified by at least one of you”, said the instructor. “And I note that you all have a different number of correct answers and so I can give out the prizes”.

    In what order were the photographs presented?

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

    [teaser879]

     
    • Jim Randell's avatar

      Jim Randell 7:16 am on 14 October 2021 Permalink | Reply

      It is straightforward to check all possible arrangements.

      This Python program runs in 87ms.

      Run: [ @replit ]

      from enigma import subsets, seq_all_different, join, printf
      
      # the six answers
      ans = [
        'PQRSTUV',
        'PQRUTSV',
        'PSUVRTQ',
        'PSQURTV',
        'PURVTSQ',
        'RPUQTSV',
      ]
      
      # count number of items in correct position
      correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys))
      
      # consider possible orderings
      for ss in subsets('PQRSTUV', size=len, select="P"):
        # how many are in the correct position?
        pos = list(correct(xs, ss) for xs in ans)
        if not seq_all_different(pos): continue
        # check each item is correct in one of the answers
        if not all(any(xs[i] == x for xs in ans) for (i, x) in enumerate(ss)): continue
        # output solution
        printf("{ss} -> {pos}", ss=join(ss, sep=" ", enc="()"))
      

      Solution: The photographs were presented in the following order: R, P, U, V, T, S, Q.

      And the number of photographs correctly identified by the constables were: 1, 2, 3, 0, 4, 5.

      There is only a single solution without the condition that each criminal was identified correctly by (at least) one of the constables. But we see that the last constable got Q and V wrong (and the rest right), but the penultimate constable got these two right, so each miscreant was identified correctly by one of the constables.

      Like

    • Frits's avatar

      Frits 10:12 am on 15 October 2021 Permalink | Reply

      Borrowing from above program and the solve() function in Teaser 3080 (One of a kind).
      The program runs in 2ms.

        
      # find members of each set, such that each member is used exactly once
      def solve(ss, ns=[], ds=set()):
        # are we done?
        if not(ss):
          yield ns
        else:
          # choose an element from the next set
          for n in ss[0]:
            if not(ds.intersection(n)):
              yield from solve(ss[1:], ns + [n], ds.union(n))
      
      # count number of items in correct position
      correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys))
      
      # the six answers
      ans = [
        'PQRSTUV',
        'PQRUTSV',
        'PSUVRTQ',
        'PSQURTV',
        'PURVTSQ',
        'RPUQTSV',
      ]
      
      nr_criminals = len(ans[0])
      nr_answers = len(ans)
      
      # make list of used values
      lst = [set(a[i] for a in ans) for i in range(nr_criminals)]
      
      for ss in solve(lst):
        pos = set(correct(xs, ss) for xs in ans)
        if len(pos) != nr_answers: continue
        
        print(f"{', '.join(ss)} --> {pos}")
      

      Like

      • Jim Randell's avatar

        Jim Randell 2:57 pm on 15 October 2021 Permalink | Reply

        You can use the following to turn a collection of rows into the corresponding collection of columns:

        cols = zip(*rows)
        

        (And [[ unzip() ]] is an alias for this in enigma.py).

        We can also simplify the way the [[ solve() ]] function operates. (I’ve renamed it [[ select() ]]).

        from enigma import unzip, seq_all_different, join, printf
        
        # the six answers
        ans = [
          'PQRSTUV',
          'PQRUTSV',
          'PSUVRTQ',
          'PSQURTV',
          'PURVTSQ',
          'RPUQTSV',
        ]
        
        # select distinct elements from each set in list of sets <ss>
        def select(ss, rs=[]):
          # are we done?
          if not ss:
            yield rs
          else:
            for x in ss[0].difference(rs):
              yield from select(ss[1:], rs + [x])
        
        # count number of items in correct position
        correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys))
        
        # each photograph was identified correctly in at least one answer
        # so the the correct value occurs in each column
        
        # consider possible correct orderings
        for ss in select(list(set(col) for col in unzip(ans))):
          # how many are in the correct position?
          pos = list(correct(xs, ss) for xs in ans)
          if not seq_all_different(pos): continue
          # output solution
          printf("{ss} -> {pos}", ss=join(ss, sep=" ", enc="()"))
        

        I did try a version of [[ select() ]] that chooses from the column with the smallest number of values available, but it is no advantage on a problem of this size.

        Like

  • Unknown's avatar

    Jim Randell 10:11 am on 12 October 2021 Permalink | Reply
    Tags:   

    Teaser 2830: Time for Christmas 

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

    For Christmas George has bought Martha a novelty digital 24-hour clock. It has an eight-digit display forming four two-digit numbers. The first three of these two-digit numbers are very conventional – the hours, the minutes and the seconds. However, the final two-digit display shows the sum of the first six displayed digits!

    On two occasions today all eight digits displayed were different and three of the four two-digit numbers were perfect squares. Between those two occasions there was a time when the eight digits displayed were again all different, but this time the sum of the eight digits was a perfect square!

    What was the eight-digit display at that in-between time?

    [teaser2830]

     
    • Jim Randell's avatar

      Jim Randell 10:12 am on 12 October 2021 Permalink | Reply

      If we accept that there are only “two occasions” in any given day, then we don’t need to consider all the times, just look until we find the second condition (p2) occurring between the two occurrences of the first condition (p1).

      This code stops when it finds the second occurrence of the first condition and runs in 61ms.

      from enigma import (irange, flatten, cproduct, printf)
      
      # output format: <hour>:<minute>:<second>:<sum> [<state>]
      fmt = "{h:02d}:{m:02d}:{s:02d}:{t:02d} [{state}]"
      
      # 2 digit squares
      squares = set(i * i for i in irange(0, 8))
      
      # initial state
      state = 0
      
      # consider times in order
      for (h, m, s) in cproduct([irange(0, 23), irange(0, 59), irange(0, 59)]):
        # collect digits
        digits = flatten((divmod(x, 10) for x in (h, m, s)), fn=list)
        # add in the sum
        t = sum(digits)
        digits.extend(divmod(t, 10))
        # all digits are different
        if len(set(digits)) != 8: continue
      
        # mark special states
        # p1 = "three of the four two digits numbers are perfect squares"
        p1 = (len(squares.intersection((h, m, s, t))) == 3)
        # p2 = "the sum of all 8 digits is a perfect square"
        p2 = (sum(digits) in squares)
        if not (p1 or p2): continue
      
        # if we're in the initial state, wait until we see a p1
        if state == 0:
          if p1:
            printf(fmt, state='first')
            state = 1
      
        # if we've seen the first p1
        elif state == 1:
          # look for the second p1
          if p1:
            printf(fmt, state='second')
            # and we are done
            break
          # or for an intermediate p2
          elif p2:
            printf(fmt, state='in-between')
      

      Solution: The display at the in-between time was 01:48:59:27.

      The times are:

      01:38:49:25 [first]
      01:48:59:27 [in-between]
      01:49:38:25 [second]

      Like

    • Jim Olson's avatar

      Jim Olson 5:24 pm on 13 October 2021 Permalink | Reply

      Am I missing something but the middle digits sum to 54 which is not a perfect square.

      Like

      • Jim Randell's avatar

        Jim Randell 5:32 pm on 13 October 2021 Permalink | Reply

        At the in-between time the sum of the 8 digits is:

        0+1+4+8+5+9+2+7 = 36 = 6²

        Like

    • Jim Olson's avatar

      Jim Olson 5:51 pm on 13 October 2021 Permalink | Reply

      Thank you I was adding the number 27 instead of the digit sum of 9.

      Like

  • Unknown's avatar

    Jim Randell 4:39 pm on 8 October 2021 Permalink | Reply
    Tags:   

    Teaser 3081: Connect four 

    From The Sunday Times, 10th October 2021 [link] [link]

    I have four different two-digit numbers, each having at least one digit which is a three. When I multiply any three of these numbers together I get a product that, with the inclusion of a leading zero, is one or more repetitions of the repetend of the reciprocal of the fourth two-digit number. A repetend is the repeating or recurring decimal of a number. For example 1 divided by 27 is 0.037037…, giving a repetend of 037; in that case, the product would be 37 or 37037 or 37037037 etc.

    What, in ascending order, are the four two-digit numbers?

    [teaser3081]

     
    • Jim Randell's avatar

      Jim Randell 4:59 pm on 8 October 2021 Permalink | Reply

      This Python program uses the [[ recurring() ]] function from the enigma.py library to calculate the repetends. It runs in 51ms.

      Run: [ @replit ]

      from enigma import (enigma, irange, nsplit, subsets, multiply, format_recurring, printf)
      
      # find the repetend of 1/n (recurring part of the recurring decimal representation)
      recurring = enigma.cache(lambda n: enigma.recurring(1, n, recur=1))
      
      # consider 2-digit numbers containing the digit 3
      ns = list(n for n in irange(10, 99) if 3 in nsplit(n))
      
      # check this set of numbers
      def check(ss, verbose=0):
        m = multiply(ss)
        for n in ss:
          # calculate the product
          p = str(m // n)
          # find the repetend (rr)
          (i, nr, rr) = recurring(n)
          # if the repetend has leading zeros extend the product
          for d in rr:
            if d != '0': break
            p = '0' + p
          # remove copies of the repetend from the product
          k = len(rr)
          while p.startswith(rr):
            p = p[k:]
          # anything left?
          if p: return False
          if verbose: printf("-> 1/{n} = {f}; product = {p}", f=format_recurring((i, nr, rr)), p=m // n)
        # if we get this far we've done it
        return True
      
      # select 4 of the numbers
      for ss in subsets(ns, size=4):
        if check(ss):
          printf("{ss}:")
          check(ss, verbose=1)
          printf()
      

      Solution: The four numbers are: 13, 33, 37, 63.


      Without the requirement that each of the 2-digit number must contain at least one 3, we can find further sets:

      (11, 27, 37, 91)
      (11, 37, 39, 63)
      (13, 21, 37, 99)
      (13, 27, 37, 77)
      (13, 33, 37, 63) [solution to the puzzle]
      (21, 33, 37, 39)

      All these sets have a product of 999999.

      And this is a consequence of the fact that for any fraction f ≤ 1, if a k-length repdigit of 9, multiplied by f gives an integer result, then that result is a k-length repetend of f (suitably padded with leading zeros, and there will be no non-repeating part of the decimal expansion).

      So any set of numbers that multiplies to a 9-repdigit will form such a set.

      For example, multiplying the numbers in the solution to give pairs:

      999999 = (13×33)×(37×63) = 429 × 2331: 1/429 = 0.(002331)…, 1/2331 = 0.(000429)…
      999999 = (13×37)×(33×63) = 481 × 2079: 1/481 = 0.(002079)…, 1/2079 = 0.(000481)…
      999999 = (13×63)×(33×37) = 819 × 1221: 1/819 = 0.(001221)…, 1/1221 = 0.(000819)…

      Or:

      9999999 = 9 × 239 × 4649:
      1/9 = 0.(1)…; 239 x 469 = 1111111
      1/239 = 0.(0041841)…; 9 × 4649 = 41841
      1/4649 = 0.(0002151)…; 9 × 239 = 2151

      Programatically we can easily find 4-sets from the candidates that multiply together to give a 9-repdigit:

      from enigma import (irange, nsplit, subsets, multiply, seq_all_same, printf)
      
      # candidate numbers (can't be divisible by 2 or 5)
      ns = list(n for n in irange(10, 99) if 3 in nsplit(n) and n % 2 and n % 5)
      
      # look for 4-subsets that multiply to a 9-repdigit
      for ss in subsets(ns, size=4):
        p = multiply(ss)
        if seq_all_same(nsplit(p), value=9):
          printf("{ss}; product={p}")
      

      Which we could then pass to the [[ check() ]] function above to verify the solution and output information for each number.

      But there are sets that don’t have a 9-repdigit product, and do have decimal expansions with non-repeating parts, e.g.:

      9990 = 54 × 185
      1/54 = 0.0(185)…
      1/185 = 0.0(054)

      (Although I haven’t found any that aren’t a 9-repdigit multiplied by a power of 10).

      But I did find that the four 2-digit numbers {28, 74, 78, 99} have the following property:

      1/28 = 0.3(571428)…; 74 × 78 × 99 = 571428

      Unfortunately it doesn’t work for the reciprocals of the other terms, but I did notice:

      1/78 = 0.0(128205)… = 0.0128(205128)…; 28 × 74 × 99 = 205128

      Like

      • Jim Randell's avatar

        Jim Randell 11:15 pm on 8 October 2021 Permalink | Reply

        And here is a faster (but slightly longer) version, using the same [[ check() ]] function, that avoids looking at all possible 4-subsets of the candidate numbers, by discarding those with a repetend that is larger than the largest possible product.

        It runs in 48ms (and the internal runtime is 884µs).

        Run: [ @replit ]

        from enigma import (enigma, irange, nsplit, multiply, subsets, format_recurring, printf)
        
        # find the repetend of 1/n (recurring part of the recurring decimal representation)
        recurring = enigma.cached(lambda n: enigma.recurring(1, n, recur=1))
        
        # consider 2-digit numbers containing the digit 3
        ns = list(n for n in irange(10, 99) if 3 in nsplit(n))
        
        # map numbers to repetends
        # [[ and rr[0] == '0' ]] to require repetend has a leading zero
        # [[ and (not nr) ]] to require no non-repeating part
        rep = dict((n, rr) for (n, (i, nr, rr)) in ((n, recurring(n)) for n in ns) if rr)
        
        # remove any numbers with repetends that are too big
        while True:
          M = multiply(ns[-3:])
          ks = list()
          for (k, v) in rep.items():
            if int(v) > M: ks.append(k)
          if not ks: break
          for k in ks: del rep[k]
          ns = sorted(rep.keys())
        
        # check a set of numbers
        def check(ss, verbose=0):
          m = multiply(ss)
          if verbose: printf("{ss}: product = {m}")
          for n in ss:
            # calculate the product
            p = str(m // n)
            # find the repetend (rr)
            rr = rep[n]
            # if the repetend has leading zeros extend the product
            for d in rr:
              if d != '0': break
              p = '0' + p
            # remove copies of the repetend from the product
            k = len(rr)
            while p.startswith(rr):
              p = p[k:]
            # anything left?
            if p: return False
            if verbose: printf("-> 1/{n} = {f}; product = {p}", f=format_recurring(*recurring(n)), p=m // n)
          # if we get this far we've done it
          return True
        
        # select 4 of the numbers
        for ss in subsets(ns, size=4):
          if check(ss):
            check(ss, verbose=1)
            printf()
        

        Like

    • GeoffR's avatar

      GeoffR 10:35 pm on 8 October 2021 Permalink | Reply

      Instead of looking for repeating digit patterns, I thought I would try equating sets of digits in the multiplication of the three two-digit numbers with the digits in the reciprocal of the fourth two-digit number, Not a conventional approach, but it seems to work.

      A set length of nine for reciprocals/multiplications proved adequate to capture the repeating digits patterns for this set comparison method. I omitted the leading zero and decimal point for reciprocal digits set to compare them with multiplication digits. I found the full length decimal reciprocal could give a last digit which was not part of the general repeating pattern.

      from enigma import irange, nsplit
      from itertools import combinations
      
      N4 = []  # list for the groups of four two-digit numbers
      
      # consider 2-digit numbers containing the digit 3
      ns = list(n for n in irange(10, 99) if 3 in nsplit(n))
      
      for p in combinations(ns, 4):
        ab, cd, ef, gh = p
        # check 1st tuple of three numbers and the reciprocal
        s1 = set(str(ab * cd * ef)[:9]).difference(set(['0']))
        r1 = set(str(1 / gh)[:9]).difference(set(['.', '0']))
        if s1 == r1:
          # check 2nd tuple of three numbers and the reciprocal
          s2 = set(str(ab * cd * gh)[:9]).difference(set(['0']))
          r2 = set(str(1 / ef)[:9]).difference(set(['.', '0']))
          if s2 == r2:
            # check 3rd tuple of three numbers and the reciprocal
            s3 = set(str(ab * ef * gh)[:9]).difference(set(['0']))
            r3 = set(str(1 / cd)[:9]).difference(set(['.', '0']))
            if s3 == r3:
              # check 4th tuple of three numbers and the reciprocal
              s4 = set(str(cd * ef * gh)[:9]).difference(set(['0']))
              r4 = set(str(1 / ab)[:9]).difference(set(['.', '0']))
              if s4 == r4:
                L = sorted([ab, cd, ef, gh])
                if L not in N4:
                  N4.append(L)
      
      if len(N4) == 1:
        print(f"Four two digit numbers are {N4[0]}")
      
      
      

      Like

    • Frits's avatar

      Frits 5:09 pm on 9 October 2021 Permalink | Reply

      from enigma import divisors
      
      # consider 2-digit numbers containing the digit 3
      nbrs = list(n for n in range(10, 100) if '3' in str(n))
        
      # retrieve repeating decimals of 1 / n
      def repdec(n):
        c = 100
        dgts = ""
        done = dict()
        i = 0
        while True:
          if c in done:
            rep = dgts[done[c]:]
            if rep[-1] == '0':
              rep = '0' + rep[:-1]
            return rep
          done[c] = i
          q, r = divmod(c, n)
          dgts += str(q)
          c = 10 * r
          i += 1   
         
      # decompose number <t> into <k> numbers from <ns>
      # so that product of <k> numbers equals <t>
      def decompose(t, k, ns, s=[]):
        if k == 1:
          if t in ns and t >= s[-1]:
            yield s + [t]
        else:
          for n in ns:
            if n not in s and (not s or n >= s[-1]):
              yield from decompose(t // n, k - 1, ns.difference([n]), s + [n])
      
      d = dict()
      for n in nbrs:
        rep = repdec(n)
        lngth = len(rep)
        if lngth > 7 or rep == 0: continue 
        r = rep
       
        # extend up to 6 digits
        for _ in range(6 // lngth):
          prod = int(r)
         
          for _ in range(1):     # dummy loop used for breaking
            # 13 * 23 * 30 <= product <= 73 * 83 * 93 
            if not(8970 <= prod <= 563487): break 
           
            # collect all divisors of product
            divs = [x for x in divisors(prod) if x in nbrs]
           
            # skip if less than 3 valid divisors
            if len(divs) < 3: break
           
            # check if product of 3 numbers equals <prod>
            for decomp in decompose(prod, 3, set(divs)):
              n4 = tuple(sorted([n] + decomp))
              d[n4] = d.get(n4, 0) + 1
        
          r += rep  # extend up to 6 digits
      
      # sets of four 2-digit numbers must have been encountered four times
      for k, v in d.items():
        if v == 4:
          print(f"answer: {k}")
       

      Like

      • Frits's avatar

        Frits 11:15 am on 11 October 2021 Permalink | Reply

        or with a recursive function:

          
        # https://codegolf.stackexchange.com/questions/78850/find-the-repetend-of-the-decimal-representation    
        def replist(n, t=1, ns=[], *d):
          return ns[(*d, t).index(t):] or replist(n, (t % n) * 10, ns + [t // n], *d, t)   
         
        # retrieve repeating decimals of 1 / n 
        def repdec(n):
          return "".join(str(x) for x in replist(n)) 
        

        Like

        • Jim Randell's avatar

          Jim Randell 12:34 pm on 11 October 2021 Permalink | Reply

          It’s certainly short, although not necessarily very readable. But it does produce the right answer for positive reciprocals with a non-terminating decimal expansion.

          The puzzle text isn’t completely clear what the intended repetend is for fractions with a normally terminating decimal expansion. You could argue that in this case there is no repetend, and these numbers can be discarded.

          But I prefer the definition that the repetend is the shortest and earliest repeating section of the non-terminating decimal representation of the fraction.

          As every non-zero fraction has a unique non-terminating decimal expansion, this is well-defined.

          And it just so happens it is what the [[ recurring() ]] function returns when [[ recur=1 ]] is set. (The [[ recurring() ]] function was originally written for Enigma 1247, but has proved useful in several other Enigma puzzles).

          So, for example, we have (with repetends in parentheses):

          1/3 = 0.(3)…
          1/1 = 0.(9)…
          1/27 = 0.(037)…
          1/28 = 0.3(571428)…
          1/32 = 0.03124(9)…

          But, it does seem to be possible to solve this puzzle with a less well defined version of “repetend”.

          Like

    • Alex. T,Sutherland's avatar

      Alex. T,Sutherland 1:59 pm on 10 October 2021 Permalink | Reply

      Each of the answer numbers must have a repeatable characteristic.
      (a) Number of possible x3,3x numbers =17. (b) Only 6 from these 17 are repeatable.
      (c) Evaluate any 4 from these 6 (15 possibles) to satisfy the constraints.Ony one solution.
      The sum of the solution is between 140-150. Run time 25ms.

      Like

      • Jim Randell's avatar

        Jim Randell 6:41 pm on 10 October 2021 Permalink | Reply

        @Alex: I found that 8 of the numbers had viable repetends. (i.e. ones where a single repeat was not larger than the largest possible product of three numbers). Or 7 if we exclude numbers with a terminating decimal representation.

        Like

        • Tony Brooke-Taylor's avatar

          Tony Brooke-Taylor 9:50 am on 12 October 2021 Permalink | Reply

          Jim I think if you add the requirement for a leading zero you can reduce this number to five possibilities. I’d be curious to compare my 5 with Alex’s 6 and your 7, once the solution embargo is lifted.

          Like

          • Jim Randell's avatar

            Jim Randell 9:57 am on 12 October 2021 Permalink | Reply

            @Tony: Yes. If you require a leading zero in the repetend, then this reduces the number of candidates to 5. (I took the “inclusion of a leading zero” to be “(where necessary)”, rather than a strict requirement).

            Reducing the set of candidates to 5, means there are only 5 sets of 4 numbers to check.

            In fact multiplying the (numerically) first 3 numbers together, gives the repetend of one of the other 2, so the answer drops out immediately. All that’s left is to check the remaining combinations work as expected. A very simple manual solution.

            Like

    • Jim Olson's avatar

      Jim Olson 4:12 pm on 11 October 2021 Permalink | Reply

      Jim is your EnigmaticCode site down temporarily?

      Like

      • Jim Randell's avatar

        Jim Randell 4:15 pm on 11 October 2021 Permalink | Reply

        Unfortunately, yes.

        It seems WordPress.com suspended the site without warning earlier today. I have contacted them to get it reinstated.

        Hopefully normal service will be resumed before too long.

        Like

    • Jim Randell's avatar

      Jim Randell 6:06 pm on 12 October 2021 Permalink | Reply

      So here’s an interesting fact, repetend fans:

      The repetend of the reciprocal of the square of the k-length 9-repdigit, consists of the concatenation of all the k-length numbers starting from zero, in order, up to the k-length 9-repdigit itself, except that the penultimate k-length number is missing.

      (And there are corresponding results for other bases).

      Let’s see:

      >>> format_recurring(recurring(1, sq(9)))
      '0.(012345679)...'
      
      >>> format_recurring(recurring(1, sq(99)))
      '0.(0001020304050607080910111213141516171819
          2021222324252627282930313233343536373839
          4041424344454647484950515253545556575859
          6061626364656667686970717273747576777879
          80818283848586878889909192939495969799)...'
      
      >>> format_recurring(recurring(1, sq(999)))
      '0.(000001002003004005006007008009010011012013014015016017018019
          020021022023024025026027028029030031032033034035036037038039
          ...
          980981982983984985986987988989990991992993994995996997999)...'
      

      And if you want to know where that penultimate term has gone, here is Numberphile to explain it all [@youtube].

      Like

  • Unknown's avatar

    Jim Randell 9:16 am on 7 October 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 851: Hay fever foiled 

    From The Sunday Times, 6th November 1977 [link]

    Our garden has paths as shown in the plan above. At each junction of paths there is a different variety of plant. The shortest walk (along the paths) from the Marigold to the Sneezewort passes only the Tickseed, and the shortest walk from the Nasturtium to the Quitch passes two plants, one of which is the Lobelia.

    My wife suffers from hay fever and so she never walks past the irritating Rosemary, Sneezewort or Tickseed. We are still able to walk together on the shortest route from the Polyanthus to the Nasturtium (past one other plant), but she has to walk over twice as far as in going from the Orpine to the Marigold.

    List the plants, in order, which my wife passes when taking her shortest route from the Nasturtium to the Marigold.

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

    [teaser851]

     
    • Jim Randell's avatar

      Jim Randell 9:17 am on 7 October 2021 Permalink | Reply

      This Python program runs in 144ms.

      Run: [ @replit ]

      from enigma import (empty, Accumulator, ordered, tuples, SubstitutedExpression, irange, translate, join, cached, printf)
      
      # adjacency matrix
      adj = [
        [1, 3, 4], # 0
        [0, 2, 5], # 1
        [1, 3, 6], # 2
        [0, 2, 7], # 3
        [0, 5, 7, 8], # 4
        [1, 4, 6, 8], # 5
        [2, 5, 7, 8], # 6
        [3, 4, 6, 8], # 7
        [4, 5, 6, 7], # 8
      ]
      
      # distances for each edge
      dist = {
        (0, 1): 314, (1, 2): 314, (2, 3): 314, (0, 3): 314, # = 100 pi
        (0, 4): 100, (1, 5): 100, (2, 6): 100, (3, 7): 100,
        (4, 5): 157, (5, 6): 157, (6, 7): 157, (4, 7): 157, # = 50 pi
        (4, 8): 100, (5, 8): 100, (6, 8): 100, (7, 8): 100,
      }
      
      # find paths from A to B, avoiding Xs
      def paths(A, B, Xs=empty, s=[]):
        if A == B:
          yield s
        else:
          # move forward from A
          for x in adj[A]:
            if x not in s and x not in Xs:
              yield from paths(x, B, Xs, s + [x])
      
      # find minimal distance paths
      @cached
      def min_path(A, B, Xs=empty):
        r = Accumulator(fn=min, collect=1)
        for s in paths(A, B, Xs, [A]):
          d = sum(dist[ordered(x, y)] for (x, y) in tuples(s, 2))
          r.accumulate_data(d, s)
        return r
      
      # is there a minimal path with n intermediates, that include incs?
      def check(A, B, Xs, n=None, incs=[]):
        incs = set(incs)
        r = min_path(A, B, frozenset(Xs))
        for p in r.data:
          if len(p) == n + 2 and incs.issubset(p): return True
        return False
      
      # check distance avoiding Xs is over twice as far
      def check2(A, B, Xs):
        r1 = min_path(A, B)
        r2 = min_path(A, B, frozenset(Xs))
        return (r2.value > 2 * r1.value)
      
      # make a solver
      p = SubstitutedExpression(
        [
          # M -> S passes only T
          "check(M, S, [], 1, [T])",
          # N -> Q passes L and 1 other
          "check(N, Q, [], 2, [L])",
          # P -> N passes 1 other plant
          "check(P, N, [R, S, T], 1)",
          # but she has to walk over twice as far going from O -> M
          "check2(O, M, [R, S, T])",
        ],
        digits=irange(0, 8),
        env=dict(check=check, check2=check2),
        verbose=0,
      )
      
      # run the solver
      for s in p.solve():
        # map positions to symbols
        m = dict((v, k) for (k, v) in s.items())
        # remove duplicate layouts
        if not (m[1] < m[3] and m[0] < min(m[1], m[2], m[3])): continue
      
        # output solution: (outer) (inner) (centre) -> wife's path from N to M
        printf("{m}", m=translate("({0} {1} {2} {3}) ({4} {5} {6} {7}) ({8})", (lambda x: m[int(x)])))
        r = min_path(s['N'], s['M'], frozenset([s['R'], s['S'], s['T']]))
        for p in r.data:
          printf("-> {p}", p=join((m[x] for x in p), sep=" "))
        printf()
      

      Solution: The plants passed are: Orpine, Polyanthus, Quitch.


      Here is a layout of the plants (reflections/rotations of this layout also work), with the wife’s best route from N to M indicated:

      Like

      • Frits's avatar

        Frits 11:39 am on 7 October 2021 Permalink | Reply

        @Jim, I would say there is no solution as normally “twice as far” would mean twice the distance, not at least twice the distance.

        Like

        • Jim Randell's avatar

          Jim Randell 11:50 am on 7 October 2021 Permalink | Reply

          @Frits: the puzzle text says “she has to walk over twice as far”, so the wife has to travel more than twice the distance of the shortest path.

          Like

  • Unknown's avatar

    Jim Randell 9:16 am on 5 October 2021 Permalink | Reply
    Tags:   

    Teaser 2828: Return to Zenda 

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

    Each postcode in Ruritania consists of ten digits, the first three showing the area, the next three showing the town, and the final four showing the house. Just twelve houses have “lucky” postcodes that have no repeated digit and which consist of two three-figure squares followed by a four-figure square. Rudolph lives in such a house and he recently met a girl called Flavia. She only told him that hers was the only house in her area with a lucky postcode, and that her area/town/house numbers were all bigger than his. He has found a postcode satisfying these conditions and sent her a Christmas card, but it has been returned as it was not her postcode.

    What is Rudolph’s postcode?

    [teaser2828]

     
    • Jim Randell's avatar

      Jim Randell 9:16 am on 5 October 2021 Permalink | Reply

      I think the wording of this puzzle could be a bit clearer. But with a reasonable assumption that leading zeros are not allowed in the squares we get a unique solution.

      This Python program runs in 54ms.

      Run: [ @replit ]

      from enigma import (irange, is_duplicate, cproduct, subsets, filter_unique, unpack, join, printf)
      
      # find n-digit squares from a^2 to b^2 with no repeated digits (as
      # strings) leading zeros are allowed
      def squares(n, a, b):
        r = list()
        for i in irange(a, b):
          s = str(i * i).zfill(n)
          if not is_duplicate(s):
            r.append(s)
        return r
      
      # 3 and 4 digit squares
      s3s = squares(3, 10, 31)
      s4s = squares(4, 32, 99)
      
      # collect "lucky" (pandigital) postcodes
      lucky = list((area, town, house)
        for (area, town, house) in cproduct([s3s, s3s, s4s])
          if len(set(area + town + house)) == 10)
      printf("[{n} lucky postcodes]", n=len(lucky))
      
      # choose the 12 lucky postcodes
      for postcodes in subsets(lucky, size=12):
      
        # flavia's postcode is the only (possible) lucky postcode in the area
        fs = filter_unique(postcodes, unpack(lambda a, t, h: a)).unique
      
        # consider rudolph's postcode
        for (ra, rt, rh) in postcodes:
          # find possible postcodes for flavia
          f = list((a, t, h) for (a, t, h) in fs if a > ra and t > rt and h > rh)
          # and there must be more than one possibility
          if len(f) > 1:
            fmt = lambda s: join(s, sep=":", enc="()")
            printf("{r} -> {f}",r=fmt((ra, rt, rh)), f=join(map(fmt, f), sep=' '))
      

      Solution: Rudolph’s postcode is (324:576:1089).

      And there are two possible postcodes for Flavia: (961:784:3025) and (361:784:9025). So Rudolph chose the wrong one.

      We can allow the squares to have leading zeros by changing the value of the [[ a ]] parameter in the calls to [[ squares() ]]. But if we do this we find that are many possible solutions.


      The 12 “lucky” postcodes (area:town:house) are:

      (169:784:3025)
      (196:784:3025)
      (324:576:1089)
      (324:576:9801)
      (361:784:9025)
      (576:324:1089)
      (576:324:9801)
      (784:169:3025)
      (784:196:3025)
      (784:361:9025)
      (784:961:3025)
      (961:784:3025)

      Flavia’s postcode is unique by area:

      (169:784:3025)
      (196:784:3025)
      (361:784:9025)
      (961:784:3025)

      And Rudolph’s postcode must be one of the 12 lucky code above, that has two candidate postcodes for Flavia that are larger in each component. There is only one possibility for Rudolph’s postcode:

      (324:576:1089) → (361:784:9025) or (961:784:3025)


      Without leading zeros there are exactly 12 lucky postcodes, so we know all possible lucky postcodes are in use. (And this is probably the scenario the setter had in mind).

      However, if we allow leading zeros in the squares we can find 34 lucky postcodes, but we know only 12 of them are in use. But how does Flavia know that hers is the only lucky postcode in her area? Is it the only possible lucky postcode in her area? Or just the only one currently in use? In my program I have implemented the latter.

      And using this interpretation we find there are many thousands of situations that lead to a solution, with 6 possible values for Rudolph’s postcode. So I think it is probably safe to assume that the setter intended there to be no leading zeros in the squares.

      Like

      • Frits's avatar

        Frits 11:49 am on 9 June 2025 Permalink | Reply

        or more efficient

        # collect "lucky" (pandigital) postcodes
        lucky = list()
        for (area, house) in product(s3s, s4s):
          if len(ah := set(area + house)) == 7:
            for town in s3s:
              if ah.isdisjoint(town):
                lucky.append((area, town, house))
        

        Like

    • Frits's avatar

      Frits 11:53 am on 5 October 2021 Permalink | Reply

      Checking lucky postcodes outside “SubstitutedExpression” would have been easier and faster but this was more challenging.

        
      from enigma import SubstitutedExpression
      
      # the alphametic puzzle 
      p = SubstitutedExpression(
        [# Rudolph
         "is_square(ABC)",
         "is_square(DEF)",
         "is_square(GHIJ)",
         
         # does more than one Flavia candidate exist for Rudolph (ABC:DEF:GHIJ) ? 
         # Flavia's postcode (a:b:c) is unique by area
         "sum(1 for a in range(ABC + 1, 1000) if is_square(a) and \
                   sum(1 for t in range(100, 1000) if is_square(t) \
                           if len(set(str(a) + str(t))) == 6 \
                         for u in range(1000, 10000) if is_square(u) and \
                         len(set(str(a) + str(t) + str(u))) == 10 \
                       ) == 1 \
                for b in range(DEF + 1, 1000) if is_square(b) \
                  if len(set(str(a) + str(b))) == 6 \
                for c in range(GHIJ + 1, 10000) if is_square(c) and \
                len(set(str(a) + str(b) + str(c))) == 10 \
             ) > 1",
         
        ],
        answer="(str(ABC) + ':' + str(DEF) + ':' + str(GHIJ))",
        distinct=("ABCDEFGHIJ"),
        verbose=0,
      )
      
      # print answer
      for (_, ans) in p.solve():
        print(f"Rudolph's postcode is {ans}")
      

      Like

    • Frits's avatar

      Frits 1:33 pm on 5 October 2021 Permalink | Reply

      Similar.

        
      import sqlite3
      
      sqs = [str(i**2) for i in range(10, 100)]
      
      luckies = []
      for a in [x for x in sqs if len(x) == 3]:
        for b in [x for x in sqs if len(x) == 3]:
          ab = a + b
          if len(set(ab)) != 6: continue
          for c in [x for x in sqs if len(x) == 4]:
            if len(set(ab + c)) != 10: continue
            luckies.append([a, b, c])
      
      # connect to SQLite database that resides in the memory
      sqlite_Connection = sqlite3.connect('temp.db')
      c1 = sqlite3.connect(':memory:')
      
      c1.execute("CREATE TABLE postcodes ( \
                          area INT, \
                          town INT, \
                          house INT \
                  )")
      
      # insert entries into table
      for (x, y, z) in luckies:
        stmnt = "INSERT INTO postcodes VALUES ("
        stmnt += x + ", " + y + ", " + z + ")"
        c1.execute(stmnt)
      
      c1.commit()
      
      stmnt = "SELECT area, town, house, " 
      stmnt += "(SELECT COUNT(1) FROM postcodes B" 
      stmnt += " WHERE B.AREA > A.AREA and" 
      stmnt += "       B.TOWN > A.TOWN and" 
      stmnt += "       B.HOUSE > A.HOUSE and"
      stmnt += "       NOT EXISTS (SELECT 1"  # Flavia's postcode is unique by area
      stmnt += "         FROM postcodes C"
      stmnt += "          WHERE C.AREA = B.AREA and" 
      stmnt += "          (C.TOWN != B.TOWN or"
      stmnt += "           C.HOUSE != B.HOUSE)" 
      stmnt += "         )"
      stmnt += "       ) as nFlavia "
      stmnt += "FROM postcodes A " 
      stmnt += "WHERE nFlavia > 1;"
      
      for ans in c1.execute(stmnt):
        print(f"Rudolph's postcode is {ans[:-1]}")
        
      c1.close()  
      
      if (sqlite_Connection):
        sqlite_Connection.close()
      

      Like

    • GeoffR's avatar

      GeoffR 3:41 pm on 5 October 2021 Permalink | Reply

      A good variety of solutions for this teaser.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      set of int: sq3 = {x*x | x in 10..31};  
      set of int: sq4 = {x*x | x in 32..99};
       
      % Rudolph's digits in 3:3:4 digits for area:town:house numbers
      var 1..9:A; var 0..9:B; var 0..9:C; 
      var 1..9:D; var 0..9:E; var 0..9:F; 
      var 1..9:G; var 0..9:H; var 0..9:I; var 0..9:J;
      constraint all_different ([A,B,C,D,E,F,G,H,I,J]);
      
      % Flavia's digits - 1st set are abc:def:ghij
      var 1..9:a; var 0..9:b; var 0..9:c; 
      var 1..9:d; var 0..9:e; var 0..9:f; 
      var 1..9:g; var 0..9:h; var 0..9:i; var 0..9:j;
      constraint all_different([a,b,c,d,e,f,g,h,i,j]);
      
      % Flavia's digits - 2nd set are mno:pqr:stuv
      var 1..9:m; var 0..9:n; var 0..9:o; 
      var 1..9:p; var 0..9:q; var 0..9:r; 
      var 1..9:s; var 0..9:t; var 0..9:u; var 0..9:v;
      constraint all_different ([m,n,o,p,q,r,s,t,u,v]);
      
      % Rudolph's numbers
      var 100..999: ABC = 100*A + 10*B + C;
      var 100..999: DEF = 100*D + 10*E + F;
      var 1000..9999: GHIJ = 1000*G + 100*H + 10*I + J;
      
      % Flavia's numbers - 1st set
      var 100..999: abc = 100*a + 10*b + c;
      var 100..999: def = 100*d + 10*e + f;
      var 1000..9999: ghij = 1000*g + 100*h + 10*i + j;
      
      % Flavia's numbers - 2nd set
      var 100..999: mno = 100*m + 10*n + o;
      var 100..999: pqr = 100*p + 10*q + r;
      var 1000..9999: stuv = 1000*s + 100*t + 10*u + v;
      
      % Square constraints for Rudolph's and Flavia's numbers
      constraint ABC in sq3 /\ DEF in sq3 /\ GHIJ in sq4;
      constraint abc  in sq3 /\ def in sq3 /\ ghij in sq4;
      constraint mno in sq3 /\ pqr in sq3 /\ stuv in sq4;
      
      % Flavia's area/town/house numbers were all bigger than Rodolph's numbers
      % We conclude she has two numbers as Rudolph made a mistake on the first.
      constraint abc > ABC /\ def > DEF /\ ghij > GHIJ;
      constraint mno > ABC /\ pqr > DEF /\ stuv > GHIJ;
      
      % Flavia's two sets of numbers are different areas/ house numbers
      % ... but make Flavia's town the same in both sets of numbers
      constraint mno > abc /\ pqr == def /\ ghij > stuv;
      
      solve satisfy;
      
      output ["Rudolph's numbers : " ++ show([ABC, DEF, GHIJ])
      ++ "\n" ++ "Flavia's numbers (1st set): " ++ show([abc, def,ghij])
      ++ "\n" ++ "Flavia's numbers (2nd set): " ++ show([mno, pqr, stuv]) ];
      
      % Rudolph's numbers : [324, 576, 1089]
      % Flavia's numbers (1st set): [361, 784, 9025]
      % Flavia's numbers (2nd set): [961, 784, 3025]
      % ----------
      % ==========
      
      
      

      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