Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 4:40 pm on 25 September 2020 Permalink | Reply
    Tags:   

    Teaser 3027: Long shot 

    From The Sunday Times, 27th September 2020 [link] [link]

    Callum and Liam play a simple dice game together using standard dice (numbered 1 to 6). A first round merely determines how many dice (up to a maximum of three) each player can use in the second round. The winner is the player with the highest total on their dice in the second round.

    In a recent game Callum was able to throw more dice than Liam in the second round but his total still gave Liam a chance to win. If Liam had been able to throw a different number of dice (no more than three), his chance of winning would be a whole number of times greater.

    What was Callum’s score in the final round?

    [teaser3027]

     
    • Jim Randell's avatar

      Jim Randell 5:13 pm on 25 September 2020 Permalink | Reply

      This Python program runs in 49ms.

      Run: [ @repl.it ]

      from enigma import (multiset, subsets, irange, div, printf)
      
      # calculate possible scores with 1, 2, 3 dice
      fn = lambda k: multiset.from_seq(sum(s) for s in subsets(irange(1, 6), size=k, select="M"))
      scores = dict((k, fn(k)) for k in (1, 2, 3))
      
      # choose number of dice for Liam and Callum (L < C)
      for (kL, kC) in subsets(sorted(scores.keys()), size=2):
        (C, L) = (scores[kC], scores[kL])
        tL = len(L)
        # consider scores for Callum
        for sC in C.keys():
          # and calculate Liam's chance of winning
          nL = sum(n for (s, n) in L.items() if s > sC)
          if nL == 0: continue
      
          # but if Liam had been able to throw a different number of dice, his
          # chance of winning would be a whole number of times greater (L < H)
          for (kH, H) in scores.items():
            if not (kH > kL): continue
            tH = len(H)
            nH = sum(n for (s, n) in H.items() if s > sC)
            d = div(nH * tL, nL * tH)
            if d is None or not(d > 1): continue
      
            # output solution
            printf("Callum: {kC} dice, score = {sC} -> Liam: {kL} dice, chance = {nL} / {tL}")
            printf("-> Liam: {kH} dice, chance = {nH} / {tH} = {d} times greater")
            printf()
      

      Solution: Callum scored 10 in the final round.

      The result of the first round was that Callum was to throw 3 dice, and Liam was to throw 2.

      Callum scored 10 on his throw. Which meant that Liam had a chance to win by scoring 11 (two ways out of 36) or 12 (one way out of 36), giving a total chance of 3/36 (= 1/12) of winning the game.

      However if Liam had been able to throw 3 dice, he would have had a total chance of 108/216 (= 1/2 = 6/12) of scoring 11 to 18 and winning the game. This is 6 times larger.

      Like

  • Unknown's avatar

    Jim Randell 12:59 pm on 24 September 2020 Permalink | Reply
    Tags:   

    Teaser 2756: Terrible teens 

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

    I have allocated a numerical value (possibly negative) to each letter of the alphabet, where some different letters may have the same value. I can now work out the value of any word by adding up the values of its individual letters. In this way NONE has value 0, ONE has value 1, TWO has value 2, and so on up to ELEVEN having value 11. Unfortunately, looking at the words for the numbers TWELVE to NINETEEN, I find that only two have values equal to the number itself.

    Which two?

    [teaser2756]

     
    • Jim Randell's avatar

      Jim Randell 1:01 pm on 24 September 2020 Permalink | Reply

      (See also: Enigma 1602).

      A manual solution:

      From:

      NONE = 0
      ONE = 1

      we see:

      N = −1

      We can write the unknown words in terms of the known words:

      TWELVE = TWO + ELEVENONE = 12
      THIRTEEN = THREE + TEN + IE = 13 + IE
      FOURTEEN = FOUR + TEN + E = 14 + E
      FIFTEEN = FIVE + TEN + FV = 15 + FV
      SIXTEEN = SIX + TEN + E = 16 + E
      SEVENTEEN = SEVEN + TEN + E = 17 + E
      EIGHTEEN = EIGHT + TEN + ET = 18 + ET
      NINETEEN = NINE + TEN + E = 19 + E

      We see the value of TWELVE is fixed as 12, so this is one of the correct unknown words. So there is only one left to find.

      Note if E = 0, then FOURTEEN, SIXTEEN, SEVENTEEN, NINETEEN are all correct, which is not possible, so E ≠ 0 (and they are all incorrect).

      So the possibilities for the other correct word are:

      THIRTEENI = E
      FIFTEENF = V
      EIGHTEENT = E

      In the first case, when THIRTEEN = 13, we have I = E, so we can substitute I with E.

      So, for NINE and EIGHTEEN:

      N = −1
      NENE = 9 ⇒ EE = 11
      EEGHTEEN = EEGHT + EE + N = 8 + 11 − 1 = 18

      So if THIRTEEN is correct, so is EIGHTEEN.

      And if EIGHTEEN = 18, we have T = E, so for THIRTEEN:

      N = −1
      NINE = 9 ⇒ NINEN = 9 − (−1) = 10
      EHIREEEN = EHREE + NINEN = 3 + 10 = 13

      So THIRTEEN and EIGHTEEN are either both correct (impossible) or both incorrect.

      Which means the only remaining viable solution is FIFTEEN = 15, and so F = V.

      Putting together what we already have and solving the equations gives the following values:

      F = −3
      N = −1
      V = −3

      I = 11 − E
      L = 15 − 3E
      O = 2 − E
      S = 11 − 2E
      T = 11 − E
      W = 2E − 11
      X = 3E − 16
      GH = E − 14
      HR = −E − 8
      UR = E + 5

      TWELVE = 12 [CORRECT]
      THIRTEEN = 24 − 2E
      FOURTEEN = 14 + E
      FIFTEEN = 15 [CORRECT]
      SIXTEEN = 16 + E
      SEVENTEEN = 17 + E
      EIGHTEEN = 7 + 2E
      NINETEEN = 19 + E

      Which makes TWELVE and FIFTEEN the other correct words, providing: E ≠ 0, E ≠ 11/2.

      Solution: TWELVE and FIFTEEN are correct.

      Like

    • Frits's avatar

      Frits 4:55 pm on 24 September 2020 Permalink | Reply

      @Jim,

      I like your equation

      TWELVE = TWO + ELEVEN – ONE = 12

       
      # N+O+N+E = 0 and O+N+E = 1 so N = - 1
      #
      # F+O+U+R+T+E+E+N, S+I+X+T+E+E+N, S+E+V+E+N+T+E+E+N and N+I+N+E+T+E+E+N
      # are all incorrect, they are of the form ...+T+E+E+N 
      # where ... already is an equation. T+E+E+N can't be 10 as not all of
      # the 4 numbers can be correct.
      #
      # Remaining correct candidates are:
      # T+W+E+L+V+E, T+H+I+R+T+E+E+N, F+I+F+T+E+E+N and E+I+G+H+T+E+E+N 
      #
      # E+I+G+H+T+E+E+N = 8+E+E+N = 7+E+E, this can never be 18 
      #
      # T+E+N = T+E-1 = 10, so T = 11 - E
      # N+I+N+E = I+E-2 = 9, so I = 11 - E
      # T+H+I+R+T+E+E+N = 3+I+T+N = 2+I+T = 24 - 2E, this can never be 13 
      # so answer must be T+W+E+L+V+E and F+I+F+T+E+E+N
      

      Like

      • Jim Randell's avatar

        Jim Randell 5:15 pm on 24 September 2020 Permalink | Reply

        @Frits: I take it you are assuming the values are integers. It’s a reasonable assumption, although not explicitly stated (and not actually necessary to solve the puzzle).

        Although if we allow fractions in both cases we get E = 11/2, so in this scenario both THIRTEEN = 13 and EIGHTEEN = 18 would be true, and we would have too many correct values.

        The TWO + ELEVEN = TWELVE + ONE equality also cropped up in Enigma 1278 (where fractions were explicitly allowed).

        Also I changed your enclosure to use:

        [code language="text" gutter="false"]
        ...
        [/code]
        

        Which turns off syntax highlighting and line numbers.

        Like

    • John Crabtree's avatar

      John Crabtree 5:30 am on 25 September 2020 Permalink | Reply

      TEN + NONE = NINE + ONE, and so I = T
      and so THIRTEEN + EIGHTEEN = THREE + TEN + (I – E) + EIGHT + TEN +(E – T) = 31
      And so THIRTEEN and EIGHTEEN are both correct, or both incorrect.

      Like

    • GeoffR's avatar

      GeoffR 8:11 am on 25 September 2020 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      var -25..25: O; var -25..25: N; var -25..25: E;
      var -25..25: T; var -25..25: W; var -25..25: H;
      var -25..25: R; var -25..25: F; var -25..25: U;
      var -25..25: I; var -25..25: V; var -25..25: S;
      var -25..25: X; var -25..25: G; var -25..25: L;
       
      % Numbers NONE .. ELEVEN are all correct 
      constraint N+O+N+E = 0 /\ O+N+E = 1 /\ T+W+O = 2 
      /\ T+H+R+E+E = 3 /\ F+O+U+R = 4 /\ F+I+V+E = 5
      /\ S+I+X = 6 /\ S+E+V+E+N = 7 /\ E+I+G+H+T = 8 
      /\ N+I+N+E = 9 /\ T+E+N = 10 /\ E+L+E+V+E+N = 11;
                
      % Exactly two of TWELVE .. NINETEEN are correct
      constraint sum ( [
      T+W+E+L+V+E == 12, T+H+I+R+T+E+E+N == 13, 
      F+O+U+R+T+E+E+N == 14, F+I+F+T+E+E+N == 15,
      S+I+X+T+E+E+N == 16, S+E+V+E+N+T+E+E+N == 17, 
      E+I+G+H+T+E+E+N == 18, N+I+N+E+T+E+E+N == 19] ) == 2;
      
      solve satisfy;
       
      output [ "TWELVE = ", show (T+W+E+L+V+E), 
               ", THIRTEEN = ", show(T+H+I+R+T+E+E+N),
               ", FOURTEEN = ", show(F+O+U+R+T+E+E+N), 
               ", FIFTEEN = ", show(F+I+F+T+E+E+N),
               ",\nSIXTEEN = ", show(S+I+X+T+E+E+N), 
               ", SEVENTEEN = ", show(S+E+V+E+N+T+E+E+N),
               ", EIGHTEEN = ", show(E+I+G+H+T+E+E+N), 
               ", NINETEEN = ", show(N+I+N+E+T+E+E+N)  ];
      
      % TWELVE = 12, THIRTEEN = 30, FOURTEEN = 11, FIFTEEN = 15,
      % SIXTEEN = 13, SEVENTEEN = 14, EIGHTEEN = 1, NINETEEN = 16
      
      % Answer: Only numbers TWELVE and FIFTEEN are correct
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 2:06 pm on 22 September 2020 Permalink | Reply
    Tags:   

    Teaser 2755: Female domination 

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

    In the village of Alphaville, the number of females divided by the number of males was a certain whole number. Then one man and his wife moved into the village and the result was that the number of females divided by the number of males was one less than before. Now today two more such married couples have moved into the village, but the number of females divided by the number of males is still a whole number.

    What is the population of the village now?

    [teaser2755]

     
    • Jim Randell's avatar

      Jim Randell 2:07 pm on 22 September 2020 Permalink | Reply

      I think this Teaser marks the start of my regular solving (i.e. I solved all subsequent Teasers at the time of their publication). So I should have notes that enable me to fill in the gaps of Teasers set after this one. (Currently I have posted all Teasers from Teaser 2880 onwards, and quite a few earlier ones – there are currently 353 Teasers available on the site). So in some ways it corresponds to Enigma 1482 on the Enigmatic Code site.

      This Python program runs in 45ms.

      Run: [ @replit ]

      from enigma import (irange, inf, divisors_pairs, div, printf)
      
      # generate solutions
      def solve():
        # consider total population
        for t in irange(2, inf):
          # divide the population into males and females
          for (d, m) in divisors_pairs(t, every=1):
            if d < 2: continue
            # p = initial f / m ratio
            p = d - 1
            f = t - m
            # ratio (f + 1) / (m + 1) = p - 1
            if not ((p - 1) * (m + 1) == f + 1): continue
            # ratio (f + 3) / (m + 3) is an integer
            q = div(f + 3, m + 3)
            if q is None: continue
            # final population (after 3 extra couples have moved in)
            yield (t + 6, m, f, p, q)
      
      # find the first solution
      for (pop, m, f, p, q) in solve():
        printf("final population = {pop} [m={m} f={f} p={p} q={q}]")
        break
      

      Solution: The population of the village is now 24.

      Initially there were 3 males and 15 females in the village (initial ratio = 15 / 3 = 5).

      The addition of one couple gave 4 males and 16 females (new ratio = 16 / 4 = 4).

      And the addition of two extra couples gives 6 males and 18 females (final ratio = 18 / 6 = 3).

      So the final population is: 6 + 18 = 24.


      Manually:

      Initially the ratio of females to males is an integer p:

      p = f / m
      f = p.m

      The ratio changes to (p − 1) with 1 more male and 1 more female:

      (p − 1) = (f + 1) / (m + 1)
      (p − 1) = (p.m + 1) / (m + 1)
      (p − 1)(m + 1) = p.m + 1
      p.m + p − m − 1 = p.m + 1
      p = m + 2

      And the ratio (f + 3) / (m + 3) is also an integer q:

      (p.m + 3) / (m + 3) = q
      ((m + 2)m + 3) / (m + 3) = q
      (m² + 2m + 3) / (m + 3) = q
      (m − 1) + 6 / (m + 3) = q

      So (m + 3) is a divisor of 6, greater than 3. i.e. (m + 3) = 6, so m = 3:

      m = 3
      p = 5
      f = 15
      q = 3

      So the solution is unique.

      Like

  • Unknown's avatar

    Jim Randell 9:40 am on 20 September 2020 Permalink | Reply
    Tags: by: C. S. Mence,   

    Brain-Teaser 8: Cat and dog 

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

    In the block of flats where I live there are 4 dogs and 4 cats. The 4 flat numbers, where the dogs are kept, multiplied together = 3,570, but added = my flat number. The 4 numbers, where the cats are kept, multiplied together also = 3,570, but added = 10 less than mine. Some flats keep both a dog and a cat, but there is only one instance of a dog and a cat being kept in adjacent flats.

    At what number do I live, and where are the dogs and cats kept?

    [teaser8]

     
    • Jim Randell's avatar

      Jim Randell 9:41 am on 20 September 2020 Permalink | Reply

      As it is written there are multiple solutions to this puzzle.

      However, we can narrow these down to a single solution (which is the same as the published solution) with the additional condition: “No dogs or cats are kept in flat number 1”.

      This Python program runs in 48ms.

      from enigma import (cproduct, divisor, group, is_disjoint, printf)
      
      # decompose p into k increasing numbers (which when multiplied together give p)
      def decompose(p, k, m=1, s=[]):
        if k == 1:
          if not (p < m):
            yield s + [p]
        else:
          # choose a divisor of p
          for x in divisor(p):
            if not (x < m):
              yield from decompose(p // x, k - 1, x, s + [x])
      
      # collect 4-decompositions of 3570 by sum
      d = group(decompose(3570, 4), by=sum)
      
      # consider possible flat numbers (= sum of dogs)
      for (t, dogs) in d.items():
        # sum of cats is t - 10
        cats = d.get(t - 10, [])
        for (ds, cs) in cproduct([dogs, cats]):
          # some flats keep both a dog and a cat
          if is_disjoint([ds, cs]): continue
          # there is only one instance of a dog and a cat in adjacently numbered flats
          if sum(abs(d - c) == 1 for (d, c) in cproduct([ds, cs])) != 1: continue
          # output solution
          printf("flat={t}: dogs={ds} cats={cs}")
      

      Solution: As set there are 9 possible solutions:

      flat=125, dogs=[1, 2, 17, 105], cats=[1, 5, 7, 102]
      flat=109, dogs=[1, 2, 21, 85], cats=[1, 6, 7, 85]
      flat=99, dogs=[1, 6, 7, 85], cats=[1, 2, 35, 51]
      flat=69, dogs=[1, 7, 10, 51], cats=[1, 6, 17, 35]
      flat=57, dogs=[1, 7, 15, 34], cats=[1, 14, 15, 17]
      flat=55, dogs=[1, 7, 17, 30], cats=[2, 5, 17, 21]
      flat=57, dogs=[2, 3, 17, 35], cats=[1, 14, 15, 17]
      flat=65, dogs=[2, 5, 7, 51], cats=[1, 7, 17, 30]
      flat=45, dogs=[2, 5, 17, 21], cats=[5, 6, 7, 17]

      However, if we eliminate solutions involving flat number 1 then we find only one of these solutions remains:

      flat=45, dogs=[2, 5, 17, 21], cats=[5, 6, 7, 17]

      And this is the published solution. So perhaps the setter “forgot” about 1 when looking at divisors of 3570.

      The following (apology?) was published with Teaser 9:

      Dogs kept at Nos. 2, 5, 17 and 21; cats at 5, 6, 7 and 17. I live at No. 45. This is not a unique solution and no correct entry was rejected.

      We can adjust the program for this variation by setting the m parameter of decompose() to 2 in line 16:

      d = group(decompose(3570, 4, 2), by=sum)
      

      Like

    • Frits's avatar

      Frits 2:04 pm on 20 September 2020 Permalink | Reply

      Limiting flat numbers to 1-199.

       
      from enigma import SubstitutedExpression, join, printf 
      
      #  only one instance of a dog and a cat being kept in adjacent flats  
      adj = lambda li1, li2: sum(1 for x in li1 if x - 1 in li2 or x + 1 in li2)
      
      p = SubstitutedExpression([
          "ABC * DEF * GHI * JKL = 3570",
          "ABC + DEF + GHI + JKL == xyz",
          "MNO * PQR * STU * VWX = 3570",
          "MNO + PQR + STU + VWX + 10 == xyz",
          # numbers are factors of 3570
          "3570 % ABC == 0",
          "3570 % DEF == 0",
          "3570 % GHI == 0",    
          "3570 % JKL == 0",
          "3570 % MNO == 0",
          "3570 % PQR == 0",
          "3570 % STU == 0",
          "3570 % VWX == 0",
          # flat numbers are ascending
          "ABC < DEF",
          "DEF < GHI",
          "GHI < JKL", 
          "MNO < PQR",
          "PQR < STU",
          "STU < VWX",
          # speed up process by limiting range to (1, 199)
          "A < 2", "D < 2", "G < 2", "J < 2",
          "M < 2", "P < 2", "S < 2", "V < 2",
          # only one instance of a dog and a cat being kept in adjacent flats
          "adj([ABC, DEF, GHI, JKL], [MNO, PQR,STU, VWX]) == 1",
          ],
          verbose=0,
          symbols="ABCDEFGHIJKLMNOPQRSTUVWXxyz",
          answer="(ABC, DEF, GHI, JKL, MNO, PQR,STU, VWX, xyz)",
          env=dict(adj=adj), # external functions
          d2i="",            # allow number respresentations to start with 0
          distinct="",       # letters may have same values                     
      )
      
      
      # Solve and print answer
      for (_, ans) in p.solve(): 
        printf("dogs: {p1:<10} - cats: {p2:<11}  Mine={ans[8]}", 
               p1 = join(ans[:4], sep = " "),
               p2 = join(ans[4:8], sep = " "))
      
      # Output:
      #
      # dogs: 1 2 21 85  - cats: 1 6 7 85     Mine=109
      # dogs: 1 6 7 85   - cats: 1 2 35 51    Mine=99
      # dogs: 1 7 10 51  - cats: 1 6 17 35    Mine=69
      # dogs: 1 7 15 34  - cats: 1 14 15 17   Mine=57
      # dogs: 1 7 17 30  - cats: 2 5 17 21    Mine=55
      # dogs: 2 3 17 35  - cats: 1 14 15 17   Mine=57
      # dogs: 2 5 7 51   - cats: 1 7 17 30    Mine=65
      # dogs: 2 5 17 21  - cats: 5 6 7 17     Mine=45
      # dogs: 1 2 17 105 - cats: 1 5 7 102    Mine=125
      

      Like

    • Ellen Napier's avatar

      Ellen Napier 1:03 am on 27 December 2025 Permalink | Reply

      Since “some flats keep both” is plural, I took the number of flats having both
      a dog and a cat to be at least two. That reduces the number of solutions from
      9 to 3.

      An alternative edit (admittedly more complex) would be to change “one instance
      of a cat and a dog” to “one instance of a cat and a cat’s canine companion”,
      which also produces the published solution.

      Like

  • Unknown's avatar

    Jim Randell 5:13 pm on 18 September 2020 Permalink | Reply
    Tags:   

    Teaser 3026: Party time 

    From The Sunday Times, 20th September 2020 [link] [link]

    A four-digit number with different positive digits and with the number represented by its last two digits a multiple of the number represented by its first two digits, is called a PAR.

    A pair of PARs is a PARTY if no digit is repeated and each PAR is a multiple of the missing positive digit.

    I wrote down a PAR and challenged Sam to use it to make a PARTY. He was successful.

    I then challenged Beth to use my PAR and the digits in Sam’s PAR to make a different PARTY. She too was successful.

    What was my PAR?

    [teaser3026]

     
    • Jim Randell's avatar

      Jim Randell 5:30 pm on 18 September 2020 Permalink | Reply

      This Python program uses the [[ SubstitutedExpression() ]] general alphametic solver from the enigma.py library to find possible PARTYs. It then collects pairs of PARs together and looks for a PAR that has two possible pairings that share the same digits.

      It runs in 56ms.

      Run: [ @replit ]

      from enigma import (defaultdict, SubstitutedExpression, irange, subsets, nsplit, printf)
      
      # find possible PARTYs (ABCD, EFGH, X)
      p = SubstitutedExpression(
        ["CD % AB = 0", "ABCD % X = 0", "GH % EF = 0", "EFGH % X = 0", "ABCD < EFGH"],
        digits=irange(1, 9),
        answer="(ABCD, EFGH, X)",
        verbose=0
      )
      
      # collect results by PARs
      d = defaultdict(list)
      for (ABCD, EFGH, X) in p.answers():
        d[ABCD].append(EFGH)
        d[EFGH].append(ABCD)
      
      # collect the digits of a number
      digits = lambda n: sorted(nsplit(n))
      
      # choose the initial PAR
      for (k, vs) in d.items():
        # choose two related PARs ...
        for (p1, p2) in subsets(vs, size=2):
          # ... that have the same digits
          if digits(p1) == digits(p2):
            printf("{k} -> {p1}, {p2}")
      

      Solution: Your PAR was 1785.

      1785 can be paired with both 2496 and 4692 (missing digit = 3) to make a PARTY.

      There are 7 possible PARTYs:

      (1938, 2754, 6)
      (1854, 3672, 9)
      (1836, 2754, 9)
      (1785, 4692, 3)
      (1785, 2496, 3)
      (1734, 2958, 6)
      (1456, 3978, 2)

      There are only 2 PARs that appear in more than one PARTY: 1785 and 2754. Each of these appears in two PARTYs, but only 1785 is paired with two other PARs that share the same digits.

      Like

      • Jim Randell's avatar

        Jim Randell 12:28 pm on 20 September 2020 Permalink | Reply

        Using the [[ SubstitutedExpression ]] general alphametic solver from the enigma.py library.

        This run file executes in 94ms.

        Run: [ @replit ]

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        # Graham's PAR = ABCD
        "CD % AB = 0"
        
        # Sam's PAR = EFGH ...
        "GH % EF = 0"
        
        # ... makes a PARTY with ABCD and X
        "ABCD % X = 0"
        "EFGH % X = 0"
        
        # Beth's PAR = IJKL ...
        "KL % IJ = 0"
        
        # ... makes a different PARTY with ABCD and X
        "IJKL % X = 0"
        "EFGH != IJKL"
        
        # solver parameters
        --digits="1-9"
        --distinct="ABCDEFGHX,ABCDIJKLX"
        --answer="ABCD"
        

        Like

    • Frits's avatar

      Frits 9:12 pm on 19 September 2020 Permalink | Reply

       
      from enigma import nconcat
      from itertools import permutations, combinations
       
      difference = lambda a, b: [x for x in a if x not in b]  
       
      def getPARs(ds, k):
        li = []
        # check all permutations of digits
        for p1 in permutations(ds): 
          # first digit can't be high
          if p1[0] > 4: continue
          
          n12 = nconcat(p1[:2])
          n34 = nconcat(p1[2:])
          n = 100 * n12 + n34
          # number last two digits is a multiple of number first 2 digits
          # and four-digit number is a multiple of k
          if n34 % n12 != 0 or n % k != 0: continue
          # store the PAR 
          li.append(n)
        return li  
            
      sol = [] 
      # loop over missing digits 
      for k in range(1,10):
        digits = difference(range(1,10), [k])
        # choose the lowest remaining digit
        low = min(digits)
        digits = difference(digits, [low])
        # pick 3 digits to complement low
        for c1 in combinations (digits, 3): 
          par1 = getPARs((low,) + c1, k) 
          if par1: 
            par2 = getPARs(difference(digits, c1), k) 
            # PAR(s) related to two or more PARs?
            if len(par1) == 1 and len(par2) == 1:
              continue
            if len(par2) == 1: 
              sol.append([par2, par1])
            elif par2:
              sol.append([par1, par2])
           
      for i in range(len(sol)):
        print(f"{sol[i][0]} --> {sol[i][1]}")
      

      Like

    • Will Harris's avatar

      Will Harris 3:14 pm on 19 December 2020 Permalink | Reply

      Not much help now but wrote this for my coursework project using GNU prolog [ https://www.tutorialspoint.com/execute_prolog_online.php ]

      :- initialization(main).
      % This program has been developed for GNU Prolog | https://www.tutorialspoint.com/execute_prolog_online.php
      
      %Question 2.1
      convert(P, Q) :-
          Q is P - 48,
          Q > 0.
      
      unique(List) :-
          sort(List, Sorted),
          length(List, OriginalLength),
          length(Sorted, SortedLength),
          OriginalLength =:= SortedLength.
      
      multiple_of([A,B,C,D]) :-
          get_num(A,B,X),
          get_num(C,D,Y),
          check_mod(X,Y).
          
      get_num(P,Q,R) :-
          R is P * 10 + Q.
      
      check_mod(N,M) :-
          M mod N =:= 0.
          
      par(Number) :-
          number_codes(Number,ListCharCodes), 
          maplist(convert,ListCharCodes,ListDigits),
          length(ListDigits, 4),
          unique(ListDigits),
          multiple_of(ListDigits).
      
      
      %Question 2.2
      candidate_pars(P, Q, []) :-
          P > Q.
      candidate_pars(P, Q, [P|W]) :-
          par(P),
          P1 is P + 1,
          candidate_pars(P1, Q, W).
      candidate_pars(P, Q, W) :- 
          P1 is P + 1,
          candidate_pars(P1, Q, W).
      
      pars( PARS ) :-
          candidate_pars(1234, 9876, PARS).
      
      
      %Question 2.3
      missing_digit(V,W,CombDigits) :-
          number_codes(V, ListOne),
          number_codes(W, ListTwo),
          maplist(convert, ListOne, ListDigitsOne),
          maplist(convert, ListTwo, ListDigitsTwo),
          append(ListDigitsOne, ListDigitsTwo, CombDigits).
      
      party(V, W) :-
          missing_digit(V, W, CombDigits),
          subtract([1,2,3,4,5,6,7,8,9], CombDigits, MissingDigits),
          length(MissingDigits, 1),
          par(V),
          par(W),
          nth(1, MissingDigits, M),
          check_mod(M,V),
          check_mod(M,W).
      
      
      %Question 2.4
      comparison(PARS, I, J) :-
          member(I, PARS), 
          member(J, PARS), 
          party(I,J).
      
      partys( PARTYS ) :-
          pars(PARS),
          findall([I,J], comparison(PARS, I, J), PARTYS).
      
      main :-
          partys( PARTYS ), write(PARTYS).
      
      /*
      There is only a single solution to Teaser 3026:
      There are only 2 PARs that appear in more than one PARTY: 1785 and 2754. 
      Each of these appears in two PARTYs, but only 1785 is paired with two other
      PARs that share the same digits. 1785 can be paired with both 2496 and 4692 
      (missing digit = 3) to make a PARTY.
      */
      

      Like

      • Jim Randell's avatar

        Jim Randell 3:26 pm on 19 December 2020 Permalink | Reply

        @Will: Thanks for your comment.

        I’m not sure if the code came through correctly (WordPress can get confused by text with < and > characters in it).

        If it’s wrong, can you post again with the code in:

        [code]
        code goes here
        [/code]
        

        and I’ll fix it up.

        Like

    • GeoffR's avatar

      GeoffR 4:57 pm on 16 December 2021 Permalink | Reply

      
      from itertools import permutations
      
      digits = set('123456789')
      
      for p1 in permutations(digits, 5):
          A, B, C, D, I = p1
          # my PAR is ABCD and I is the missing digit
          ABCD = int(A + B + C + D)
          if ABCD % int(I) !=0: continue
          AB, CD = int(A + B), int(C + D)
          if not(CD % AB == 0):continue
          
          # find another two PARs to make two PARTYs
          q1 = digits.difference(p1)
          for p2 in permutations(q1):
              E, F, G, H = p2
              EFGH = int(E + F + G + H)
              if not EFGH % int(I) == 0: continue
              EF, GH = int(E + F), int(G + H)
              if not (GH % EF == 0):continue
              
              # form 2nd PAR with digits (E, F, G, H)
              for p3 in permutations(p2):
                  e, f, g, h = p3
                  efgh = int(e + f + g + h)
                  # there must be two different PARs from EFGH
                  if EFGH == efgh:continue
                  if efgh % int(I) != 0: continue
                  ef, gh = int(e + f), int(g + h)
                  if not (gh % ef == 0):continue
                  print(f"My PAR = {ABCD}")
                  print(f"Sam / Beth's PARTY = {ABCD}, {EFGH}")
                  print(f"Sam / Beth's PARTY = {ABCD}, {efgh}")
                  print()
      
      # My PAR = 1785
      # Sam / Beth's PARTY = 1785, 4692
      # Sam / Beth's PARTY = 1785, 2496
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 11:59 am on 17 September 2020 Permalink | Reply
    Tags:   

    Teaser 2736: HS2 

    From The Sunday Times, 1st March 2015 [link] [link]

    Last night I dreamt that I made a train journey on the HS2 line. The journey was a whole number of miles in length and it took less than an hour. From the starting station the train accelerated steadily to its maximum speed of 220 mph, then it continued at that speed for a while, and finally it decelerated steadily to the finishing station. If you took the number of minutes that the train was travelling at a steady speed and reversed the order of its two digits, then you got the number of minutes for the whole journey.

    How many miles long was the journey?

    [teaser2736]

     
    • Jim Randell's avatar

      Jim Randell 12:00 pm on 17 September 2020 Permalink | Reply

      If the time at constant speed is AB minutes, then the total time is BA minutes, and:

      AB < BA < 60

      So:

      0 < A < B < 6

      The maximum speed is 220 mph = 11/3 miles per minute.

      From the velocity/time graph we get the total distance d is:

      d = (121/6)(A + B)

      From which we see (A + B) must be divisible by 6, so (A, B) = (1, 5) or (2, 4), and d = 121.

      A simple Python program can verify this:

      Run: [ @replit ]

      from enigma import (subsets, irange, div, printf)
      
      for (A, B) in subsets(irange(1, 5), size=2):
        d = div(121 * (A + B), 6)
        if d is not None:
          printf("d={d} [A={A} B={B}]")
      

      Solution: The journey was 121 miles long.

      Like

  • Unknown's avatar

    Jim Randell 8:31 am on 15 September 2020 Permalink | Reply
    Tags: ,   

    Teaser 2719: Foursums 

    From The Sunday Times, 2nd November 2014 [link] [link]

    In a woodwork lesson the class was given a list of four different non-zero digits. Each student’s task was to construct a rectangular sheet whose sides were two-figure numbers of centimetres with the two lengths, between them, using the four given digits. Pat constructed the smallest possible such rectangle and his friend constructed the largest possible. The areas of these two rectangles differed by half a square metre.

    What were the four digits?

    [teaser2719]

     
    • Jim Randell's avatar

      Jim Randell 8:32 am on 15 September 2020 Permalink | Reply

      This Python program runs in 55ms.

      Run: [ @repl.it ]

      from enigma import (subsets, irange, Accumulator, partitions, product, nconcat, join, printf)
      
      # generate (width, height) pairs from digits <ds>
      def generate(ds):
        for (ws, hs) in partitions(ds, 2):
          for (w, h) in product((ws, ws[::-1]), (hs, hs[::-1])):
            yield (nconcat(w), nconcat(h))
      
      # choose 4 different non-zero digits
      for ds in subsets(irange(1, 9), size=4):
        # record the min/max areas
        rs = Accumulator.multi(fns=[min, max])
      
        # form the digits into width, height pairs
        for (w, h) in generate(ds):
          # calculate the area: A = ab x cd
          A = w * h
          rs.accumulate_data(A, (w, h))
      
        # do the max and min areas differ by 5000 cm^2 ?
        (mn, mx) = rs
        if mx.value - mn.value == 5000:
          printf("digits = {ds}")
          printf("-> min area = {mn.value} = {wh}", wh=join(mn.data, sep=" x "))
          printf("-> max area = {mx.value} = {wh}", wh=join(mx.data, sep=" x "))
          printf()
      

      Solution: The four digits are: 1, 6, 7, 8.

      The minimum area is: 17 × 68 = 1156.

      The maximum area is: 81 × 76 = 6156.

      Like

    • Frits's avatar

      Frits 11:55 pm on 15 September 2020 Permalink | Reply

       
      from enigma import SubstitutedExpression, irange, subsets
      
      # determine smallest possible rectangle 
      def mini(A, B, C, D):
        minimum = 9999
        for s in subsets(str(A)+str(B)+str(C)+str(D), size=4, select="P"):
          area = (int(s[0])*10 + int(s[1])) * (int(s[2])*10 + int(s[3]))
          minimum = min(minimum, area)
        return minimum  
        
      # determine larges possible rectangle
      def maxi(A, B, C, D):
        maximum = 0 
        for s in subsets(str(A)+str(B)+str(C)+str(D), size=4, select="P"):
          area = (int(s[0])*10 + int(s[1])) * (int(s[2])*10 + int(s[3]))
          maximum = max(maximum, area)
        return maximum   
          
      
      p = SubstitutedExpression([
          "(maxi(A, B, C, D) - mini(A, B, C, D)) == 5000",
          # only report a solution once
          "A < B",
          "B < C", 
          "C < D", 
          ],
          verbose=0,
          answer="(A, B, C, D)",
          env=dict(mini=mini, maxi=maxi), # external functions
          digits=irange(1, 9),
      )
      
      # Solve and print answer
      print("Answer")
      for (_, ans) in p.solve(): 
        print(ans)
      
      
      # Output:
      #
      # Answer
      # (1, 6, 7, 8)
      

      Like

      • Frits's avatar

        Frits 11:59 pm on 15 September 2020 Permalink | Reply

        Also possible is to determine the smallest and largest possible rectangle in one function/loop.

        Like

      • Jim Randell's avatar

        Jim Randell 2:15 pm on 16 September 2020 Permalink | Reply

        @Frits: There’s no need to turn the digits into strings and back.

        Here’s a version that just operates on the digits directly:

        from enigma import (SubstitutedExpression, irange, subsets, nconcat)
         
        # determine smallest possible rectangle 
        def mini(A, B, C, D):
          minimum = 9999
          for s in subsets((A, B, C, D), size=4, select="P"):
            area = nconcat(s[:2]) * nconcat(s[2:])
            minimum = min(minimum, area)
          return minimum  
           
        # determine larges possible rectangle
        def maxi(A, B, C, D):
          maximum = 0
          for s in subsets((A, B, C, D), size=4, select="P"):
            area = nconcat(s[:2]) * nconcat(s[2:])
            maximum = max(maximum, area)
          return maximum   
         
        p = SubstitutedExpression([
              "(maxi(A, B, C, D) - mini(A, B, C, D)) = 5000",
              # only report a solution once
              "A < B",
              "B < C", 
              "C < D", 
            ],
            answer="(A, B, C, D)",
            env=dict(mini=mini, maxi=maxi), # external functions
            digits=irange(1, 9),
        )
         
        # Solve and print answer
        p.run(verbose=16)
        

        And you could use Python’s argument syntax to deal with the list of digits without unpacking them:

        def mini(*ds):
          ...
          for s in subsets(ds, size=4, select="P"):
            ...
        

        Like

    • Frits's avatar

      Frits 3:21 pm on 16 September 2020 Permalink | Reply

      Thanks.

      I have seen the (*ds) before and have done some investigation but it is not yet in my “tool bag”.

      Like

  • Unknown's avatar

    Jim Randell 3:01 pm on 13 September 2020 Permalink | Reply
    Tags:   

    Teaser 2731: City snarl-up 

    From The Sunday Times, 25th January 2015 [link]

    I had to drive the six miles from my home into the city centre. The first mile was completed at a steady whole number of miles per hour (and not exceeding the 20mph speed limit). Then each succeeding mile was completed at a lower steady speed than the previous mile, again at a whole number of miles per hour.

    After two miles of the journey my average speed had been a whole number of miles per hour, and indeed the same was true after three miles, after four miles, after five miles, and at the end of my journey.

    How long did the journey take?

    [teaser2731]

     
    • Jim Randell's avatar

      Jim Randell 3:02 pm on 13 September 2020 Permalink | Reply

      My first program looked at all sequences of 6 decreasing speeds in the range 1 to 20 mph. It was short but took 474ms to run.

      But it’s not much more work to write a recursive program that checks the average speeds as it builds up the sequence.

      This Python 3 program runs in 50ms.

      Run: [ @repl.it ]

      from enigma import (Rational, irange, as_int, catch, printf)
      
      Q = Rational()  # select a rational implementation
      
      # check speeds for distance d, give a whole number average
      check = lambda d, vs: catch(as_int, Q(d, sum(Q(1, v) for v in vs)))
      
      # solve for k decreasing speeds, speed limit m
      def solve(k, m, vs=[]):
        n = len(vs)
        # are we done?
        if n == k:
          yield vs
        else:
          # add a new speed
          for v in irange(m, 1, step=-1):
            vs_ = vs + [v]
            if n < 1 or check(n + 1, vs_):
              yield from solve(k, v - 1, vs_)
      
      # find a set of 6 speeds, not exceeding 20mph
      for vs in solve(6, 20):
        # calculate journey time (= dist / avg speed)
        t = Q(6, check(6, vs))
        printf("speeds = {vs} -> time = {t} hours")
      

      Solution: The journey took 2 hours in total.

      The speeds for each mile are: 20mph, 12mph, 6mph, 5mph, 2mph, 1mph.

      Giving the following times for each mile:

      mile 1: 3 minutes
      mile 2: 5 minutes
      mile 3: 10 minutes
      mile 4: 12 minutes
      mile 5: 30 minutes
      mile 6: 60 minutes
      total: 120 minutes

      And the following overall average speeds:

      mile 1: 20 mph
      mile 2: 15 mph
      mile 3: 10 mph
      mile 4: 8 mph
      mile 5: 5 mph
      mile 6: 3 mph

      Like

    • Frits's avatar

      Frits 1:40 pm on 16 September 2020 Permalink | Reply

      With a decent elapsed time.

       
      from enigma import SubstitutedExpression 
      
      # is average speed a whole number
      def avg_int(li):
        miles = len(li)
        time = 0
        # Add times (1 mile / speed)
        for i in range(miles):
          time += 1 / li[i]
        #return miles % time == 0
        return (miles / time).is_integer()
      
      p = SubstitutedExpression([
          "AB <= 20",
          # no zero speeds allowed
          "KL > 0",
          # speeds are descending
          "AB > CD",
          "CD > EF",
          "EF > GH", 
          "GH > IJ",
          "IJ > KL",
          # average speeds must be whole numbers
          "avg_int([AB, CD])",
          "avg_int([AB, CD, EF])",
          "avg_int([AB, CD, EF, GH])",
          "avg_int([AB, CD, EF, GH, IJ])",
          "avg_int([AB, CD, EF, GH, IJ, KL])",
          ],
          verbose=0,
          answer="(AB, CD, EF, GH, IJ, KL, \
                  60/AB + 60/CD + 60/EF + 60/GH + 60/IJ + 60/KL)",
          env=dict(avg_int=avg_int), # external functions
          d2i="",            # allow number respresentations to start with 0
          distinct="",       # letters may have same values                     
      )
      
      # Solve and print answer
      for (_, ans) in p.solve(): 
        print("Speeds:", ", ".join(str(x) for x in ans[:-1]))
        print(f"Minutes: {round(ans[6])}")  
      
      # Output:
      #
      # Speeds: 20, 12, 6, 5, 2, 1
      # Minutes: 120
      

      @Jim, do you discourage the use of / for division?

      Is there a better way to check that a division by a float is a whole number?
      I tried divmod(p, r) but sometimes r is close to zero like x E-19.
      (miles % time == 0) also failed.

      Like

      • Jim Randell's avatar

        Jim Randell 2:39 pm on 16 September 2020 Permalink | Reply

        @Frits

        I am careful about my use of / for division, as it behaves differently in Python 2 and Python 3 (and I do generally still attempt to write code that works in both Python 2 and Python 3 – although the day when Python 2 is abandoned completely seems to be getting closer).

        But I do tend to avoid using float() objects unless I have to. Floating point numbers are only ever approximations to a real number, so you have to allow some tolerance when you compare them, and rounding errors can build up in complex expressions. (Python recently added math.isclose() to help with comparing floats).

        On the other hand, I am a big fan of the Python fractions module, which can represent rational numbers and operate on them to give exact answers. So if I can’t keep a problem within the integers (which Python also will represent exactly) I tend to use fraction.Fraction if possible.

        (The only problem is that it is a bit slower, but for applications that need more speed gmpy2.mpq() can be used, which gives a faster implementation of rationals. I might add a Rational() function to enigma.py to search for an appropriate rational implementation).

        Like

        • Jim Randell's avatar

          Jim Randell 4:46 pm on 16 September 2020 Permalink | Reply

          I have added code to enigma.py to allow selecting of a class for rational numbers.

          So now instead of using:

          from fractions import Fraction as Q
          

          You can import the function Rational() from enigma.py and then use it to select a rational implementation:

          from enigma import Rational
          
          Q = Rational()  # or ...
          Q = Rational(verbose=1)
          

          Setting the verbose parameter will make it display which implementation is being used.

          I’ve got gmpy2 installed on my system and this change improved the runtime of my original code from 448ms (using fractions.Fraction under PyPy 7.3.1) to 153ms (using gmpy2.mpq under CPython 2.7.18).

          Like

  • Unknown's avatar

    Jim Randell 4:44 pm on 11 September 2020 Permalink | Reply
    Tags:   

    Teaser 3025: Please mind the gap 

    From The Sunday Times, 13th September 2020 [link] [link]

    Ann, Beth and Chad start running clockwise around a 400m running track. They run at a constant speed, starting at the same time and from the same point; ignore any extra distance run during overtaking.

    Ann is the slowest, running at a whole number speed below 10 m/s, with Beth running exactly 42% faster than Ann, and Chad running the fastest at an exact percentage faster than Ann (but less than twice her speed).

    After 4625 seconds, one runner is 85m clockwise around the track from another runner, who is in turn 85m clockwise around the track from the third runner.

    They decide to continue running until gaps of 90m separate them, irrespective of which order they are then in.

    For how long in total do they run (in seconds)?

    [teaser3025]

     
    • Jim Randell's avatar

      Jim Randell 5:28 pm on 11 September 2020 Permalink | Reply

      For the distances involved they must be very serious runners.

      I amused myself by writing a generic function to check the runners are evenly spaced. Although for the puzzle itself it is possible to use a simpler formulation that does not always produce the correct result. But once you’ve got that sorted out the rest of the puzzle is straightforward.

      This Python program runs in 55ms.

      Run: [ @replit ]

      from enigma import (irange, div, tuples, printf)
      
      # one lap of the track is 400m
      L = 400
      
      # time for separation of 85m
      T = 4625
      
      # check for equal separations
      def check_sep(ps, v):
        k = len(ps) - 1
        # calculate the remaining gap
        g = L - k * v
        if not (k > 0 and g > 0): return False
        # calculate positions of the runners
        ps = sorted(p % L for p in ps)
        # calculate the distances between them
        ds = list((b - a) % L for (a, b) in tuples(ps, 2, circular=1))
        # look for equal spacings
        return (g in ds and ds.count(v) >= k)
      
      # consider speeds for A
      for A in irange(1, 9):
        # A's distance after T seconds
        dA = A * T
        # B's distance after T seconds
        dB = div(dA * 142, 100)
        if dB is None: continue
        # A and B are separated by 85m or 170m
        if not (check_sep((dA, dB), 85) or check_sep((dA, dB), 170)): continue
      
        # now choose a whole number percentage increase on A
        for x in irange(143, 199):
          # C's distance after T seconds
          dC = div(dA * x, 100)
          if dC is None: continue
          # A, B, C are separated by 85m
          if not check_sep((dA, dB, dC), 85): continue
          printf("A={A} -> dA={dA} dB={dB}; x={x} dC={dC}")
      
          # continue until separation is 90m
          for t in irange(T + 1, 86400):
            dA = A * t
            dB = div(dA * 142, 100)
            dC = div(dA * x, 100)
            if dB is None or dC is None: continue
            if not check_sep((dA, dB, dC), 90): continue
            # output solution
            printf("-> t={t}; dA={dA} dB={dB} dC={dC}")
            break
      

      Solution: They run for 7250 seconds (= 2 hours, 50 seconds).

      After which time A will have covered 29 km (about 18 miles), B will have covered 41.2 km (about 25.6 miles), and C (who runs at a speed 161% that of A) will have covered 46.7 km (about 29 miles), which is pretty impressive for just over 2 hours.

      For comparison, Mo Farah on his recent world record breaking run of 21,330 m in 1 hour [link], would not have been able to keep up with C (who maintained a faster pace for just over 2 hours), and would be only slightly ahead of B at the 1 hour mark.

      Like

  • Unknown's avatar

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

    Teaser 2730: It’s a lottery 

    From The Sunday Times, 18th January 2015 [link]

    I regularly entered the Lottery, choosing six numbers from 1 to 49. Often some of the numbers drawn were mine but with their digits in reverse order. So I now make two entries: the first entry consists of six two-figure numbers with no zeros involved, and the second entry consists of six entirely different numbers formed by reversing the numbers in the first entry. Interestingly, the sum of the six numbers in each entry is the same, and each entry contains just two consecutive numbers.

    What (in increasing order) are the six numbers in the entry that contains the highest number?

    [teaser2730]

     
    • Jim Randell's avatar

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

      Each digit in the 2-digit numbers must form a tens digit of one of the 2-digit lottery numbers, so the digits are restricted to 1, 2, 3, 4, and no number may use two copies of the same digit.

      The following Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, intersect, tuples, printf)
      
      # generate (number, reverse) pairs
      ps = list((10 * a + b, 10 * b + a) for (a, b) in subsets(irange(1, 4), size=2, select="P"))
      
      # count consecutive pairs of numbers
      cons = lambda s: sum(y == x + 1 for (x, y) in tuples(s, 2))
      
      # find 6-sets of pairs
      for ss in subsets(ps, size=6):
        # split the pairs into numbers, and reverse numbers
        (ns, rs) = zip(*ss)
        rs = sorted(rs)
        # ns has the highest number
        if not (ns[-1] > rs[-1]): continue
        # sequences have the same sum
        if sum(ns) != sum(rs): continue
        # sequences have no numbers in common
        if intersect((ns, rs)): continue
        # sequences have exactly one consecutive pair
        if not (cons(ns) == 1 and cons(rs) == 1): continue
        # output solution
        printf("{ns}; {rs}", rs=tuple(rs))
      

      Solution: The six numbers are: 13, 21, 23, 24, 41, 43.

      23 and 24 are consecutive.

      Which means the reversed numbers (in order) are: 12, 14, 31, 32, 34, 42.

      31 and 32 are consecutive.

      Like

      • Frits's avatar

        Frits 6:11 pm on 10 September 2020 Permalink | Reply

        As all 12 numbers must be different we can reduce the main loop from 924 to 64 iterations.
        We can’t pick more than one item from each of the 6 groups.

        There probably is a more elegant way to create the list sixgroups.

        Using function seq_all_different instead of intersect doesn’t seem to matter.

         
        from enigma import subsets, irange, printf, seq_all_different
        from itertools import product
        
        # group 1:  (12, 21) (21, 12)
        # group 2:  (13, 31) (31, 13)
        # group 3:  (14, 41) (41, 14)
        # group 4:  (23, 32) (32, 23)
        # group 5:  (24, 42) (42, 24)
        # group 6:  (34, 43) (43, 34)
        
        sixgroups = [[]]*6
        i = 0
        for a in range(1,4):
          for b in range(a+1,5):
            sixgroups[i] = [((10 * a + b, 10 * b + a))]
            sixgroups[i].append((10 * b + a, 10 * a + b))
            i += 1
        
        # pick one from each group
        sel = [p for p in product(*sixgroups)]
        
        # count consecutive pairs of numbers
        cons = lambda s: sum(y == x + 1 for (x, y) in list(zip(s, s[1:])))
         
        # find 6-sets of pairs
        for ss in sel:
          # split the pairs into numbers, and reverse numbers
          (ns, rs) = zip(*ss)
          ns = sorted(ns)
          rs = sorted(rs)
          # ns has the highest number
          if not(ns[-1] > rs[-1]): continue
          # sequences have the same sum
          if sum(ns) != sum(rs): continue
          # sequences have no numbers in common
          if not seq_all_different(ns + rs): continue
          # sequences have exactly one consective pair
          if not(cons(ns) == 1 and cons(rs) == 1): continue
          # output solution
          printf("{ns}; {rs}", rs=tuple(rs))
        

        Like

        • Jim Randell's avatar

          Jim Randell 9:25 pm on 10 September 2020 Permalink | Reply

          For this particular puzzle (where all 12 possible numbers are used) we can go down to 32 pairs of sequences, as we always need to put the largest number (43) into the first sequence (and so 34 goes in the second sequence, and we don’t need to check the sequences have no numbers in common).

          Here’s my way of dividing the 12 candidate digit pairs into the 32 pairs of sequences:

          from enigma import (subsets, irange, nconcat, tuples, printf)
          
          # possible digit pairs
          ps = list(subsets(irange(1, 4), size=2))
          
          # construct numbers from digit pairs <ps> and flags <fs> (and xor flag <x>)
          def _construct(ps, fs, x=0):
            for ((a, b), f) in zip(ps, fs):
              yield (nconcat(a, b) if f ^ x else nconcat(b, a))
          construct = lambda *args: sorted(_construct(*args))
          
          # count consecutive pairs of numbers
          cons = lambda s: sum(y == x + 1 for (x, y) in tuples(s, 2))
          
          # choose an order to assemble digits
          for fs in subsets((0, 1), size=5, select="M"):
            fs += (0,)
            ns = construct(ps, fs, 0)
            rs = construct(ps, fs, 1)
            # sequences have the same sum
            if sum(ns) != sum(rs): continue
            # sequences have exactly one consecutive pair
            if not (cons(ns) == 1 and cons(rs) == 1): continue
            # output solution
            printf("{ns}; {rs}")
          

          And as all the numbers are used, and their sum is 330, we know each of the sequences must sum to 165.

          The more analysis we do, the less work there is for a program to do.

          Like

          • Frits's avatar

            Frits 5:11 pm on 11 September 2020 Permalink | Reply

            We can go on like this.

            fi, three consecutive numbers, we can exclude 111…, 000… and ..0.00 from fs and get it down from 34 to 18.

            12 13 14 23 24 34 Group A
            21 31 41 32 42 43 Group B
            -----------------
             9 18 27  9 18  9 Difference 
            

            Regarding the sum constraint we can derive that for each lottery entry from each group 2, 3 or 4 numbers have to picked.
            The numbers picked from group B must have a total difference of 45 (same for the numbers picked from group A)..

            If only 2 numbers are picked from a group then this group must be group B and the numbers {41, 42} or {41, 31}.

            Like

    • Frits's avatar

      Frits 11:47 am on 10 September 2020 Permalink | Reply

      Based on Jim’s cons lambda function.

       
      from enigma import SubstitutedExpression, irange, seq_all_different, tuples
      
      p = SubstitutedExpression([
          # sum of the six numbers in each entry is the same
          "AB + CD + EF + GH + IJ + KL == BA + DC + FE + HG + JI + LK",
          # ascending range
          "AB < CD", "CD < EF", "EF < GH", "GH < IJ", "IJ < KL",
          # all different numbers
          "diff([AB, CD, EF, GH, IJ, KL, BA, DC, FE, HG, JI, LK])",
          # each entry contains just two consecutive numbers
          "consec([AB, CD, EF, GH, IJ, KL]) == 1",
          "consec([BA, DC, FE, HG, JI, LK]) == 1",
          # report entry that contains the highest number
          "max(AB, CD, EF, GH, IJ, KL) >= max(BA, DC, FE, HG, JI, LK)",
         ],
          verbose=0,
          answer="AB, CD, EF, GH, IJ, KL",
          code="diff = lambda x: seq_all_different(x); \
                consec = lambda s: sum(y == x + 1 \
                                   for (x, y) in tuples(sorted(s), 2))",
          distinct="",
          digits=irange(1,4)
      )
      
      # Print answers
      for (_, ans) in p.solve():
        print(ans)  
      
      # Output:
      #
      # (13, 21, 23, 24, 41, 43)
      

      Like

      • Frits's avatar

        Frits 12:16 pm on 10 September 2020 Permalink | Reply

        list(zip(s, s[1:])) is the same as tuples(s, 2)

        Like

  • Unknown's avatar

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

    Brainteaser 1108: Field fare 

    From The Sunday Times, 30th October 1983 [link]

    The field near to our home is rectangular. Its longer sides run east to west. In the south-east corner there is a gate. In the the southern fence there is a stile a certain number of yards from the south-west corner. In the northern fence there is a stile, the same certain number of yards from the north-east corner. A straight path leads across the field from one stile to the other. Another straight path from the gate is at right angles to the first path and meets it at the old oak tree in the field.

    When our elder boy was ten, and his brother five years younger, they used to race from opposite ends of a long side of the field towards the stile in that as the target. But later, when they were a little less unevenly matched, they raced from opposite stiles towards the oak as the target. Of course the younger boy raced over the shorter distances. This change of track gave the younger boy 9 yards more and the elder boy 39 yards less than before to reach the target.

    The sides of the field and the distances of the oak tree from each of the stiles are all exact numbers of yards.

    How far apart are the two stiles, and how far is the oak tree from the gate?

    [teaser1108]

     
    • Jim Randell's avatar

      Jim Randell 8:09 am on 8 September 2020 Permalink | Reply

      If we suppose the north and south boundaries of the field are separated by a distance of a, and the stiles divide the these boundaries into sections with length x and y, and the distances from the tree to the stiles are b and c, and the distance from the tree to the corner is z. Then we have a layout like this:

      When the children were younger they would run distances x and y.

      And when they were older the distances are b and c.

      Hence:

      b = x + 9
      c = y − 39
      ⇒ y − x = c − b + 48

      The distance between the two stiles, (b + c) forms the hypotenuse of a Pythagorean triangle with other sides of (y − x) = (c − b + 48) and a (triangle: S1 Y S2).

      So we can consider Pythagorean triples for this triangle. One of the non-hypotenuse sides corresponds to a, and the other when added to the hypotenuse gives:

      (b + c) + (c − b + 48) = 2c + 48

      So choosing one of the sides for a, allows us to calculate the value for c, and then b, x, y, z.

      The distance G S2 is the hypotenuse of the two triangles (G T S2) and (G X S2), and we can check that these hypotenuses match.

      The following Python program runs in 58ms.

      Run: [ @replit ]

      from enigma import (pythagorean_triples, div, sqrt, printf)
      
      # consider pythagorean triples corresponding to (a, c - b + 48, b + c)
      
      for (X, Y, Z) in pythagorean_triples(1000):
        # choose a side for a
        for (a, other) in [(X, Y), (Y, X)]:
          # and: other + Z = 2c + 48
          c = div(other + Z - 48, 2)
          if c is None or c < 1: continue
          b = Z - c
          x = b - 9
          y = c + 39
          if not (0 < b < c and 0 < x < y): continue
          # calculate z^2
          z2 = y * y - c * c
          if not (b * b + z2 == a * a + x * x): continue
          # output solution
          printf("({X}, {Y}, {Z}) -> a={a} b={b} c={c}, x={x} y={y} z={z:g}", z=sqrt(z2))
      

      Solution: The stiles are 200 yards apart. The oak tree is 117 yards from the gate.

      The short side of the field is 120 yards.

      The long side is 230 yards, and this is split into sections of 35 and 195 yards by the stiles.

      The oak tree is 44 yards from one stile and 156 yards from the other.


      This puzzle appeared in the 1986 book Microteasers (p 4) by Victor Bryant, in which he deals with solving puzzles using the BBC Micro. (See: [ @EnigmaticCode ]).

      Here is Victor Bryant’s BBC BASIC program (slightly modified by me to stop after the first solution is encountered, and print the run time for the program):

      >LIST
         10 REM Victor Bryant program for Teaser 1108
         15 start% = TIME
         20 FOR c = 26 TO 500  
         30   FOR b = 25 TO c - 1
         40     a = SQR((c + 39)^2 - c^2 + b^2 - (b - 9)^2)
         50     IF a <> INT(a) THEN 70
         60     IF a^2 <> (b + c)^2 - (c - b + 48)^2 THEN 70
         65     PRINT "Distances: stiles - tree = "; b; " and "; c; " & short side = "; a
         66     GOTO 85
         70   NEXT b
         80 NEXT c
         85 PRINT "(Time = "; (TIME - start%) / 100; "s)"
      
      >RUN
      Distances: stiles - tree = 44 and 156 & short side = 120
      (Time = 250.2s)
      
      >
      

      Here is a Python program using the same approach. It takes 65ms to find the first solution, or 137ms to run to completion. (On an emulated BBC Micro Victor’s program takes about an hour to run to completion).

      from enigma import (irange, subsets, is_square, printf)
      
      for (b, c) in subsets(irange(25, 500), size=2):
        a = is_square((c + 39)**2 - c**2 + b**2 - (b - 9)**2)
        if a is None: continue
        if a**2 + (c - b + 48)**2 != (b + c)**2 : continue
        printf("a={a} b={b} c={c}")
        break
      

      Comparing internal runtimes we find that my program runs in 2.4ms (to completion), and the port of Victor’s program in 17.3ms (to first solution; 219ms to completion). They search a similar solution space, (i.e. distance S1 S2 less than 1000 yards).

      Like

      • Jim Randell's avatar

        Jim Randell 3:08 pm on 14 September 2020 Permalink | Reply

        Here’s my analytical solution:

        We have:

        x = b − 9
        y = c + 39

        and

        y² = c² + z²
        (c + 39)² = c² + z²
        2.39.c + 39.39 = z²
        c = (z² − 39.39) / 2.39
        c = (z²/39 − 39)/2

        Now, c is an integer, so z²/39 is an odd integer:

        z²/39 = 2k + 1
        z² = 39(2k + 1)

        and:

        c = (2k + 1 − 39)/2
        c = k − 19

        and: c > b > 9, so k > 29.

        Also, we have:

        a² = (b + c)² − (c − b + 48)²
        a² = (b² + 2bc + c²) − (b² + c² − 2bc − 96b + 96c + 2304)
        a² = 4bc − 96b + 96c + 2304
        a² = 4(b − 24)(c + 24)
        a² = 4(b − 24)(k + 5)

        So: b > 24k > 44.

        And:

        b² + z² = a² + (b − 9)²
        b² + 39(2k + 1) = 4(b − 24)(k + 5) + (b² − 18b + 81)
        78k + 39 = 4bk + 20b − 96k − 480 − 18b + 81
        2b(2k + 1) = 174k + 438
        b = (87k + 219) / (2k + 1)

        As k → ∞, b → 87/2 = 43.5, so: b ≥ 44 and k ≤ 175.

        This gives us a limited range of values for k, which we can check:

        from enigma import (irange, div, is_square, sqrt, printf)
        
        # consider possible values for k
        for k in irange(45, 175):
          # calculate a, b, c, x, y, z
          c = k - 19
          b = div(87 * k + 219, 2 * k + 1)
          if b is None or not (0 < b < c): continue
          a = is_square(4 * (b - 24) * (c + 24))
          if a is None: continue
          x = b - 9
          y = c + 39
          z = sqrt(78 * k + 39)
          if not (0 < x < y): continue
          # output solution
          printf("[k={k}] a={a} b={b} c={c}; x={x} y={y} z={z:g}")
        

        Or we can narrow down the values with further analysis:

        Now:

        (b − 24) = 39(k + 5) / (2k + 1)

        and so:

        a² = [2(k + 5)]² (39 / (2k + 1))

        The value of (2k + 1) is in the range 91 to 351, so the (39 / (2k + 1)) part, must contribute pairs of factors (which divide into the pairs of factors provided by the [2(k + 5)]² part).

        So (2k + 1) is some odd square multiple of 39 (which also means that z must be an integer).

        The only option in the required range is:

        (2k + 1) = 39×3² = 351
        k = 175
        a = 120, b = 44, c = 156
        x = 35, y = 195, z = 117

        Like

    • John Crabtree's avatar

      John Crabtree 6:11 am on 9 September 2020 Permalink | Reply

      (b + c)^2 = a^2 +(y – x)^2
      a^2 + x^2 = b^2 +z^2
      y^2 = c^2 + z^2
      Adding the three equations and simplifying gives bc + xy = z^2
      But z^2 = 39(2y – 39) = (39k)^2
      And so y = 39(k^2 + 1)/2
      Using b = x + 9 and c = y – 39, leads to x = 69/2 + 9/(2.k^2)
      For integral solutions, either k = 1 and then x = y = z = 39 which is invalid,
      or k = 3, x = 35, y = 195 and z = 117, etc with a = 120 as a check

      Like

      • John Crabtree's avatar

        John Crabtree 4:08 am on 10 September 2020 Permalink | Reply

        I should have included: a = 39k + 9/k = z + 9/k

        Like

        • Frits's avatar

          Frits 10:37 am on 10 September 2020 Permalink | Reply

          Nice solution, I checked your equations.

          I think you have to proof k (and z?) is an integer
          before only only considering k=1 and k=3.

          fi, k = 3^0.5 would also lead to an integral solution for x.

          We only know for sure that a, b, c and x,y are natural numbers.

          Like

          • John Crabtree's avatar

            John Crabtree 4:02 pm on 11 September 2020 Permalink | Reply

            Frits, thank you. I had assumed that z was a whole number, which we are not told. We also need to consider a, =(2y -30)/k before determining possible values of k. y is a whole number, and so k must be rational, = n/m. But then for y to be a whole number, m= 1, and so k is an integer.
            I think that this makes the manual solution, which should exist, complete.

            Like

        • Frits's avatar

          Frits 11:31 pm on 12 September 2020 Permalink | Reply

          Hi John,

          Unfortunately some of your text was lost during formatting. I can follow the logic if (2y -30)/k equals an integer expression. What formula or variable did you intend to use as left hand side of ” (2y -30)/k”?

          I now used opening and closing pre tags for this section.

          Like

  • Unknown's avatar

    Jim Randell 4:06 pm on 6 September 2020 Permalink | Reply
    Tags:   

    Brain-Teaser 7: The Golden Wedding 

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

    Many years ago there lived in Addison Street four sisters, June, Alice, Mary and Dawn. In that street there lived also four young men. Harry, Graham, Richard and Tom. On a bright, spring day in 1911 each of the four sisters married the young man of her choice. Each of the four couples had children: June and Mary together had as many children as Alice.

    When all the children grew up and, in the course of time, married to become parents themselves each of them had the same number of children as there had been in his or her own family (for instance, had June borne five children, each of these five would have had five, too, and so on).

    Last week, the four original couples celebrated their Golden Wedding at a party attended by all their 170 grandchildren. Richard, who despite the fact that his was the smallest family had always been fascinated by numbers, pointed out that June and Alice together had as many grandchildren, as Mary and Dawn. He also noticed that Harry had four times as many grandchildren as Graham had children.

    Which sisters married which young men on that bright spring day in 1911?

    [teaser7]

     
    • Jim Randell's avatar

      Jim Randell 4:07 pm on 6 September 2020 Permalink | Reply

      The (children, grandchildren) pairs are of the form (n, n²).

      We know in total there are 170 grandchildren, and that (J and A) have as many grandchildren as (M and D). So each bracketed pair has 85 grandchildren in total.

      This Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (Record, irange, subsets, printf)
      
      # record for each sister: w=initial, c=children, g=grandchildren
      ss = (J, A, M, D) = tuple(Record(w=x) for x in "JAMD")
      
      # squares less than 85 (map: square -> root)
      sq = dict((n * n, n) for n in irange(0, 9))
      
      # find pairs with squares that sum to 85
      pairs = list(s for s in subsets(sq.keys(), size=2, select="M") if sum(s) == 85)
      
      # choose 4 values for the children/grandchildren of each sister
      for (x1, x2) in subsets(pairs, size=2, select="M"):
        xs = x1 + x2
        (J.g, A.g, M.g, D.g) = xs
        (J.c, A.c, M.c, D.c) = (sq[x] for x in xs) 
        # "J and M together have as many children as A"
        if not (J.c + M.c == A.c): continue
        # assign the sisters to husbands
        for (H, G, R, T) in subsets(ss, size=4, select="P"):
          # "R has the smallest family"
          if not (R.c == min(J.c, A.c, M.c, D.c)): continue
          # "H has 4 times as many grandchildren as G has children"
          if not (H.g == 4 * G.c): continue
          # output solution
          printf("H+{H.w}={H.c}+{H.g}; G+{G.w}={G.c}+{G.g}; R+{R.w}={R.c}+{R.g}; T+{T.w}={T.c}+{T.g}")
      

      Solution: The married couples are: Dawn & Harry; Alice & Graham; June & Richard; Mary & Tom.

      Dawn & Harry: 6 children; 36 grandchildren.
      Alice & Graham: 9 children; 81 grandchildren.
      June & Richard: 2 children; 4 grandchildren.
      Mary & Tom: 7 children; 49 grandchildren.

      Like

      • Jim Randell's avatar

        Jim Randell 9:52 pm on 6 September 2020 Permalink | Reply

        Manually we see that (J and A) and (M and D) both have 85 grandchildren, so they correspond to the 2 ways to represent 85 as the sum of 2 squares:

        85 = 2² + 9² = 6² + 7²

        Also (J and M) have as many children as A.

        So J has 2 children, M has 7 and A has 9, leaving D with 6.

        R has the smallest family. So:

        J & R have 2 children and 4 grandchildren.

        And H has 4 times as many grandchildren as G has children, so:

        D & H have 6 children and 36 grandchildren.
        A & G have 9 children and 81 grandchildren.

        Leaving:

        M & T have 7 children and 49 grandchildren.

        Like

    • Frits's avatar

      Frits 5:50 pm on 6 September 2020 Permalink | Reply

       
      from enigma import  SubstitutedExpression, irange, seq_all_same
      
      p = SubstitutedExpression([
          # each sister married a brother
          # so similar number of children for sisters and brothers
          "sorted([J,A,M,D]) == sorted([H,G,R,T])",
          
          # in total 170 grandchildren 
          "J*J + A*A + M*M + D*D == 170",
          "H*H + G*G + R*R + T*T == 170",
          
          #(J and A) have as many grandchildren as (M and D). 
          "J*J + A*A == M*M + D*D",
          
          # "J and M together have as many children as A"    
          "J + M = A",
          
          # "R has the smallest family"
          "min(H,G,R,T) == R", 
          
          # "H has 4 times as many grandchildren as G has children"
          "H*H == 4 * G"
         ],
          verbose=0,
          # example with 2 code functions
          answer="(J, A, M, D, H, G, R, T)",
          distinct="",
          digits=irange(1, 9),
      )
      
      sis = ['June', 'Alice', 'Mary', 'Dawn']
      bro = ['Harry', 'Graham', 'Richard',  'Tom']
      
      # Print answers
      for (_, ans) in p.solve():
        for i in range(4):
          for k in range(4,8):
            if ans[k] == ans[i]:
              print(f"{sis[i]:5} & {bro[k-4]:7}: \
      {ans[i]} children, {ans[i]*ans[i]} grandchildren")
        
      # Output:
      #
      # June  & Richard: 2 children, 4 grandchildren
      # Alice & Graham : 9 children, 81 grandchildren
      # Mary  & Tom    : 7 children, 49 grandchildren
      # Dawn  & Harry  : 6 children, 36 grandchildren
      

      Like

  • Unknown's avatar

    Jim Randell 4:42 pm on 4 September 2020 Permalink | Reply
    Tags:   

    Teaser 3024: Triple jump 

    From The Sunday Times, 6th September 2020 [link] [link]

    From a set of playing cards, Tessa took 24 cards consisting of three each of the aces, twos, threes, and so on up to the eights. She placed the cards face up in single row and decided to arrange them such that the three twos were each separated by two cards, the threes were separated by three cards and so forth up to and including the eights, duly separated by eight cards. The three aces were numbered with a one and were each separated by nine cards. Counting from the left, the seventh card in the row was a seven.

    In left to right order, what were the numbers on the first six cards?

    [teaser3024]

     
    • Jim Randell's avatar

      Jim Randell 5:03 pm on 4 September 2020 Permalink | Reply

      (See also: Enigma 369).

      I assume that for each set of three cards with value n (the aces have a value of 9): the leftmost one is separated from the middle one by n other cards, and the middle one is separated from the rightmost one by n other cards. (So the leftmost and rightmost instances are not separated by n cards).

      I treat the puzzle as if cards with values 2 to 9 were used (3 copies of each). Then when we have found a solution we can replace the value 9 cards with aces.

      This Python program runs in 50ms.

      Run: [ @repl.it ]

      from enigma import update, join, printf
      
      # generate sequences in <s> where 3 occurrences of the numbers in <ns> are
      # separated by <n> other slots
      def generate(s, ns):
        if not ns:
          yield s
        else:
          n = ns[0]
          for (i, x) in enumerate(s):
            j = i + n + 1
            k = j + n + 1
            if not(k < len(s)): break
            if not(x is None and s[j] is None and s[k] is None): continue
            yield from generate(update(s, [i, j, k], [n, n, n]), ns[1:])
      
      # start with 24 slots
      s = [None] * 24
      # we are told where the 7's are:
      s[6] = s[14] = s[22] = 7
      # place the remaining cards
      for ss in generate(s, [9, 8, 6, 5, 4, 3, 2]):
        # output solution
        printf("{ss}", ss=join(("??23456781"[x] for x in ss), sep=" "))
      

      Solution: The first 6 cards are: 1, 2, 6, 8, 2, 5.

      Without pre-placing the 7’s there are four sequences that work (or two sequences, and their reverses):

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

      The answer to the puzzle is the first of these sequences.

      Like

      • Jim Randell's avatar

        Jim Randell 11:15 am on 5 September 2020 Permalink | Reply

        Here is a MiniZinc model to solve the puzzle:

        %#! minizinc -a
        
        include "globals.mzn";
        
        % index of middle cards with values 2 to 9
        var 11..14: i9;
        var 10..15: i8;
        var  9..16: i7;
        var  8..17: i6;
        var  7..18: i5;
        var  6..19: i4;
        var  5..20: i3;
        var  4..21: i2;
        
        % each card is in a different slot
        constraint all_different([
          i9, i9 - 10, i9 + 10,
          i8, i8 - 9, i8 + 9,
          i7, i7 - 8, i7 + 8,
          i6, i6 - 7, i6 + 7,
          i5, i5 - 6, i5 + 6,
          i4, i4 - 5, i4 + 5,
          i3, i3 - 4, i3 + 4,
          i2, i2 - 3, i2 + 3,
        ]);
        
        % there is a 7 in slot 7
        constraint i7 - 8 = 7;
        
        solve satisfy;
        

        Like

        • Jim Randell's avatar

          Jim Randell 4:32 pm on 5 September 2020 Permalink | Reply

          And here’s an alternative implementation in Python that collects the indices of the central cards of each value, rather than filling out the slots:

          from enigma import irange, update, join, printf
          
          # generate indices for the central number in <ns> where the 3
          # occurrences are separated by <n> other slots
          def solve(ns, m=dict(), used=set()):
            if not ns:
              yield m
            else:
              n = ns[0]
              # consider possible indices for the middle card with value n
              d = n + 1
              for i in irange(d, 23 - d):
                (j, k) = (i - d, i + d)
                if not used.intersection((i, j, k)):
                  yield from solve(ns[1:], update(m, [(n, i)]), used.union((i, j, k)))
          
          # place 7 (at 6, 14, 22), and solve for the remaining cards
          for m in solve([9, 8, 6, 5, 4, 3, 2], { 7: 14 }, set([6, 14, 22])):
            # construct the solution sequence
            s = [0] * 24
            for (n, i) in m.items():
              d = n + 1
              s[i] = s[i - d] = s[i + d] = (1 if n == 9 else n)
            # output solution
            printf("{s}", s=join(s, sep=" "))
          

          Like

    • Frits's avatar

      Frits 8:57 pm on 5 September 2020 Permalink | Reply

      Based on Jim’s MiniZinc model.

      Only the last”v” call is necessary, the rest is added for performance., it’s a bit messy.

       
      from enigma import SubstitutedExpression, irange, seq_all_different
      
      p = SubstitutedExpression([
          "K < 3",
          "M < 3",
          "O < 3",
          "O > 0",
          "A < 3 and AB > 3 and AB < 22",       # i2
          "C < 3 and CD > 4 and CD < 21",       # i3
          "E < 3 and EF > 5 and EF < 20",       # i4
          "G < 3 and GH > 6 and GH < 19",       # i5
          "IJ > 7 and IJ < 18",                 # i6
          "KL = 15",                            # i7
          "MN > 9 and MN < 16",                 # i8
          "OP > 10 and OP < 15",                # i9
          "v([KL, KL - 8, KL + 8, \
              MN, MN - 9, MN + 9])",
          "v([KL, KL - 8, KL + 8, \
              MN, MN - 9, MN + 9, \
              OP, OP - 10, OP + 10])",
          "v([AB, AB - 3, AB + 3, \
              KL, KL - 8, KL + 8, \
              MN, MN - 9, MN + 9, \
              OP, OP - 10, OP + 10])",
          "v([AB, AB - 3, AB + 3, \
              CD, CD - 4, CD + 4, \
              KL, KL - 8, KL + 8, \
              MN, MN - 9, MN + 9, \
              OP, OP - 10, OP + 10])",
          "v([AB, AB - 3, AB + 3, \
              CD, CD - 4, CD + 4, \
              EF, EF - 5, EF + 5, \
              KL, KL - 8, KL + 8, \
              MN, MN - 9, MN + 9, \
              OP, OP - 10, OP + 10])",
          "v([AB, AB - 3, AB + 3, \
              CD, CD - 4, CD + 4, \
              EF, EF - 5, EF + 5, \
              GH, GH - 6, GH + 6, \
              IJ, IJ - 7, IJ + 7, \
              KL, KL - 8, KL + 8, \
              MN, MN - 9, MN + 9, \
              OP, OP - 10, OP + 10])",
         ],
          verbose=0,
          d2i="",          # allow leading zeroes
          code="v = lambda x: seq_all_different(x)",
          answer="(AB, CD, EF, GH, IJ, KL, MN, OP)",
          distinct="",
      )
      
      out = [2, 3, 4, 5, 6, 7, 8, 1]
      
      # Print answers
      for (_, ans) in p.solve(verbose=0):
        print("Numbers on first 6 cards: ", end="")
        for k in range(1,7):
          for i in range(8):
            if ans[i] == k: print(f"{out[i]} ", end="")
            if ans[i] - i - 3 == k: print(f"{out[i]} ", end="")
        print()
      

      Like

  • Unknown's avatar

    Jim Randell 8:53 am on 3 September 2020 Permalink | Reply
    Tags:   

    Teaser 2741: Neater Easter Teaser 

    From The Sunday Times, 5th April 2015 [link] [link]

    In my latest effort to produce a neater Easter Teaser I have once again consistently replaced each of the digits by a different letter. In this way:

    NEATER
    LATEST
    EASTER
    TEASER

    represent four six-figure numbers in increasing order.

    Furthermore, the following addition sum is correct:

    What is the value of BONNET?

    [teaser2741]

     
    • Jim Randell's avatar

      Jim Randell 8:54 am on 3 September 2020 Permalink | Reply

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

      The following run file executes in 249ms.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression "FLORAL + EASTER = BONNET" "N < L < E < T"
      

      Solution: BONNET = 961178.


      Or for a faster solution we can use a 1-line Python program that uses the [[ SubstitutedSum ]] solver from the enigma.py library:

      from enigma import SubstitutedSum
      
      SubstitutedSum(['FLORAL', 'EASTER'], 'BONNET').go(lambda s: s['N'] < s['L'] < s['E'] < s['T'])
      

      Like

      • Jim Randell's avatar

        Jim Randell 10:05 am on 28 September 2022 Permalink | Reply

        For an even faster solution we can use the [[ SubstitutedExpression.split_sum ]] solver.

        The following run file executes in 72ms. And the internal run time of the generated program is 234µs).

        Run: [ @replit ]

        #! python3 -m enigma -rr
        
        SubstitutedExpression.split_sum
        
        "FLORAL + EASTER = BONNET"
        
        --extra="N < L < E < T"
        --answer="BONNET"
        

        Like

    • Frits's avatar

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

      Another Python constraint solver.

      Maybe this is not the most efficient way to do it as it takes 3 seconds.

       
      # https://pypi.org/project/python-constraint/
      
      from constraint import Problem, AllDifferentConstraint
      from functools import reduce
      
      to_num = lambda seq: reduce(lambda x, y: 10 * x + y, seq)
      
      # "FLORAL + EASTER = BONNET" "N < L < E < T"
      
      letters = "FLORAESTBN"
      
      problem = Problem()
      
      for x in letters:
        problem.addVariable(x, range(0,10))
      
      problem.addConstraint(AllDifferentConstraint())
      
      problem.addConstraint(lambda a, b, c, d: a < b and b < c and c < d,
                            ("N", "L", "E", "T"))
                            
      problem.addConstraint(lambda F,L,O,R,A,E,S,T,B,N: to_num([F,L,O,R,A,L]) +
                            to_num([E,A,S,T,E,R]) == to_num([B,O,N,N,E,T]),
                            ("F","L","O","R","A","E","S","T","B","N"))
                            
      for ans in problem.getSolutions():
        print(f"{ans['B']}{ans['O']}{ans['N']}{ans['N']}{ans['E']}{ans['T']}")
      

      Like

      • Frits's avatar

        Frits 11:03 am on 12 October 2020 Permalink | Reply

        A better model to use for future puzzles.

        “N < L" "L < E" "E < T" is a little bit faster than "N < L < E < T"

        # https://pypi.org/project/python-constraint/
        
        from constraint import Problem, AllDifferentConstraint
        from functools import reduce
        from re import findall
        
        # generate numerical expression   inp: A,B,C  output: A*100+B*10+C
        expa = lambda w: ''.join(w[i]+'*'+str(10**(len(w)-i-1))+'+'
                         for i in range(len(w)-1))+w[-1]
        
        # variables in constraint have to be uppercase
        def addC(constraint):
          vars = set(findall('([A-Z])', constraint))
          nbrs = set(findall('([A-Z]+)', constraint))
          
          for n in nbrs:
            constraint = constraint.replace(str(n), expa(n))
          
          parms = "lambda "+', '.join(vars) + ': ' + constraint + \
                  ', ("' + '", "'.join(vars) + '")'
          
          exec("p.addConstraint("+parms+")")  
          
        def report(ans, vars):
          print(f"{vars} = ", end="")
          for x in vars:
            print(f"{ans[x]}", end="")
          print()  
         
        p = Problem()
        
        p.addVariables(("LORASTN"), range(0,10))  
        p.addVariables(("FEB"), range(1,10))  
        
        p.addConstraint(AllDifferentConstraint())
        
        addC("N < L") 
        addC("L < E") 
        addC("E < T")         
        addC("FLORAL + EASTER == BONNET")
        
        for ans in p.getSolutions():
          report(ans, "BONNET")
        

        Like

    • GeoffR's avatar

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

      
      % A Solution in MiniZinc
      include "alldifferent.mzn";
      
      var 0..9: N; var 1..9: E; var 0..9: A;
      var 0..9: T; var 0..9: L; var 0..9: S;
      var 0..9: R; var 1..9: B; var 0..9: O;
      var 1..9: F;
      
      constraint alldifferent ([N,E,A,T,L,S,R,B,O,F]);
      
      constraint 
        (L + A*10 + R*100 + O*1000 + L*10000 + F*100000)
      + (R + E*10 + T*100 + S*1000 + A*10000 + E*100000)
      = (T + E*10 + N*100 + N*1000 + O*10000 + B*100000) ;
      
      solve satisfy;
      
      output["BONNET = " ++ show(T + E*10 + N*100 + N*1000 + O*10000 + B*100000)];
      
      % Answer: 961178
      % time elapsed: 0.05s
      % ----------
      % ==========
      % time elapsed: 0.18 s
      % Finished in 436msec
      
      
      

      A standard MiniZinc solution produced a reasonable run-time, but what is the correct run-time to quote in a posting
      :
      a) time to print first solution?
      b) time elapsed ?
      c) Finished time?

      I like to think it is the time it takes to print out the first time quoted!

      Like

      • Jim Randell's avatar

        Jim Randell 12:43 pm on 12 October 2020 Permalink | Reply

        @GeoffR:

        When I report the timings of my programs I use the complete execution time of the command that runs the program presented.

        So I hardly ever report the time taken to find the first solution, as I am usually more interested in the time taken to exhaustively search the solution space and find all possible solutions. (The exception being if the program is only looking for the first answer it finds, and terminates once it is found).

        I can collect this data using the time builtin of my shell which reports the total runtime of a command. I perform multiple executions, and take the shortest time. I call this the “external runtime”.

        For example comparing my SubstitutedSum() based solution above against a MiniZinc model (I added in a constraint to ensure N < L < E < T) I get:

        % time python3.9 teaser2741.py
        python3.9 teaser2741.py  0.04s user 0.01s system 91% cpu 0.055 total
        
        % time minizinc -a teaser2741geoff.mzn
        minizinc -a teaser2741geoff.mzn  0.07s user 0.02s system 96% cpu 0.095 total
        

        So I would report a runtime of 55ms for the Python program, and 95ms for the MiniZinc model.

        This lets me compare programs using different languages, but makes it less easy to compare programs in the same language that run very quickly. For example on my system run a Python program that consists of the single statement pass, takes between 30ms and 50ms (depending on the version of Python). So to compare Python programs that take less than 50ms I sometimes use the “internal runtime”. I do this by using the [[ Timer ]] class from the enigma.py library, putting a [[ timer.start() ]] statement before the first statement of the program, and a [[ timer.stop() ]] statement at the end. This removes some of the overhead involved in using Python, but means the values are not necessary useful when comparing with non-Python programs, and each language will have a different way of reporting internal runtimes.

        For example, the internal runtime of the Python program above is 13.65ms.

        MiniZinc has a --statistics option which reports: solveTime=0.002176, so it may have an internal runtime of 2.176ms.

        Like

      • Frits's avatar

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

        @GeoffR, I only quote timings if I think the program runs (too) slow. Elapsed times more than one second (PyPy) seem suspicous to me for relatively simple puzzles.

        Normally a) is not important to me for Python programs as you might be lucky to get a solution quickly.

        I use enigma.run(“xxxx.py”, timed=1) for Python programs (normally running with PyPy).

        Unfortunately I was not able to used function toNum in the output statement.

        % A Solution in MiniZinc
        include "alldifferent.mzn";
         
        var 0..9: N; var 1..9: E; var 0..9: A;
        var 0..9: T; var 0..9: L; var 0..9: S;
        var 0..9: R; var 1..9: B; var 0..9: O;
        var 1..9: F;
        
        var 102234..987765: bonnet;
         
        function var int: toNum(array[int] of var int: a) =
                  let { int: len = length(a) }
                  in
                  sum(i in 1..len) (
                    ceil(pow(int2float(10), int2float(len-i))) * a[i]
                  );
        
        constraint alldifferent ([N,E,A,T,L,S,R,B,O,F]);
        
        constraint bonnet == toNum([B, O, N, N, E, T]);
        constraint toNum([F, L, O, R, A, L]) + toNum([E, A, S, T, E, R]) == bonnet;
           
        solve satisfy;
        output["BONNET = " ++ show(bonnet)];
         
        % Answer: 961178
        

        Like

  • Unknown's avatar

    Jim Randell 8:31 am on 1 September 2020 Permalink | Reply
    Tags:   

    Teaser 1885: Sacred sign 

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

    The “Sacred Sign of Solomon” consists of a pentagon whose vertices lie on a circle, roughly as shown:

    Starting at one of the angles and going round the circle the number of degrees in the five angles of the large pentagon form an arithmetic progression; i.e., the increase from the first to the second equals the increase from the second to the third, etc.

    As you can see, the diagonals form a small inner pentagon and in the case of the sacred sign one of its angles is a right angle.

    What are the five angles of the larger pentagon?

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

    [teaser1885]

     
    • Jim Randell's avatar

      Jim Randell 8:32 am on 1 September 2020 Permalink | Reply

      If the five sides of the pentagon are A, B, C, D, E, then the angles subtended by each side at the circumference of the circle are all equal, say, a, b, c, d, e.

      (The angles in the small pentagon, such as ace denote the sum of the three symbols, i.e. (a + c + e) etc).

      We see that: a + b + c + d + e = 180° (from many triangles, cyclic quadrilaterals, and the angles subtended at the centre of the circle).

      The internal angles of the pentagon are an arithmetic progression, say: (x − 2y, x − y, x, x + y, x + 2y).

      So, let’s say:

      a + b + e = x − 2y
      a + b + c = x − y
      b + c + d = x
      c + d + e = x + y
      a + d + e = x + 2y

      And these angles sum to 540°.

      5x = 540°
      x = 108°

      And if: x = b + c + d = 108°, then: a + e = 72°

      So starting with: a + d + e = x + 2y, we get:

      72° + d = 108° + 2y
      d = 36° + 2y

      Similarly we express each of the angles a, b, c, d, e in terms of y:

      a = 36° + y
      b = 36° − 2y
      c = 36°
      d = 36° + 2y
      e = 36° − y

      The internal angles of the small pentagon are:

      a + c + e = 108°
      a + b + d = 108° + y
      b + c + e = 108° − 3y
      a + c + d = 108° + 3y
      b + d + e = 108° − y

      And one of these is 90°.

      Our candidates are (b + c + e) and (b + d + e).

      b + c + e = 90° ⇒ y = 6°
      a = 42°
      b = 24°
      c = 36°
      d = 48°
      e = 30°

      And the angles of the large pentagon are then:

      a + b + e = 96°
      a + b + c = 102°
      b + c + d = 108°
      c + d + e = 114°
      a + d + e = 120°

      For the other candidate:

      b + d + e = 90° ⇒ y = 18°
      a = 54°
      b = 0°
      c = 36°
      d = 72°
      e = 18°

      This is not a viable solution, so:

      Solution: The angles of the larger pentagon are: 96°, 102°, 108°, 114°, 120°.

      Like

      • Jim Randell's avatar

        Jim Randell 11:54 am on 1 September 2020 Permalink | Reply

        Here’s a drawing of the symbol, with the two perpendicular lines indicated:

        Like

  • Unknown's avatar

    Jim Randell 8:45 am on 30 August 2020 Permalink | Reply
    Tags: ,   

    Teaser 1858: Cash flow 

    From The Sunday Times, 26th April 1998 [link]

    We play a variation of a famous puzzle game using coins instead of rings. We start with a pile of coins consisting of at least one of each of the 1p, 2p, 5p, 10p, 20p, 50p and £1 coins, with no coin on top of another that is smaller in diameter. So the 5p coins are on the top, then the 1p, 20p, £1, 10p, 2p and 50p coins respectively, with the 50p coins being on the bottom. One typical pile is illustrated:

    The object of the game is to move the pile to a new position one coin at a time. At any stage there can be up to three piles (in the original position, the final position, and one other). But in no pile can any coin ever be above another of smaller diameter.

    We did this with a pile of coins recently and we found that the minimum number of moves needed equalled the value of the pile in pence. We then doubled the number of coins by adding some 1p, 5p and 50p coins totalling less than £3, and we repeated the game. Again the minimum number of moves equalled the value of the new pile in pence.

    How many coins were in the pile for the first game?

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

    [teaser1852]

     
    • Jim Randell's avatar

      Jim Randell 8:46 am on 30 August 2020 Permalink | Reply

      (See also: Enigma 1254, Enigma 14).

      In a standard Towers of Hanoi problem there are n discs of different sizes.

      In the optimal solution the largest disc is moved 1 time, the next largest 2 times, then 4 times, 8 times, 16 times, …

      So we get a total number of moves: H(n) = 2^n − 1.

      In this version there are multiple discs the same size, but we can treat a block of k discs the same size as a single disk that requires k moves, and solve the puzzle in the same way.

      So, the stack shown in the diagram (which I denote (1, 2, 1, 1, 3, 1, 1), by counting the number of coins of each size, starting at the bottom), will require: 1×1 + 2×2 + 1×4 + 1×8 + 3×16 + 1×32 + 1×64 = 161 moves.

      In general if there number of coins of each size is (A, B, C, D, E, F, G) then the number of moves is:

      A + 2B + 4C + 8D + 16E + 32F + 64G

      and the monetary value of the coins is:

      50A + 2B + 10C + 100D + 20E + F + 5G

      and if these values are equal, we get:

      49A + 6C + 92D + 4E = 31F + 59G

      In the puzzle, the number of coins is doubled by increasing A by a, F by f, G by g, and their monetary value is less than £3:

      50a + f + 5g < 300

      but the monetary value of the augmented stack is still equal to the number of moves required, so:

      49a = 31f + 59g

      We can work out possible values for a, f, g, and their sum gives us the initial number of coins (which is the answer to the puzzle).

      We can then look for stacks of coins of this size with the monetary value and number of moves, and then add the extra coins to the stack and see if the condition still holds.

      This Python 3 program runs in 54ms.

      Run: [ @replit ]

      from enigma import (irange, div, printf)
      
      # denominations of coins by size (largest diameter to smallest diameter)
      ds = (50, 2, 10, 100, 20, 1, 5)
      
      # number of moves for sequence of discs (largest to smallest)
      def H(ns):
        t = 0
        m = 1
        for n in ns:
          t += m * n
          m *= 2
        return t
      
      # decompose <t> into <k> positive numbers
      def decompose(t, k, s=[]):
        if k == 1:
          yield s + [t]
        else:
          for x in irange(1, t + 1 - k):
            yield from decompose(t - x, k - 1, s + [x])
      
      # choose a value for a (= 50p, so there must be 1-5)
      for a in irange(1, 5):
        # choose a value for g (= 5p)
        for g in irange(1, (299 - 50 * a) // 5):
          # calculate f
          f = div(49 * a - 59 * g, 31)
          if f is None or f < 1 or not(50 * a + 5 * g + f < 300): continue
          # the total number of extra coins = the number of original coins
          n = a + f + g
          # output solution
          printf("n={n} [a={a} f={f} g={g}]")
      
          # allocate the number of original coins (n in total)
          for ns in decompose(n, 7):
            # calculate the total value of the stack
            t = sum(n * d for (n, d) in zip(ns, ds))
            # calculate the number of moves for the stack
            m = H(ns)
            if m != t: continue
      
            # make the final stack
            ns2 = list(ns)
            ns2[0] += a
            ns2[5] += f
            ns2[6] += g
            # calculate the total value of the stack
            t2 = sum(n * d for (n, d) in zip(ns2, ds))
      
            # output solution
            printf("-> {ns} [{t}] -> {ns2} [{t2}]")
      

      Solution: There were 12 coins in the original pile.

      There is only one possible size for the original pile, and it must be composed of: (2× 50p, 1× 2p, 1× 10p, 1× £1, 3× 20p, 1× 1p, 3× 5p), making a total value of 288p and requiring 288 moves.

      We then add: (5× 50p, 6× 1p, 1× 5p) = 261p to make a pile with total value 549p and requiring 549 moves.

      Like

  • Unknown's avatar

    Jim Randell 7:09 pm on 28 August 2020 Permalink | Reply
    Tags:   

    Teaser 3023: Timely coincidence 

    From The Sunday Times, 30th August 2020 [link] [link]

    George and Martha possess two digital “clocks”, each having six digits. One displays the time on a 24-hour basis in the format hh mm ss, typically 15 18 45, and the other displays the date in the format dd mm yy, typically 18 07 14.

    On one occasion, George walked into the room to find that the two “clocks” displayed identical readings. Martha commented that the long-term (400-year) average chance of that happening was 1 in just over a six-digit number. That six-digit number gives the birth date of one their daughters.

    On what date was that daughter born?

    [teaser3023]

     
    • Jim Randell's avatar

      Jim Randell 7:35 pm on 28 August 2020 Permalink | Reply

      On any particular day (ignoring jumps in time, such as leap seconds, daylight savings time, calendar reforms, etc.) there is either a 1/86400 chance of observing the clocks displaying the same an any given second (assuming they clocks are equally likely to be observed at any particular second of the day – which is unlikely), or a 0/86400 chance if the date does not correspond to a valid time.

      The following Python program runs in 100ms.

      Run: [ @repl.it ]

      import datetime
      from enigma import int2base, printf
      
      # consider a 400 year period, and count dates that give a valid time
      d = datetime.date(day=1, month=1, year=1900)
      i = datetime.timedelta(days=1)
      n = 0
      t = 0
      while d.year < 2300:
        if d.day < 24 and d.year % 100 < 60: n += 1
        d += i
        t += 1
      
      # output solution
      t *= 86400
      x = t // n
      printf("p = {n} / {t} (~ 1 / {x}); date = {b}", b=int2base(x, group=2, sep=".", width=6))
      

      Solution: The daughter was born on 19th May 1961.


      In order for the date to be read as a valid time we need the day of the month to be in the range 1-23, and the last 2 digits of the year to be in the range 0-59.

      In each year of the required range there are 23×12 = 276 viable days.

      In each 100 year period there are 60×276 = 16560 viable days.

      In each 400 year period there are 4×16560 = 66240 viable days.

      And in a 400 year period there is a total of 400×365 + 100 − 3 = 400×365.2425 = 146097 days.

      Converting to seconds we get a chance of 1 in (146097 × 86400 / 66240) = 190561.304….

      Truncating this result to an integer and reading as a date gives: Friday, 19th May 1961.

      Like

  • Unknown's avatar

    Jim Randell 9:10 am on 27 August 2020 Permalink | Reply
    Tags:   

    Teaser 2681: Inconsequential 

    From The Sunday Times, 9th February 2014 [link] [link]

    An “arithmetic” sequence is one in which each number is a fixed amount more than the previous one. So, for example, 10, 29, 48, … is an arithmetic sequence. In this case its ninth number is 162, which happens to be divisible by 9. I have in mind another arithmetic sequence whose ninth number is divisible by 9. This time it starts with two three-figure numbers, but in this case I have consistently replaced digits by letters, with different letters used for different digits.

    The arithmetic sequence then begins ONE, TWO, …, and its ninth number is NINE.

    To win, find the number to WIN.

    [teaser2681]

     
    • Jim Randell's avatar

      Jim Randell 9:10 am on 27 August 2020 Permalink | Reply

      The common difference is (TWOONE), and there are 8 instances of the common difference between ONE and NINE.

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

      The following run file executes in 110ms.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "div(NINE - ONE, TWO - ONE) = 8"
      "div(NINE, 9) > 0"
      
      --answer="WIN"
      

      Solution: WIN = 523.

      The progression is: ONE = 631, TWO = 956, …, NINE = 3231.

      The common difference is: TWOONE = 325.

      Like

    • GeoffR's avatar

      GeoffR 10:39 am on 27 August 2020 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9: O; var 1..9: N; var 1..9: E;
      var 1..9: T; var 1..9: W; var 1..9: I;
      
      constraint all_different ([O, N, E, T ,W, I]);
      
      var 100..999: ONE = 100*O + 10*N + E;
      var 100..999: TWO = 100*T + 10*W + O;
      var 100..999: WIN = 100*W + 10*I + N;
      var 1000..9999: NINE = 1000*N + 100*I + 10*N + E;
      
      % Define 9th term of series
      constraint NINE = ONE + 8 * (TWO - ONE)
       /\ NINE mod 9 == 0;
      
      solve satisfy;
      
      output [ "Series is " ++ show(ONE) ++ "," ++ show(TWO) ++ 
      "..." ++ show(NINE) ++ "\n" ++ "WIN = " ++ show(WIN)]
      
      % Series is 631,956...3231
      % WIN = 523
      % ----------
      % ==========
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 11:02 am on 27 August 2020 Permalink | Reply

      
      from itertools import permutations
      
      for Q in permutations(range(1,10), 6):
        O, N, E, T, W, I = Q
        ONE, TWO = 100*O + 10*N + E, 100*T + 10*W + O
        WIN = 100*W + 10*I + N
        NINE = 1010*N + 100*I + E
        if NINE == ONE + (TWO - ONE) * 8 and NINE % 9 == 0:
          print(f"ONE = {ONE}, TWO = {TWO}, NINE = {NINE}, WIN = {WIN}")
      
      # ONE = 631, TWO = 956, NINE = 3231, WIN = 523
      
      

      Like

      • Frits's avatar

        Frits 1:26 pm on 27 August 2020 Permalink | Reply

        @GeoffR, shouldn’t you allow for E, I and W to be zero as well?

        Like

    • Frits's avatar

      Frits 1:20 pm on 27 August 2020 Permalink | Reply

       
      # elements in range <r> unequal to values of D[i]
      def domain(i, r):
        vals = set()
        for s in D[i]:
          # use globals() iso eval()
          vals.add(globals()[s])
        return [x for x in r if x not in vals]
      
      # set up dictionary of for-loop iterator names
      def init(li):
        for i in range(1,len(li)):
          D[li[i]] = li[0:i]
          
      # concatenate list of integers 
      to_num = lambda *args: int("".join(map(str, args)))    
      
      
      D = {}
      init(['O','N','E','T','W'])
      
      
      for O in range(1,10):
        for N in domain('N', range(1,10)):
          for E in domain('E', range(0,10)):
            ONE = to_num(O,N,E)
            for T in domain('T', range(1,10)):
              if T > O:  
                for W in domain('W', range(0,10)):
                  TWO = to_num(T,W,O)
                  unit = TWO - ONE
                  units8 = 8*unit + ONE
                  if units8 % 9 != 0 or units8 < 1000: continue
                  
                  s = str(units8)
                  if s[3] != str(E): continue 
                  if s[0] != str(N) or s[2] != str(N): continue 
                  # check letter I
                  if int(s[1]) in {O,N,E,T,W}: continue
            
                  WIN = str(W)+s[1]+str(N)
                  print("WIN ONE TWO NINE")
                  print(WIN, ONE, TWO, units8)
      
      # WIN ONE TWO NINE
      # 523 631 956 3231
      

      Like

    • GeoffR's avatar

      GeoffR 1:47 pm on 27 August 2020 Permalink | Reply

      @Frits: In theory maybe, but it would not have made any difference in practice.
      As the title of the teaser says – “Inconsequential” ?

      Like

  • Unknown's avatar

    Jim Randell 9:06 am on 25 August 2020 Permalink | Reply
    Tags: ,   

    Teaser 2677: One for each day 

    From The Sunday Times, 12th January 2014 [link] [link]

    George and Martha have been looking into tests for divisibility,  including one for the elusive number seven. George wrote down a thousand-figure number by simply writing one particular non-zero digit a thousand times. Then he replaced the first and last digits by another non-zero digit to give him a thousand-figure number using just two different digits. Martha commented that the resulting number was divisible by seven. George added that it was actually divisible by exactly seven of 2, 3, 4, 5, 6, 7, 8, 9 and 11.

    What were the first two digits of this number?

    Note: This puzzle is marked as flawed, as under the intended interpretation there is no solution.

    [teaser2677]

     
    • Jim Randell's avatar

      Jim Randell 9:06 am on 25 August 2020 Permalink | Reply

      Although the puzzle is flawed, I didn’t have a problem solving it, as I interpreted the “actually” in George’s statement to mean that George was correcting Martha’s statement, so it didn’t really matter if Martha’s statement was true or false. All we needed to do was find a number divisible by exactly seven of the given divisors. Which is what my code does, and it finds a unique solution, so I was happy enough with the puzzle.

      Python’s support for large integers means this one can be solved in a straightforward way. The following program runs in 55ms.

      Run: [ @replit ]

      from enigma import (subsets, printf)
      
      digits = "123456789"
      divisors = (2, 3, 4, 5, 6, 7, 8, 9, 11)
      
      for (a, b) in subsets(digits, size=2, select="P"):
        n = int(a + b * 998 + a)
        ds = list(d for d in divisors if n % d == 0)
        if len(ds) == 7:
          printf("a={a} b={b} [ds={ds}]")
      

      Solution: The first two digits of George’s number are 6 and 3.

      So the 1,000-digit number is 6333…3336. Which is divisible by 2, 3, 4, 6, 8, 9, 11.


      However, apparently the setter intended Martha’s statement to be true, and the number to be divisible by 7 and exactly six of the other divisors. Unfortunately, there is no such number. (As we can see by adding [[ and 7 in ds ]] to the condition on line 9).

      The intended solution was 2777…77772, and the setter mistakenly determined that this was divisible by 8. In fact the number is divisible by only six of the divisors: 2, 3, 4, 6, 7, 11.

      Like

    • Hugh+Casement's avatar

      Hugh+Casement 11:33 am on 26 December 2021 Permalink | Reply

      The puzzle could have stated that the number is an integer multiple of seven of the integers 2 to 12 inclusive.

      However, we don’t need the ability to handle huge numbers (or even a computer).

      We can regard the number as jk followed by 498 blocks of kk followed by kj.
      The test for divisibility by 11 involves taking the alternating positive and negative sum of the digits. jk and kj cancel out, and each kk cancels, so the alternating digit sum is 0: the number is an integer multiple of 11 whatever the values of j and k.

      It cannot be a multiple of 10 since we were told the digits are non-zero.

      A test for divisibility by 7 is to take the alternating positive and negative sum of groups of three digits from the right. If and only if the result is a multiple of 7 is the overall number also a multiple of 7.

      In this case we have a number with j on the left, then 332 blocks of kkk, and kkj on the right.
      The even number of blocks cancel out, leaving kkj − j = kk0 = k × 110.
      That is an integer multiple of 7 only if k = 7.

      If the number is not a multiple of 2 then it cannot be a multiple of 4, 6, 8, or 12.
      If it is not a multiple of 3 then it cannot be a multiple of 6, 9, or 12.
      In order to have seven factors in the list it must be a multiple of both 2 and 3.

      If j = 2 the number is an integer multiple of 2 and 4 (but, as Jim says, not 8).

      So now we know j and k.
      We can regard the number as 27 followed by 332 blocks of 777 followed by 72.
      777 is a multiple of 3, so the digit sum is a multiple of 3 and the number is an integer multiple of 3 (but not of 9). Being a multiple of 3 and 4 it is also a multiple of 12.

      The number is not a multiple of 5, 8, or 9 (nor 10).

      Liked by 1 person

  • Unknown's avatar

    Jim Randell 11:18 am on 23 August 2020 Permalink | Reply
    Tags: by: D. A. Hammersley   

    Brain-Teaser 6: [Family products] 

    From The Sunday Times, 2nd April 1961 [link]

    Living next door to each other are two families each having three children. The product of the three ages in one family is equal to the product of those in the other family. Next month three of the six children, including the eldest, have birthdays, and at the end of the month the two products will again be equal.

    None of the children has the same age either now or at the end of next month, and no child is older than twelve.

    What are the ages in each family now?

    This puzzle was originally published with no title.

    [teaser6]

     
    • Jim Randell's avatar

      Jim Randell 11:19 am on 23 August 2020 Permalink | Reply

      This Python program runs in 55ms.

      from enigma import (group, subsets, irange, multiply, printf)
      
      # collect triples of ages from 1 to 12, by their product
      ts = group(subsets(irange(1, 12), size=3), by=multiply)
      
      # we are only interested in products with multiple values
      ts = dict((k, v) for (k, v) in ts.items() if len(v) > 1)
      
      # choose the first product
      for (k1, vs1) in ts.items():
        # choose the initial sets
        for (a1, b1) in subsets(vs1, size=2):
          # the ages are all different
          s1 = a1 + b1
          if len(set(s1)) < 6: continue
      
          # choose the second product
          for (k2, vs2) in ts.items():
            if not (k1 < k2): continue
            # choose the final sets
            for (a2, b2) in subsets(vs2, size=2, select="P"):
              # ages are still all different
              s2 = a2 + b2
              if len(set(s2)) < 6: continue
              # and three of them differ by 1
              ds = tuple(y - x for (x, y) in zip(s1, s2))
              if not (ds.count(1) == 3 and ds.count(0) == 3): continue
              # including the max
              if not (max(s1) + 1 == max(s2)): continue
              # output solution
              printf("{k1} -> {a1} {b1}; {k2} -> {a2} {b2}")
      

      Solution: The current ages are: (1, 8, 9) and (3, 4, 6).

      And 1×8×9 = 3×4×6 = 72.

      At the end of next month the ages will be: (1, 9, 10) and (3, 5, 6), both giving products of 90.

      Like

      • Jim Randell's avatar

        Jim Randell 12:13 pm on 24 August 2020 Permalink | Reply

        A slightly shorter version:

        from enigma import (irange, subsets, multiply, is_square, partitions, printf)
        
        # find 6 ages with a product that is square
        for s1 in subsets(irange(1, 12), size=6):
          p = is_square(multiply(s1))
          if p is None: continue
          # split the ages into 2 sets
          for (a1, b1) in partitions(s1, 3):
            if multiply(a1) != p: continue
            # now increment 3 of the values
            for js in subsets(irange(0, 5), size=3):
              s2 = list(a1 + b1)
              for j in js: s2[j] += 1
              # check the ages are still all different
              if len(set(s2)) < 6: continue
              # check the eldest is incremented
              if not (max(s2) == max(s1) + 1): continue
              # split the ages
              (a2, b2) = (s2[:3], s2[3:])
              # check the products are still the same
              if not (multiply(a2) == multiply(b2)): continue
              # output solution
              printf("{p} -> {a1}, {b1}; {q} -> {a2}, {b2}", q=multiply(a2))
        

        Like

    • GeoffR's avatar

      GeoffR 5:27 pm on 23 August 2020 Permalink | Reply

      A part programmatic / part manual solution:

      
      from itertools import permutations
      
      from collections import defaultdict
      D = defaultdict(list)
      
      # Family 1 ages are a,b,c and Family 2 ages are d,e,f
      # Allow for no child older than 12 after a 1 year increase
      for Q in permutations(range(1,12), 6):
        a, b, c, d, e, f = Q
        # make c the eldest child (is either c or f)
        if a < b and b < c and c > f and d < e and e < f:
          if a * b * c == d * e * f:
            # add result to dictionary, with product as key
            D[a * b * c] += [(a, b, c, d, e, f)]
      
      for k,v in D.items():
          print(k,v)
      
      # Manual Assessment
      # A print-out of Dictionary D gives
      # the product first and the group of 6 family ages
      # 36 [(1, 4, 9, 2, 3, 6)]
      # 60 [(1, 6, 10, 3, 4, 5)]
      # 72 [(1, 8, 9, 3, 4, 6)]
      # 90 [(1, 9, 10, 3, 5, 6)]
      # 120 [(2, 6, 10, 3, 5, 8)] 
      # 180 [(3, 6, 10, 4, 5, 9)]
      
      # The only group of three ages which can all be
      # increased by 1 are (8,9,4) in product 72
      # to give (9,10,5) in product 90 with
      # eldest child's age increased by i year
      #
      # This gives (1,9,10) and (3,5,6) as the ages
      # of the two families now
      #
      # Note;
      # It appears that 120/180 products could have three 1 year
      # age increments ie (2,3,8) to become (3,4,9),
      # but there is no increase in the eldest
      # child's age, which is a condition, so invalid
      
      

      Like

    • Frits's avatar

      Frits 7:29 pm on 23 August 2020 Permalink | Reply

       
      from enigma import  SubstitutedExpression, irange, seq_all_different
      
      p = SubstitutedExpression([
        "A < D",                           # avoid reporting same solution twice
        "A < B",
        "B < C",
        "D < E",
        "E < F",
        "A*B*C == D*E*F",
        "b + c + d + e + f + g = 3",       # addition bits
        "max(b,c,d,e,f,g) == 1",           # bits are 0 or 1
        "max(C, F) + 1 == max(C+d, F+g)",  # eldest birthday
        "v([A+b,B+c,C+d,D+e,E+f,F+g])",    # all different
        "(A+b)*(B+c)*(C+d) == (D+e)*(E+f)*(F+g)",
        ], 
        verbose=0, 
        digits=irange(0, 11),
        symbols="ABCDEFbcdefg",
        code="v = lambda x: seq_all_different(x)",
        distinct="ABCDEF",     
        answer="(A,B,C,D,E,F,A+b,B+c,C+d,D+e,E+f,F+g,A*B*C,D*E*F, \
                (A+b)*(B+c)*(C+d),(D+e)*(E+f)*(F+g))"
      )
          
        
      # Print answers
      for (_, ans) in p.solve(verbose=0):
        print(f"Current:\nAges \
      ({ans[0]},{ans[1]},{ans[2]}) product {ans[12]}, \
      ({ans[3]},{ans[4]},{ans[5]}) product {ans[13]} \
      \nEnd of the month: \nAges \
      ({ans[6]},{ans[7]},{ans[8]}) product {ans[14]}, \
      ({ans[9]},{ans[10]},{ans[11]}) product {ans[15]}")
        
        
      # Current:
      # Ages (1,8,9) product 72, (3,4,6) product 72
      # End of the month:
      # Ages (1,9,10) product 90, (3,5,6) product 90
      

      Like

    • GeoffR's avatar

      GeoffR 10:05 am on 24 August 2020 Permalink | Reply

      Frits’ solution has given me an idea for a solution in MiniZinc.

      i.e limiting the six addition digits to (0..1) range and the sum of the six additions to three

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      % First Family - three children's ages 
      var 1..11: A; var 1..11: B; var 1..11: C; 
      
      % Second Family - three children's ages 
      var 1..11: D; var 1..11: E; var 1..11: F; 
      
      % Six children's ages are all different
      constraint all_different([A, B, C, D, E, F]);
      
      % Order ages 
      constraint A < B /\ B < C /\  C > F
      /\ D < E /\ E < F;
      
      % Possible digit increases for A, B, C, D, E, F
      var 0..1:a; var 0..1:b; var 0..1:c; 
      var 0..1:d; var 0..1:e; var 0..1:f; 
      
      % Digit increases are for three children only
      % including the eldest
      constraint c == 1 /\  a + b + c + d + e + f == 3;
      
      % Current Product of Family 1's children = 
      % Product of Family 2 's children
      constraint A * B * C == D * E * F;
      
      % Family age products after one month
      constraint (A+a) * (B+b) * (C+c)
      == (D+d) * (E+e) * (F+f);
      
      solve satisfy;
      
      output [ "Family ages now:    " ++ show([A,B,C]) ++ 
      " and "++ show([D,E,F]) ++ "\nFuture family ages: " ++ 
      show([A+a, B+b, C+c]) ++ " and " ++
      show([D+d, E+e, F+f]) ];
      
      % Family ages now:    [1, 8, 9] and [3, 4, 6]
      % Future family ages: [1, 9, 10] and [3, 5, 6]
      % time elapsed: 0.05 s
      % ----------
      % ==========
      
      

      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