Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 4:38 pm on 21 August 2020 Permalink | Reply
    Tags:   

    Teaser 3022: Sporty set 

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

    There are 100 members of my sports club where we can play tennis, badminton, squash and table tennis (with table tennis being the least popular). Last week I reported to the secretary the numbers who participate in each of the four sports. The digits used overall in the four numbers were different and not zero.

    The secretary wondered how many of the members were keen enough to play all four sports, but of course he was unable to work out that number from the four numbers I had given him. However, he used the four numbers to work out the minimum and the maximum possible numbers playing all four sports. His two answers were two-figure numbers, one being a multiple of the other.

    How many played table tennis?

    [teaser3022]

     
    • Jim Randell's avatar

      Jim Randell 11:26 pm on 21 August 2020 Permalink | Reply

      If the numbers that participate in each sport are A, B, C, D (in increasing order), and the sum of these values is T.

      Then the maximum size of their intersection is easily determined – it is the size of the smallest set, in this case A; (or as John points out below, if all 100 members participate in at least one of the available sports, the maximum is: floor((T − 100) / 3), if this is smaller than A).

      So our maximum is: min(A, floor((T − 100) / 3).

      The minimum size of the intersection is trickier to determine. But if we have a family of subsets S[i] of some universal set U, then the minimum size of the intersection is given by:

      X = \left|\bigcap_{i=1}^{n}S_i\right| \medskip \newline N = |U| \medskip \newline T = \sum_{i=1}^{n}|S_i| \medskip \newline \newline X \geq 0 \medskip \newline X \geq T - (n - 1)N

      So in this case the minimum size is: max(0, T − 300).

      The following Python program runs in 138ms.

      Run: [ @repl.it ]

      from enigma import subsets, irange, diff, nconcat, divf, div, multiset, printf
      
      # generate k increasing 2-digit numbers from the supplied (increasing) digits
      def generate(k, ds, ns=[]):
        # choose the tens digits
        for d10s in subsets(ds, size=k, select="C"):
          # choose the units digits
          for d1s in subsets(diff(ds, d10s), size=k, select="P"):
            # construct the numbers
            yield tuple(nconcat(z) for z in zip(d10s, d1s))
      
      # collect solutions (size of the smallest set)
      ss = multiset()
      
      # choose increasing numbers of participants for each of the 4 sports
      for ns in generate(4, irange(1, 9)):
        T = sum(ns)
        # maximum size of intersection
        M = min(ns[0], divf(T - 100, 3))
        # minimum size of intersection
        m = max(0, T - 300)
      
        # min is a 2-digit numbers
        if m < 10: continue
        # M is a multiple of m
        k = div(M, m)
        if k is None or not(k > 1): continue
      
        printf("[ns={ns}; min={m} max={M} k={k}]")
        ss.add(ns[0])
      
      # output solutions
      for (k, v) in ss.most_common():
        printf("min(ns) = {k} [{v} solutions]")
      

      Solution: 65 played table tennis.

      The other three sports are represented by the numbers (70, 80, 90) + (1, 3, 4) assembled in some order.

      The minimum size of the intersection is 13, and the maximum size is 65 (= 5×13).

      Like

      • Jim Randell's avatar

        Jim Randell 8:54 am on 23 August 2020 Permalink | Reply

        We don’t need to work out the actual sizes of the 3 larger sets, to calculate the sum of their sizes. We only need to know what the tens and units digits are.

        So here is a faster version of my program. You can also pass a command line argument to specify if members playing zero sports are allowed (1) or not (0). It runs in 54ms.

        Run: [ @repl.it ]

        from enigma import irange, subsets, diff, divf, div, arg, printf
        
        # set z to:
        # 0 - if members playing 0 sports are not allowed
        # 1 - if members playing 0 sports are allowed
        z = arg(0, 0, int)
        
        # allowable digits
        digits = irange(1, 9)
        
        # choose the 10s digits for the sets
        for d10s in subsets(digits, size=4):
          t10 = sum(d10s) * 10
          if t10 < 280: continue
        
          # choose the units digits
          for d1s in subsets(diff(digits, d10s), size=4):
            # size of the union of the sets
            T = t10 + sum(d1s)
            # minimum size of intersection
            m = T - 300
            # is a 2 digit number
            if not(9 < m < 100): continue
        
            # choose a units digit for the smallest set
            for d1 in d1s:
              # maximum size of intersection
              A = 10 * d10s[0] + d1
              M = (A if z else min(A, divf(T - 100, 3)))
              # is a multiple of m
              k = div(M, m)
              if k is None or not(k > 1): continue
        
              # output solution
              printf("min(ns) = {A} [min={m} max={M} k={k}; ns = {d10s} x {d1s}] [z={z}]")
        

        Like

        • Frits's avatar

          Frits 10:11 pm on 24 August 2020 Permalink | Reply

          Very nice solution.

          “if t10 Don’t we already know d10s must always be equal to (6, 7, 8, 9)?

          if it is (5, 7, 8, 9) then t10 = 290 and sum(d1s) <= 15 (fi 2,3,4,6)
          So T <= 305 which is not acceptable.

          Like

          • Frits's avatar

            Frits 12:12 pm on 25 August 2020 Permalink | Reply

             
            from enigma import irange, diff, div, printf
            
            # minimum A + B + C + D - 300 > 9 
            # so 10s digits must be (6,7,8,9)
            
            # (A + B + C + D - 100) / 3 >= 70
            # which is bigger than A, so maximum M equals A
            
            # allowable digits for units
            digits = irange(1, 5)
            
            # d is the units digit which is not used in A, B, C and D
            for d in digits:
              # sum of A, B, C and D
              T = 315 - d
              # minimum size of intersection
              m = T - 300
              # choose a units digit for the smallest set
              d1s = diff(digits, (d,))
              for d1 in d1s:
                # maximum size of intersection (is A)
                M = 60 + d1    
                # is a multiple of m
                k = div(M, m)
                if k is None or not(k > 1): continue
            
                # output solution
                printf("min(ns) = {M} [min={m} max={M} k={k}; ns = (6,7,8,9) x {d1s}]")
            

            Like

          • Jim Randell's avatar

            Jim Randell 12:51 pm on 25 August 2020 Permalink | Reply

            @Frits: It looks like your comment didn’t make it through WordPress unscathed. Usually this happens when you use < and > in a comment. WordPress sees everything in between as an HTML tag, and then doesn’t recognise it, so it throws it away. You can use &lt; and &gt; to make < and > appear correctly. (If you want to send me a corrected version, I’ll fix it up).

            It looks like you are asking about line 9 of my code. It’s really just a quick bit of early rejection so the code doesn’t go on to evaluate cases that can’t possibly work, and the program works just as well (and not much slower) without it.

            I reasoned that if (T − 300) is to be a 2-digit number, then (T − 300) > 9, so: T > 309. The maximum possible value of the units digits of the four numbers that make up T is 6+7+8+9 = 30, so the sum of the tens parts of the numbers must be more than 309 − 30 = 279, which is what that test does, and it cuts the number of cases considered drastically.

            With a bit more work we can see that there is only one possible set of values for the tens digits, and we are half way towards a manual solution.

            When programming a solution there is always an amount of choice on how much analysis is necessary to get a program that runs in a reasonable time. But normally if it doesn’t make much difference to the run time I let the computer do the checks.

            Like

            • Frits's avatar

              Frits 6:26 pm on 25 August 2020 Permalink | Reply

              @Jim, Thanks.

              I like to make use of –> which WordPress doesn’t like.

              Yes, I was trying to understand line 9 of your code and wondered why you had chosen this (for me suboptimal) limit.

              I still need to verify for myself that the minimum and maximum formula are indeed correct.

              Is there not an easy way of allowing us to edit our own posts (as it is linked to an email address)?
              Many times directly after posting I see errors/improvements.

              Like

              • Jim Randell's avatar

                Jim Randell 10:37 am on 26 August 2020 Permalink | Reply

                @Frits: Sadly WordPress doesn’t treat comments at the same level as posts. Even for logged-in users. So the only way they can be edited is by a site administrator (who can make changes to any aspect of the site).

                It is the main reason I sometimes think about moving away from WordPress, but I have not yet found a suitable alternative.

                At one point I had hoped that WordPress would implement some level of editing (or even just the basic courtesy of previewing a comment before it is posted), but it has become clear that they really have no interest in this.

                On the upside the system does make it relatively easy to manage a site, and has remained (mostly) problem free for the last 8 years.

                In the meantime, the best I can offer as site administrator is to make amendments to comments if you send me any revisions.

                Like

    • Frits's avatar

      Frits 2:38 pm on 22 August 2020 Permalink | Reply

      Making use of Jim’s analysis (and Brian’s at [{broken link removed}]) .

       
      from enigma import  SubstitutedExpression, irange 
      
      # AB = table tennis
      # minimum played is AB + CD + EF + GH - 300
      # maximum played is AB
      # maximum = X * minimum
      
      p = SubstitutedExpression([
          "GH > EF", 
          "EF > CD", 
          "CD > AB", 
          "AB % (AB + CD + EF + GH - 300) == 0", 
          "(AB + CD + EF + GH - 300) > 10",
         ], 
          verbose=0, 
          answer="AB,CD,EF,GH",
          digits=irange(1, 9))
        
      # Print answers
      for (_, ans) in p.solve(verbose=0):
        print(f"{ans} minimum={sum(ans)-300} maximum={ans[0]}")
      
      # Output:
      #
      # (65, 74, 83, 91) minimum=13 maximum=65
      # (65, 73, 84, 91) minimum=13 maximum=65
      # (65, 74, 81, 93) minimum=13 maximum=65
      # (65, 71, 84, 93) minimum=13 maximum=65
      # (65, 73, 81, 94) minimum=13 maximum=65
      # (65, 71, 83, 94) minimum=13 maximum=65
      

      Like

    • GeoffR's avatar

      GeoffR 3:25 pm on 22 August 2020 Permalink | Reply

      @Frits: You probably don’t know, but there is a sort of convention in Brian/Jim’s groups for programmed solutions with the answer not to be published early. This helps to minimise people entering the Sunday Times Prize Draw competition if they are just looking for the answer to take part in the draw, without attempting the puzzle.

      Like

      • Frits's avatar

        Frits 4:46 pm on 22 August 2020 Permalink | Reply

        @GeoffR.
        Thanks. I keep that in mind.

        Like

    • John Crabtree's avatar

      John Crabtree 6:22 pm on 23 August 2020 Permalink | Reply

      The maximum is not A, but min(A, (A + B + C + D – 100) / 3)

      Like

      • Jim Randell's avatar

        Jim Randell 8:45 pm on 23 August 2020 Permalink | Reply

        @John: You are, of course, quite right. Thanks.

        I originally came up with the maximum without making the assumption that every member participated in at least one sport. But then I used that assumption to determine the minimum, and I neglected to revisit the maximum.

        I have provided code above that allows you to select whether participation in zero sports is allowed or not.

        Like

        • Jim Randell's avatar

          Jim Randell 10:08 pm on 26 August 2020 Permalink | Reply

          Actually, it looks like it doesn’t matter if we allow members who participate in 0 sports or not. The possible numbers presented to the secretary are the same in both cases, and so is the answer to the puzzle.


          The minimum intersection size is given by: max(0, T − 300).

          And we require this to be a 2 digit number.

          So we must have:

          T − 300 ≥ 10
          T ≥ 310

          And T is the sum of four 2-digit numbers, all with different non-zero digits.

          The largest we can make the units digits of our four numbers is 6 + 7 + 8 + 9 = 30.

          So the tens digits have to sum to at least 28, so the numbers are of the form:

          4a + 7b + 8c + 9d = 280 + (a + b + c + d)
          5a + 6b + 8c + 9d = 280 + (a + b + c + d)
          5a + 7b + 8c + 9d = 290 + (a + b + c + d)
          6a + 7b + 8c + 9d = 300 + (a + b + c + d)

          In the first case the maximum value of (a + b + c + d) is (6 + 5 + 3 + 2) = 16, giving a maximum possible total of 296, so this is not feasible.

          In the second case the maximum value of(a + b + c + d) is (7 + 4 + 3 + 2) = 16, giving a maximum possible total of 296, so this is not feasible.

          In the third case the maximum value of(a + b + c + d) is (6 + 4 + 3 + 2) = 15, giving a maximum possible total of 305, so this is not feasible.

          In the fourth case the maximum value of (a + b + c + d) is (5 + 4 + 3 + 2) = 14, giving a maximum possible total of 314, so this is feasible.

          So the four numbers must be of the form: 6a, 7b, 8c, 9d

          The size of the smallest set is thus: 61, 62, 63, 64, or 65.

          And the smallest possible value of floor((T − 100) / 3) = floor((310 − 100) / 3) = 70.

          So, in this case, the maximum intersection size is always the same as the size of the smallest set, whether we allow participants of zero sports or not.

          Like

  • Unknown's avatar

    Jim Randell 12:05 pm on 20 August 2020 Permalink | Reply
    Tags: ,   

    Teaser 2551: Take nothing for granted 

    From The Sunday Times, 14th August 2011 [link] [link]

    In arithmetic, the zero has some delightful properties. For example:

    ANY + NONE = SAME
    X . ZERO = NONE

    In that sum and product, digits have been replaced with letters, different letters being used for different digits. But nothing should be taken for granted: here the equal signs are only approximations as, in each case, the two sides may be equal or differ by one.

    What is your NAME?

    [teaser2551]

     
    • Jim Randell's avatar

      Jim Randell 12:06 pm on 20 August 2020 Permalink | Reply

      We can use the [[ SubstitutedExpression() ]] solver from the enigma.py library to tackle this puzzle. Unfortunately there are two possible solutions.

      The following run file executes in 159ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "abs(ANY + NONE - SAME) < 2"
      "abs(X * ZERO - NONE) < 2"
      
      --answer="NAME"
      #--answer="SYNONYM" # for a unique answer
      

      Solution: The two possible values for NAME are: 7146 and 7543.

      The possible sums are:

      170 + 7976 = 8146; 6 × 1329 ≈ 7973
      570 + 7973 = 8543; 3 × 2659 ≈ 7976

      In both cases the addition is exact, but the multiplication sums are off by 1.

      If instead, the puzzle had asked the value of SYNONYM the answer would have been unique (= 8079704).

      Like

    • GeoffR's avatar

      GeoffR 3:07 pm on 20 August 2020 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:A; var 1..9:N; var 0..9:Y; 
      var 0..9:O; var 0..9:E; var 1..9:X;
      var 1..9:Z; var 0..9:R; var 1..9:S;  
      var 0..9:M;
      
      constraint all_different([A,N,Y,O,E,X,Z,R,S,M]);
      
      var 100..999: ANY = 100*A + 10*N + Y;
      var 1000..9999: NONE = 1010*N + 100*O + E;
      var 1000..9999: SAME = 1000*S + 100*A + 10*M + E;
      var 1000..9999: ZERO = 1000*Z + 100*E + 10*R + O;
      var 1000..9999: NAME = 1000*N + 100*A + 10*M + E;
      
      constraint ANY + NONE == SAME \/ANY + NONE == SAME + 1
      \/ ANY + NONE == SAME - 1;
      
      constraint X * ZERO == NONE \/  X * ZERO == NONE + 1
      \/  X * ZERO == NONE - 1;
      
      solve satisfy;
      
      output ["NAME = " ++ show(NAME)
      ++ "\n Sum is " ++ show(ANY) ++ " + " ++ show(NONE) 
      ++ " = " ++ show(SAME)
      ++ "\n Product is " ++ show(X) ++ " * " ++ show(ZERO)
      ++ " = " ++ show(NONE)
      ++ "\n SYNONYM = " ++ show(S),show(Y),show(N),show(O),
      show(N),show(Y),show(M)];
      
      % NAME = 7543
      %  Sum is 570 + 7973 = 8543
      %  Product is 6 * 1329 = 7973
      %  SYNONYM = 8079704
      % ----------
      % NAME = 7146
      %  Sum is 170 + 7976 = 8146
      %  Product is 3 * 2659 = 7976
      %  SYNONYM = 8079704
      % ----------
      % ==========
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:37 am on 18 August 2020 Permalink | Reply
    Tags:   

    Brainteaser 1847: Home’s best 

    From The Sunday Times, 8th February 1998 [link]

    Four football teams — Albion, Borough, City and District — each play each other twice, once at home and once away. They get three points for a win and one for a draw. Last season, each team did worse in the away match than in the corresponding home match, scoring fewer goals and getting fewer points. The final position was as follows:

    What were the two scores when Borough played District?

    This is the final puzzle to use the title Brainteaser. The following puzzle is Teaser 1848.

    [teaser1847]

     
    • Jim Randell's avatar

      Jim Randell 8:39 am on 18 August 2020 Permalink | Reply

      This Python program uses the [[ Football() ]] helper class from the enigma.py library.

      It runs in 452ms.

      Run: [ @repl.it ]

      from enigma import Football, printf
      
      # scoring system
      football = Football(games="wdl", points=dict(w=3, d=1))
      
      # check home matches have more points and more goals than away matches
      def check(Hs, As):
        for (H, A) in zip(Hs, As):
          if not(H[0] > A[1]): return False
          (h, a) = football.outcomes([H, A], [0, 1])
          if not(football.points(h) > football.points(a)): return False
        return True
      
      # choose C's six games (home, away)
      for (CA, CB, CD, AC, BC, DC) in football.games(repeat=6):
        # table for C
        tC = football.table([CA, CB, CD, AC, BC, DC], [0, 0, 0, 1, 1, 1])
        if not(tC.points == 7): continue
      
        # remaining matches for D
        for (DA, DB, AD, BD) in football.games(repeat=4):
          # table for D
          tD = football.table([DA, DB, DC, AD, BD, CD], [0, 0, 0, 1, 1, 1])
          if not(tD.points == 5): continue
      
          # remaining matches (for A and B)
          for (AB, BA) in football.games(repeat=2):
            # table for A, B
            tA = football.table([AB, AC, AD, BA, CA, DA], [0, 0, 0, 1, 1, 1])
            tB = football.table([BA, BC, BD, AB, CB, DB], [0, 0, 0, 1, 1, 1])
            if not(tA.points == 12 and tB.points == 8): continue
      
            # make possible scores (for C)
            for (sCA, sCB, sCD, sAC, sBC, sDC) in football.scores([CA, CB, CD, AC, BC, DC], [0, 0, 0, 1, 1, 1], 3, 5):
              # check the home matches were better than the away matches
              if not check([sCA, sCB, sCD], [sAC, sBC, sDC]): continue
      
              # remaining scores (for D)
              for (sDA, sDB, sAD, sBD) in football.scores([DA, DB, AD, BD], [0, 0, 1, 1], 9, 13, [sCD, sDC], [1, 0]):
                # check home matches were better than away matches
                if not check([sDA, sDB, sDC], [sAD, sBD, sCD]): continue
      
                # remaining scores (for A)
                for (sAB, sBA) in football.scores([AB, BA], [0, 1], 15, 8, [sAC, sAD, sCA, sDA], [0, 0, 1, 1]):
                  # check for/against B
                  (fB, aB) = football.goals([sBA, sBC, sBD, sAB, sCB, sDB], [0, 0, 0, 1, 1, 1])
                  if not(fB == 12 and aB == 13): continue
                  # check home matches were better than away matches
                  if not check([sAB, sAC, sAD], [sBA, sCA, sDA]): continue
                  if not check([sBA, sBC, sBD], [sAB, sCB, sDB]): continue
      
                  printf("AB={AB}:{sAB} AC={AC}:{sAC} AD={AD}:{sAD} / BA={BA}:{sBA} CA={CA}:{sCA} DA={DA}:{sDA}")
                  printf("BA={BA}:{sBA} BC={BC}:{sBC} BD={BD}:{sBD} / AB={AB}:{sAB} CB={CB}:{sCB} DB={DB}:{sDB}")
                  printf("CA={CA}:{sCA} CB={CB}:{sCB} CD={CD}:{sCD} / AC={AC}:{sAC} BC={BC}:{sBC} DC={DC}:{sDC}")
                  printf("DA={DA}:{sDA} DB={DB}:{sDB} DC={DC}:{sDC} / AD={AD}:{sAD} BD={BD}:{sBD} CD={CD}:{sCD}")
                  printf()
      

      Solution: The scores in the Borough vs. District matches were: B (home) vs. D (away) = 4 – 2; D (home) vs. B (away) = 3 – 3.

      The scores in the matches are: (home vs. away)

      A vs B = 4 – 1; A vs C = 2 – 0; A vs D = 3 – 1
      B vs A = 3 – 3; B vs C = 1 – 0; B vs D = 4 – 2
      C vs A = 1 – 1; C vs B = 1 – 0; C vs D = 1 – 0
      D vs A = 2 – 2; D vs B = 3 – 3; D vs C = 1 – 0

      Like

  • Unknown's avatar

    Jim Randell 9:26 am on 16 August 2020 Permalink | Reply
    Tags: by: PL Havard   

    Teaser 1852: Fits to a T 

    From The Sunday Times, 15th March 1998 [link]

    We wish to just completely cover a 6-by-6 square grid with T-shaped pieces made up of 4 squares as shown. No overlapping is allowed. We wish to do this in as many different ways as possible. Any pattern that can be obtained from another by rotation or reflection is regarded as the same pattern.

     

    (a) In how many ways can the 6-by-6 grid be filled?
    (b) In how many ways can a 10-by-10 grid be filled?

    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 9:27 am on 16 August 2020 Permalink | Reply

      Each T-shaped piece consists of 4 smaller squares, so to tile an n×n grid we require n² to be divisible by 4 (i.e. n to be divisible by 2).

      For a 6×6 grid we would need 9 T’s, and for a 10×10 grid we would need 25 T’s.

      If we colour the grid chessboard-style with alternating black and white squares, then we see when we place a T it will cover either 3 black + 1 white (type A), or 3 white + 1 black (type B).

      So, for an n×n grid, if we choose k type A shapes (for k = 0..(n²/4)) we will need ((n²/4) − k) type B shapes to give the required number of squares.

      And we need the number of black squares and the number of white squares to both be n²/2:

      3k + ((n²/4) − k) = n²/2
      2k = n²/4
      k = n²/8

      So n² must be divisible by 8, and hence n must be divisible by 4.

      Therefore we cannot tile a 6×6 grid or a 10×10 grid using T4 shapes.

      Solution: There are no ways to fill either the 6×6 grid or the 10×10 grid.

      But we can tile a (4n)×(4n) grid by repeating the following pattern:

      Like

  • Unknown's avatar

    Jim Randell 9:34 pm on 14 August 2020 Permalink | Reply
    Tags:   

    Teaser 3021: Field for thought 

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

    Farmer Giles had a rectangular field bordered by four fences that was 55 hectares in size. He divided the field into three by planting two hedges, from the mid-point of two fences to two corners of the field. He then planted two more hedges, from the mid-point of two fences to two corners of the field. All four hedges were straight, each starting at a different fence and finishing at a different corner.

    What was the area of the largest field when the four hedges had been planted?

    [teaser3021]

     
    • Jim Randell's avatar

      Jim Randell 10:24 pm on 14 August 2020 Permalink | Reply

      The x and y co-ordinates of the intersections of the hedges helpfully divide the edges of the rectangle into equal divisions.

      The area of the central quadrilateral is then easily calculated.

      Solution: The central field has an area of 11 hectares.

      In the first diagram, each of the four coloured triangular areas has an area of xy/5, so the central region also has an area of xy/5.

      Another way to think of it is that there are five quadrilaterals each identical to the central one, but four of them have had bits sliced off them, and then the sliced off bits have been repositioned to make the original rectangle.

      This gives a handy practical construction achievable using folding and straight edge to divide a rectangle into 5 equal strips (or a 5×5 grid of identical smaller rectangles).


      This is a bit like Routh’s Theorem (see: Enigma 1313Enigma 320Enigma 1076, Teaser 2865) but for a rectangle instead of a triangle.

      In general, if the the lines are drawn from a corner to a point a fraction f along a side we can determine the area of the central quadrilateral (as a fraction of the overall parallelogram) as:

      A = (1 − f)² / (1 + f²)

      In the case of f = 1/2 we get:

      A = (1/2)² / (1 + (1/2)²) = (1/4) / (5/4) = 1/5

      Like

  • Unknown's avatar

    Jim Randell 5:43 pm on 13 August 2020 Permalink | Reply
    Tags:   

    Teaser 2705: In the pub 

    From The Sunday Times, 27th July 2014 [link] [link]

    George and Martha are in the pub. He has ordered a glass of lager for her and some ale for himself. He has written three numbers in increasing order, none involving a zero, then consistently replaced digits by letters to give:

    DRANK
    GLASS
    LAGER

    George explains this to Martha and tells her that the third number is in fact the sum of the previous two. From this information she is able to work out the value of each letter.

    What is the number of George’s ALE?

    [teaser2705]

     
    • Jim Randell's avatar

      Jim Randell 5:43 pm on 13 August 2020 Permalink | Reply

      We can solve this straightforwardly using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file executes in 270ms.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      --digits="1-9"
      --answer="ALE"
      
      "DRANK + GLASS = LAGER"
      "G > D"
      

      Solution: ALE = 392.


      For a faster execution time, we can use the [[ SubstitutedSum() ]] solver to solve the alphametic sum, and then check the solutions it finds to ensure D < G.

      from enigma import (SubstitutedSum, irange, printf)
      
      # all digits are non-zero
      p = SubstitutedSum(['DRANK', 'GLASS'], 'LAGER', digits=irange(1, 9))
      
      for s in p.solve(check=(lambda s: s['D'] < s['G']), verbose=1):
        printf("ALE={ALE}", ALE=p.substitute(s, 'ALE'))
      

      By itself the alphametic sum has 3 solutions (when 0 digits are disallowed), but only one of these has D < G.

      Like

      • Frits's avatar

        Frits 11:26 am on 14 August 2020 Permalink | Reply

        @Jim

        You might also add “D + G == L or D + G + 1 == L” to speed things up.

        Like

        • Jim Randell's avatar

          Jim Randell 2:53 pm on 14 August 2020 Permalink | Reply

          Yes. If we break down the sum into individual columns, with carries of 0 or 1 between them we can use the following run file to solve the problem:

          #! python -m enigma -rr
          
          SubstitutedExpression
          
          --answer="{ALE}"
          --distinct="ADEGKLNRS"
          --invalid="0,ADEGKLNRS"
          --invalid="2-9,abcd"
          
          # breaking the sum out into individual columns
          "{D} + {G} + {a} =  {L}"
          "{R} + {L} + {b} = {aA}"
          "{A} + {A} + {c} = {bG}"
          "{N} + {S} + {d} = {cE}"
          "{K} + {S}       = {dR}"
          
          # and G > D
          "{G} > {D}"
          
          --template="(DRANK + GLASS = LAGER) (G > D)"
          

          If we specify the [[ --verbose=32 ]] to measure the internal run-time of the generated code, we find that it runs in 170µs. (The total execution time for the process (which is what I normally report) is 97ms).

          The question is whether the fraction of a second saved in run time is paid off by the amount of additional time (and effort) taken to write the longer run file.

          Like

          • Frits's avatar

            Frits 8:18 pm on 15 August 2020 Permalink | Reply

            @Jim,

            If I use [[ –verbose=32 ]] on your code (with pypy) the internal run-time is 20% of the total execution time (15ms / 75ms). A lot more than you reported.

            I rewrote the generated code.
            It runs approx 3 times faster (elapsed time).

            PS Currently I am more interested in efficient Python code than the easiest/shortest way of programming.

            # breaking the sum out into individual columns
            # "{D} + {G} + {a} =  {L}"
            # "{R} + {L} + {b} = {aA}"
            # "{A} + {A} + {c} = {bG}"
            # "{N} + {S} + {d} = {cE}"
            # "{K} + {S}       = {dR}"
            
            notIn = lambda w: [x for x in range(1,10) if x not in w]
            
            for A in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
              for c in [0, 1]:
                x = 2*A + c; G = x % 10
                if G != A and G in {2,3,4,5,6,7,8}:
                  b = x // 10
                  for D in [1, 2, 3, 4, 5, 6, 7]:
                    if D != A and G > D:
                      for a in [0, 1]:
                        L = D + G + a
                        if L != A and L < 10:
                          for R in notIn({D,G,L,A}):
                            x = R + L + b
                            if x % 10 == A and x // 10 == a:
                              for K in notIn({D,G,L,A,R}):
                                for S in notIn({D,G,L,A,R,K}):
                                  x = K + S
                                  if x % 10 == R:
                                    d = x // 10
                                    for N in notIn({D,G,L,A,R,K,S}):
                                      x = N + S + d
                                      E = x % 10
                                      if E not in {D,G,L,A,R,K,S,N,0}:
                                        if x // 10 == c:
                                          print(" ALE DRANK GLASS LAGER\n",
                                                " ---------------------\n",
                                                A*100+L*10+E,
                                                D*10000+R*1000+A*100+N*10+K,
                                                G*10000+L*1000+A*100+S*11,
                                                L*10000+A*1000+G*100+E*10+R)
             

            Like

          • Jim Randell's avatar

            Jim Randell 10:00 am on 24 March 2021 Permalink | Reply

            I’ve added the [[ SubstitutedExpression.split_sum() ]] method to enigma.py, so you don’t have to split out the columns manually:

            from enigma import SubstitutedExpression
            
            # split the sum into columns
            args = SubstitutedExpression.split_sum("DRANK + GLASS = LAGER")
            
            # none of the symbols in the original sum can be zero
            args.d2i.update((0, x) for x in args.symbols)
            
            # solve the sum, with additional condition: (G > D)
            SubstitutedExpression(
              args.exprs + ["{G} > {D}"],
              distinct=args.symbols,
              d2i=args.d2i,
              answer="{ALE}",
              template=args.template + " ({G} > {D}) ({ALE})",
              solution=args.symbols,
            ).run()
            

            Like

          • Jim Randell's avatar

            Jim Randell 2:55 pm on 22 October 2022 Permalink | Reply

            We can now call the [[ SubstitutedExpression.split_sum ]] solver directly from a run file. Giving us a solution that is both short and fast.

            The following run file executes in 68ms, and the internal runtime of the generated program is just 139µs.

            Run: [ @replit ]

            #! python3 -m enigma -rr
            
            SubstitutedExpression.split_sum
            
            --invalid="0,ADEGKLNRS"  # none of the digits are 0
            --answer="ALE"
            
            "{DRANK} + {GLASS} = {LAGER}"
            
            --extra="{G} > {D}"
            

            Like

    • Frits's avatar

      Frits 11:28 am on 14 August 2020 Permalink | Reply

      from enigma import join
      
      print("ALE DRANK GLASS LAGER")
      print("---------------------")
      for G in range(1,10):
        for D in range(1,10):
          if D in {G}: continue
          if G <= D: continue
          for L in range(1,10):
            if L in {G,D}: continue
            if D + G != L and D + G + 1 != L: continue
            for A in range(1,10):
              if A in {G,D,L}: continue
              if A + A != 10 + G and A + A != G and A + A != 9 + G and A + A + 1 != G: continue
              for S in range(1,10):
                if S in {G,D,L,A}: continue
                GLASS = G*10000+L*1000+A*100+S*10+S
                for K in range(1,10):
                  if K in {G,D,L,A,S}: continue
                  for R in range(1,10):
                    if R in {G,D,L,A,S,K}: continue
                    if K + S != 10 + R and K + S != R: continue
                    for N in range(1,10):
                      if N in {G,D,L,A,S,K,R}: continue
                      DRANK = D*10000+R*1000+A*100+N*10+K
                      for E in range(1,10):
                        if E in {G,D,L,A,S,K,R,N}: continue
                        LAGER = L*10000+A*1000+G*100+E*10+R
                        if DRANK + GLASS != LAGER: continue
                        print(join([A,L,E]),join([D,R,A,N,K]),join([G,L,A,S,S]),join([L,A,G,E,R]))
      

      Like

      • Frits's avatar

        Frits 12:52 pm on 14 August 2020 Permalink | Reply

        Fastest solution so far and more concise:

        # range 1,2,..,9 without elements in set w
        notIn = lambda w: [x for x in range(1,10) if x not in w]
        
        print("ALE DRANK GLASS LAGER")
        print("---------------------")
        for D in range(1,10):
          for G in notIn({D}):
            if G < D: continue
            for L in notIn({D,G}):
              if D + G != L and D + G + 1 != L: continue
              for A in notIn({D,G,L}):
                if A + A != 10 + G and A + A != G and \
                   A + A != 9 + G and A + A + 1 != G: continue
                for S in notIn({D,G,L,A}):
                  GLASS = G*10000+L*1000+A*100+S*10+S
                  for K in notIn({D,G,L,A,S}):
                    for R in notIn({D,G,L,A,S,K}):
                      if K + S != 10 + R and K + S != R: continue
                      for N in notIn({D,G,L,A,S,K,R}):
                        DRANK = D*10000+R*1000+A*100+N*10+K
                        for E in notIn({D,G,L,A,S,K,R,N}):
                          LAGER = L*10000+A*1000+G*100+E*10+R
                          if DRANK + GLASS != LAGER: continue
                          print(A*100+L*10+E, DRANK, GLASS, LAGER)
        
        # ALE DRANK GLASS LAGER
        # ---------------------
        # 392 14358 79366 93724
        

        Like

        • Jim Randell's avatar

          Jim Randell 1:12 pm on 14 August 2020 Permalink | Reply

          @Frits: You should be able to do without the loop for E.

          Once you have determined the other letters there is only one possible value for E determined by the fact: DRANK + GLASS = LAGER.

          This is one of the things the [[ SubstitutedExpression() ]] solver does to improve its performance. (See: Solving Alphametics with Python, Part 2.

          Not a huge win in this case (as there is only a single value left to be E), but generally quite useful technique.

          Like

          • Jim Randell's avatar

            Jim Randell 3:31 pm on 14 August 2020 Permalink | Reply

            … and if we use this technique on the equivalent expression:

            LAGERGLASS = DRANK

            We only need to consider the 6 symbols on the left, which gives a much more respectable execution time of 164ms for the following short run file:

            #! python -m enigma -rr
            
            SubstitutedExpression
            
            --digits="1-9"
            --answer="ALE"
            
            # instead of: "DRANK + GLASS = LAGER"
            "LAGER - GLASS = DRANK"
            
            "G > D"
            

            Like

    • GeoffR's avatar

      GeoffR 3:56 pm on 14 August 2020 Permalink | Reply

      The following link is an alphametic solver :

      http://www.tkcs-collins.com/truman/alphamet/alpha_solve.shtml

      (Enter DRANK and GLASS in the left hand box and LAGER in the right hand box)

      It outputs eight solutions, five of whch contain zeroes, which are invalid for this teaser.

      It also outputs three non-zero solutions, only one of which has GLASS > DRANK, which is the answer:
      i.e. 14358 + 79366 = 93724, from which ALE = 392

      Like

    • GeoffR's avatar

      GeoffR 4:42 pm on 14 August 2020 Permalink | Reply

      import time
      start = time.time()
      
      from itertools import permutations
      
      for Q in permutations('123456789'):
          d,r,a,n,k,l,s,g,e = Q
          drank = int(d+r+a+n+k)
          glass = int(g+l+a+s+s)
          if glass > drank:
              lager = int(l+a+g+e+r)
              if drank + glass == lager:
                  ale = int(a+l+e)
                  print(f"ALE = {ale}")
                  t1 = time.time() - start
                  print(t1, "sec") 
                  print(f"Sum is {drank} + {glass} = {lager}")
      
      # ALE = 392
      # 0.0311 sec
      # Sum is 14358 + 79366 = 93724
      
      
      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:D; var 1..9:R; var 1..9:A; 
      var 1..9:N; var 1..9:K; var 1..9:G;
      var 1..9:L; var 1..9:S; var 1..9:E; 
      
      constraint all_different([D,R,A,N,K,G,L,S,E]);
      
      var 100..999: ALE = 100*A + 10*L + E;
      
      var 10000..99999: DRANK = 10000*D + 1000*R + 100*A + 10*N + K;
      var 10000..99999: GLASS = 10000*G + 1000*L + 100*A + 11*S;
      var 10000..99999: LAGER = 10000*L + 1000*A + 100*G + 10*E + R;
      
      constraint GLASS > DRANK /\ DRANK + GLASS == LAGER;
      
      solve satisfy;
      
      output ["Sum is " ++ show(DRANK) ++ " + " ++ show(GLASS)
      ++ " = " ++ show(LAGER) ++ "\nALE = " ++ show(ALE) ];
      
      % Sum is 14358 + 79366 = 93724
      % ALE = 392
      % time elapsed: 0.04 s
      %----------
      %==========
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 10:06 pm on 18 August 2020 Permalink | Reply

      % A Solution in MiniZinc - adding columns with carry digits
      include "globals.mzn";
      
      % D R A N K
      % G L A S S +
      %----------
      % L A G E R
      
      var 1..9:d; var 1..9:r; var 1..9:a; 
      var 1..9:n; var 1..9:k; var 1..9:g;
      var 1..9:l; var 1..9:s; var 1..9:e;
      
      var 0..1: carry1; var 0..1: carry2;
      var 0..1: carry3; var 0..1: carry4;
      
      var 100..999: ale = 100*a + 10*l + e;
      
      constraint all_different([d,r,a,n,k,g,l,s,e]);
      
      % adding columns, working from right side
      constraint (k + s) mod 10 == r           % column 1
      /\ (k + s) div 10 == carry1;
      
      constraint (n + s + carry1) mod 10 == e  % column 2
      /\ (n + s + carry1) div 10 == carry2;
      
      constraint (a + a + carry2) mod 10 == g  % column 3
      /\ (a + a + carry2) div 10 == carry3;
      
      constraint (r + l + carry3) mod 10 == a  % colmun 4
      /\ (r + l + carry3) div 10 == carry4;
      
      constraint (d + g + carry4) == l;        % column 5
      
      % number glass > drank
      constraint (10000*g + 1000*l + 100*a + 11*s)
      > (10000*d + 1000*r + 100*a + 10*n + k);
      
      solve satisfy;
      
      output ["ALE = " ++ show(ale) ];
      
      % ALE = 392
      % time elapsed: 0.04 s
      % ----------
      % ==========
      
      
      

      Like

      • John Crabtree's avatar

        John Crabtree 7:32 pm on 20 August 2020 Permalink | Reply

        I solved this by hand. Looking at the first three columns, if NK + SS < 100, by digital roots, D + R + A = 0 mod 9. If NK + SS > 100, D + R + A = 8 mod 9.
        For each condition and each A, G is fixed. Then for each possible D (<G and G + D < 10), R is fixed, and then so is L. I only found four sets of (A, G, D, R and L), ie (3, 6, 1, 5 8), (3, 6, 2, 4, 9), (1, 3, 2, 5, 6) and (3, 7, 1, 4, 9) for which it was necessary to evaluate the other letters.
        Could this be the basis for a programmed solution?

        Like

  • Unknown's avatar

    Jim Randell 11:43 am on 11 August 2020 Permalink | Reply
    Tags:   

    Brainteaser 1839: It’s a knockout 

    From The Sunday Times, 14th December 1997 [link]

    Sixteen teams, A-P, entered a knockout football competition. In none of the matches did any team score more than five goals, no two matches had the same score, and extra time ensured that there were no draws.

    The table shows the total goals, for and against, for each team:

    My own team, K, was knocked out by the team who were the eventual champions.

    Who played whom in the semi-finals, and what were the scores.

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

    [teaser1839]

     
    • Jim Randell's avatar

      Jim Randell 11:44 am on 11 August 2020 Permalink | Reply

      When we look at the table we can spot teams that might have been knocked out in the first round. The number of goals for and against must be different (no draws) and neither must be more than 5. We can then choose 8 such teams, and their values in the table give the scores in the matches. We then choose 8 of the remaining teams to be their opponents and see if we can construct a new table for the next round of the tournament (which will have half as many matches). And so on until we reach the last 2 teams.

      This Python 3 program runs in 1.2s.

      Run: [ @repl.it ]

      from enigma import subsets, diff, ordered, flatten, join, printf
      
      # the total goals scored/conceded
      gs0 = dict(
        A=(5, 6), B=(14, 0), C=(15, 8), D=(2, 4), E=(1, 3), F=(0, 5), G=(4, 6), H=(1, 4),
        I=(1, 2), J=(12, 12), K=(6, 5), L=(0, 1), M=(4, 5), N=(1, 5), O=(2, 3), P=(7, 6),
      )
      
      # do the list of <ls>, <ws> make a set of matches using goals scored/conceded in <gs>?
      # return list of (<winner> + <loser>, (<scored>, <conceded>))
      # and a new version of <gs> for the next round
      def matches(gs, ls, ws):
        ms = list()
        d = dict()
        for (w, l) in zip(ws, ls):
          ((fw, aw), (fl, al)) = (gs[w], gs[l])
          (f, a) = (fw - al, aw - fl)
          if f < 0 or a < 0: return (None, None)
          ms.append((w + l, (al, fl)))
          d[w] = (f, a)
        return (ms, d)
      
      # allowable score:
      # no side scores more than 5 goals, and no draws
      score = lambda x, y: not(x > 5 or y > 5 or x == y)
      
      # play out a tournament
      # k = number of matches in this round
      # gs = total number of goals scored in the remaining rounds
      # ms = match outcomes in each round
      # ss = scores used so far
      def tournament(k, gs, ms=list(), ss=set()):
        # how many matches?
        if k == 1:
          # there should be 2 teams
          (x, y) = gs.keys()
          ((fx, ax), (fy, ay)) = (gs[x], gs[y])
          if fx == ay and ax == fy and score(fx, ax):
            yield ms + [[(x + y, (fx, ax)) if fx > ax else (y + x, (fy, ay))]]
        else:
          # teams that can be knocked out in this round
          teams = list(k for (k, (f, a)) in gs.items() if score(f, a))
          # choose the losers
          for ls in subsets(teams, size=k):
            # choose the winners from the remaining teams
            for ws in subsets(diff(gs.keys(), ls), size=k, select="P"):
              (ms_, gs_) = matches(gs, ls, ws)
              if not gs_: continue
              ss_ = set(ordered(*xs) for (ts, xs) in ms_)
              if ss_.intersection(ss): continue
              yield from tournament(k // 2, gs_, ms + [ms_], ss.union(ss_))
              
      
      # there are 8 matches in the first round
      for ms in tournament(8, gs0):
        # check K is knocked out by the champions
        champ = ms[-1][0][0][0]
        if not(champ + 'K' in (ts for (ts, xs) in flatten(ms))): continue
        # output the tournament
        for (i, vs) in enumerate(ms, start=1):
          printf("Round {i}: {vs}", vs=join((f"{t[0]}v{t[1]} = {x}-{y}" for (t, (x, y)) in sorted(vs)), sep="; "))
        printf()
      

      Solution: The semi-finals were: B vs K = 3-0; C vs J = 5-3.

      So the final was: B vs C = 2-0, and B were the champions.

      The strategy given above finds two possible tournaments, but only in one of them is K knocked out by the eventual champions.

      Like

      • Frits's avatar

        Frits 8:13 pm on 18 August 2020 Permalink | Reply

        @Jim

        Did you also solve the puzzle manually?

        It is not so difficult to determine who is playing whom in the semi-finals.
        The scores are not easy to determine.

        Like

        • Jim Randell's avatar

          Jim Randell 12:00 pm on 19 August 2020 Permalink | Reply

          @Frits: I didn’t do a manual solution for this one. But I imagine the fact there are only 15 possible scores for the 15 games must make things a bit easier. (This is something I didn’t use in my program).

          Like

  • Unknown's avatar

    Jim Randell 8:53 am on 9 August 2020 Permalink | Reply
    Tags: by: T. J. Gaskell   

    Brain-Teaser 5: Independent witness 

    From The Sunday Times, 26th March 1961 [link]

    This problem and its result illustrate why in courts of Law leading questions are frowned on and why two independent witnesses constitute strong evidence.

    A pack of cards is shuffled and cut at random. The cut card is shown to two persons who tell the truth with probability of a half. They are asked to name the card, and independently, the both answer: “Five of Diamonds”.

    What is the probability that the card is the Five of Diamonds?

    [teaser5]

     
    • Jim Randell's avatar

      Jim Randell 8:54 am on 9 August 2020 Permalink | Reply

      For any particular card there is a 1/52 probability of it being chosen.

      If the witnesses are independent their behaviours can be: both true (probability 1/4), both false (probability 1/4), one true and one false (probability 1/2).

      If the card chosen is 5D, the only way they will both say 5D is if they are both true (probability 1/4).

      So the probability of 5D being chosen and both witnesses saying 5D is (1/52)×(1/4) = 1/208 = 51/10608

      If the card chosen is not 5D, then the only way they will both say 5D is if they are both false (probability 1/4), and they both choose 5D as a random incorrect response (probability 1/51 for each witness).

      So the probability of a card other than 5D being chosen and both witnesses saying 5D is (51/52)×(1/4)×(1/51)² = 1/10608.

      So if we observe both witnesses saying 5D it is 51 times more likely that that the card is 5D than that it isn’t.

      Solution: The probability that the card is the Five of Diamonds is 51/52 (i.e. about 98.1%).


      The following program simulates the scenario.

      import random
      from itertools import product
      from enigma import irange, diff, fdiv, printf
      
      # make a pack of cards
      cards = list(v + s for (v, s) in product("A123456789XJQK", "CDHS"))
      
      # count outcomes
      n = [0, 0]
      
      for _ in irange(1, 10000000):
        # choose a card
        X = random.choice(cards)
        # choose a behaviour for the witnesses
        w1 = random.choice((True, False))
        w2 = random.choice((True, False))  
        # choose a response from the witness
        W1 = random.choice([X] if w1 else diff(cards, [X]))
        W2 = random.choice([X] if w2 else diff(cards, [X]))
        # record outcomes where both witnesses respond 5D
        if W1 == W2 == "5D":
          n[X == "5D"] += 1
      
      (x, t) = (n[1], sum(n))
      printf("p = {x}/{t} ({p:.2%})", p=fdiv(x, t))
      

      It takes 10.6s to run 10,000,000 random trials, and produces the following result:

      % python teaser5.py
      p = 44827/45647 (98.20%)
      

      Like

  • Unknown's avatar

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

    Teaser 3020: Baz’s bizarre arrers 

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

    “Bizarrers” dartboards have double and treble rings and twenty sectors ordered as on this normal dartboard [shown above]. However, a sector’s central angle (in degrees) is given by (100 divided by its basic score). The 20 sector incorporates the residual angle to complete 360º.

    Each player starts on 501 and reduces this, eventually to 0 to win. After six three-dart throws, Baz’s seventh could win. His six totals were consecutive numbers. Each three-dart effort lodged a dart in each of three clockwise-adjacent sectors (hitting, in some order, a single zone, a double zone and a treble zone). [Each] three-sector angle sum (in degrees) exceeded that total.

    The sectors scored in are calculable with certainty, but not how many times hit with certainty, except for one sector.

    Which sector?

    This puzzle uses a different spelling for “arraz” from the previous puzzle involving Baz (Teaser 2934).

    [teaser3020]

     
    • Jim Randell's avatar

      Jim Randell 5:21 pm on 7 August 2020 Permalink | Reply

      This Python program runs in 52ms.

      Run: [ @replit ]

      from fractions import Fraction as F
      from collections import defaultdict
      from itertools import product
      from enigma import tuples, subsets, multiset, seq_all_same, intersect, join, printf
       
      # sectors of the dartboard
      board = [ 20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5 ]
      
      # map scores from 1 to 19 to sector angles (in degrees)
      angle = dict((n, F(100, n)) for n in board if n != 20)
      # the 20 sector completes the board
      angle[20] = 360 - sum(angle.values())
       
      # consider 3 adjacent sectors
      d = defaultdict(list) # store the results by the score (single, double, treble)
      for ss in tuples(board + board[:2], 3):
        # calculate the sector angle sum
        a = sum(angle[s] for s in ss)
        # choose multipliers for the scores
        for ms in subsets(ss, size=len, select="P"):
          # calculate the total
          t = sum(s * m for (m, s) in enumerate(ms, start=1))
          # which is less than the angle sum
          if not (t < a): continue
          d[t].append(ms)
      
      # look for 6 consecutive scores
      for ts in tuples(sorted(d.keys()), 6):
        if not (ts[0] + 5 == ts[-1]): continue
        # produce possible sets of scoring sectors
        mss = list(multiset.from_seq(*ss) for ss in product(*(d[t] for t in ts)))
        # each set of sectors should be the same
        if not seq_all_same(set(m.keys()) for m in mss): continue
        # look for common (sector, count) pairs for all possible scores
        ps = intersect(m.items() for m in mss)
        if len(ps) == 1:
          # output solution
          for (s, n) in ps:
            printf("sector = {s} [count = {n}]")
          printf("  scores = {ts}")
          for t in ts:
            printf("    {t}: {ss}", ss=join(d[t], sep=" "))
      

      Solution: We can say for certain that the 4 sector was hit twice.

      Baz’s 6 scores were (in consecutive order): 58, 59, 60, 61, 62, 63.

      So he has 138 left to score. (Which is achievable in many ways with 3 darts (assuming standard rules of darts apply), for example: treble 20, treble 20, double 9).

      Possible ways to make the first 6 scores with consecutive sectors are:

      58 = (single 3, double 2, treble 17)
      59 = (single 20, double 18, treble 1); (single 10, double 2, treble 15); (single 2, double 3, treble 17)
      60 = (single 4, double 1, treble 18)
      61 = (single 18, double 20, treble 1)
      62 = (single 2, double 15, treble 10)
      63 = (single 1, double 4, treble 18)

      We don’t know which of the alternatives makes up the 59, but whichever is chosen the 4 sector is used twice (in scoring 60 and 63), and the full list of sectors used is: 1, 2, 3, 4, 10, 15, 17, 18, 20.

      There is only one other possible consecutive set of consecutive scores: (41, 42, 43, 44, 45, 46), but this leaves a 240 finish which is not possible with 3 darts (at least not using the standard rules of darts, but maybe there is a way to score at least 80 with one dart on this strange dartboard, or another rule that would allow Baz to win that we don’t know about). But it also turns out that with this set of scores we cannot be certain which sectors were definitely used to make the scores either, so this possibility is excluded without us needing to know the exact rules for the variation of darts being used in the puzzle.

      Like

      • Frits's avatar

        Frits 12:17 pm on 9 August 2020 Permalink | Reply

        It wasn’t easy for me to understand this puzzle. Only after seeing the code it became clear.

        “After six three-dart throws, Baz’s seventh could win”.

        You don’t seem to check that after six three-dart throws Baz has scored at least 331 points.

        With this extra check the 41-46 range can be eliminated earlier (ts[0] ≥ 52).

        Like

        • Jim Randell's avatar

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

          @Frits: There are two possible sets of six consecutive scores, but one is eliminated by the requirement that we can be certain of the sectors that were definitely used to make the scores. It also turns out that it would not be possible to win with three darts from this position (at least on a normal dart board), and it is possible for the other set of six consecutive scores (in many ways, which can easily be seen), so I didn’t incorporate that test into my code, as it seemed to be superfluous (although probably useful in a manual solution).

          Like

          • Frits's avatar

            Frits 5:17 pm on 9 August 2020 Permalink | Reply

            Agree.

            If the six totals had come out as f.i. 54-59 the score after six three-dart throws would have been 339 leaving 162 which is not a finish at darts.

            If the program is intended to find a quick solution which has to be checked manually that’s OK with me.

            Like

            • Jim Randell's avatar

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

              Here is a modified version of my program that ensures that Baz can finish on his 7th throw [ @replit ].

              Although as previously noted, this doesn’t change the solutions found.

              The puzzle text only states that the remaining total should be reduced to 0, so that’s what I’ve implemented. No requirement for the final dart to be a double, and as no inner or outer bull is mentioned I have not included them either.

              New Scientist Puzzle #06 explores scores achievable with n darts (on a standard dartboard).

              Like

    • Robert Brown's avatar

      Robert Brown 1:07 am on 10 August 2020 Permalink | Reply

      That total doesn’t work. You need 58-63, which can be made 3 different ways. Total is 363 leaving 138 which can be finished with any 3 darts that sum to 46, all of which are trebles. It’s quite quick to manually check the 3 different sequences: there’s one sector that appears the same number of times in each sequence, which is the required answer.

      Like

      • Jim Randell's avatar

        Jim Randell 8:55 am on 10 August 2020 Permalink | Reply

        @Robert: I’m not sure what total you are objecting to. Also I understood that in standard rules of darts you have to finish on a double. (Although in the version of the game used in the puzzle we are not told what the rules for finishing are. Fortunately though, of the two possible consecutive sequences of scores one of them is ruled out for reasons that are explained in the puzzle, so we don’t need to know the rules for finishing. We are just told that Baz could finish on his next throw, which is nice for him, but inconsequential for us).

        Like

    • Robert Brown's avatar

      Robert Brown 8:22 pm on 10 August 2020 Permalink | Reply

      I don’t know the rules of darts! The total that I found (58,59,60,61,62,63) needs 138 to finish, so I assumed finishing on trebles was ok. But they tell me that a bull’s eye counts as 50, so you can finish with 2 bull’s eyes & a double 19.
      I was objecting to Frits’s 6 totals (54-59) which I don’t see as a possibility. The important thing about my sequence is that there are 3 way to make 59, which changes the number of times that each sector is called, except for one which is the answer.

      Like

      • Jim Randell's avatar

        Jim Randell 10:23 pm on 10 August 2020 Permalink | Reply

        @Robert: Right. I believe Frits only gave those numbers as a hypothetical example to illustrate his point.

        You have found the right set of scores that leads to the answer. And fortunately (as I said above) you don’t need to know the rules of darts to eliminate the only other possible consecutive sequence of scores.

        Like

    • GeoffR's avatar

      GeoffR 8:40 am on 12 August 2020 Permalink | Reply

      I had the idea of defining a valid function for three adjacent sectors – i.e.valid if the six totals were less than the sum of the three angles. This enabled the code below to find the unique six consecutive totals, but not the single requested sector.

      
      from itertools import permutations
      from collections import defaultdict
      D = defaultdict(set)
      
      L, L2 = [], []
      
      # function to check if a three-sector is valid
      # a, b, c are adjacent sector numbers
      def is_valid(a, b, c):
        asum = sum(100 / x for x in (a, b, c))
        ps = []
        for p3 in permutations((a, b, c)):
          score = sum(m * x for m, x in zip((1, 2, 3), p3))
          if score < asum:
            ps.append(score)
        return ps
      
      # the scores starting at north and running clockwise
      db = (20, 1, 18, 4, 13, 6, 10, 15,  2, 17,
            3, 19, 7, 16, 8, 11, 14, 9, 12, 5)
      
      for p in range(20):
        p3 = tuple((p + i) % 20 for i in (0, 1, 2))
        sc = tuple(db[i] for i in p3)
        L.append(sc)
      
      for x in range(20):
        a, b, c = L[x]
        s6 = is_valid(a, b, c)
        if s6:
          L2.append(s6)
      
      for p in permutations(L2,6):
        L1, L2, L3, L4, L5, L6 = p
        
        L22 = sorted(L1 + L2 + L3 + L4 + L5 + L6)
        L22 = sorted(list(set(L22)))
        
        for b in range(0, len(L22)-6):
          b1  = L22[b:b+6]
          # split slice into six totals
          a0, b0, c0, d0, e0, f0 = b1
          # total of six scores
          s = a0 + b0 + c0 + d0 + e0 + f0
          # total must be less than 180 to close with the 7th set of 3 darts
          # else there is another set of 6 consecutive numbers in the forties
          if 501 - s > 180:
            continue
          # check six numbers are consecutive
          if b0-a0 == c0-b0 == d0-c0 == e0-d0 == f0-e0 == 1:
            D[s].add ((a0,b0,c0,d0,e0,f0))
      
      for k,v in D.items():
        print(f"Sum of six 3 dart totals = {k}")
        print(f"Unique six totals are {v}")
        sc_left = 501 - k
        print(f"Seventh group of three darts score = {sc_left}")
        print(f"7th 3 darts group could be 3*18 + 3*20 + 2*12 = 138")
        print(f"Finishing on a double!")
      
      # Sum of six 3 dart totals = 363
      # Unique six totals are {(58, 59, 60, 61, 62, 63)}
      # Seventh group of three darts score = 138
      # 7th 3 darts group could be 3*18 + 3*20 + 2*12 = 138
      # Finishing on a double!
      
      
      

      I looked at a print out of Jim’s dictionary which gives the same six consecutive totals and the triples used in each of the numbers 58,59,60,61, 62 and 63.

      scores = (58, 59, 60, 61, 62, 63)
      58: (3, 2, 17)
      59: (20, 18, 1) (10, 2, 15) (2, 3, 17)
      60: (4, 1, 18)
      61: (18, 20, 1)
      62: (2, 15, 10)
      63: (1, 4, 18)

      The number 59 has three triples and all these digits appear in the numbers 58,60,61,62 and 63. The total of 363 can therefore be made up three ways. All three ways require the numbers 60 and 63, which also contain the digit ‘4’, so we can say thet the digit ‘4’ definitely occurs twice.

      A minor variation to my programme found the only eight valid sectors were:
      (20, 1, 18), (1, 18, 4), (4, 13, 6), (10, 15, 2),
      (15, 2, 17), (2, 17, 3), (3, 19, 7), (5, 20, 1)

      Jim’s dictionary above show that only four of these eight triples were needed for the final solution;
      i.e (2,3,17), (20,18,1), (10,2,15), (4,1,18)

      Like

  • Unknown's avatar

    Jim Randell 9:26 am on 6 August 2020 Permalink | Reply
    Tags:   

    Teaser 2678: Map snap 

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

    I have two rectangular maps depicting the same area, the larger map being one metre from west to east and 75cm from north to south. I’ve turned the smaller map face down, turned it 90 degrees and placed it in the bottom corner of the larger map with the north-east corner of the smaller map touching the south-west corner of the larger map. I have placed a pin through both maps, a whole number of centimetres from the western edge of the larger map. This pin goes through the same geographical point on both maps. On the larger map 1cm represents 1km. On the smaller map 1cm represents a certain whole number of kilometres …

    … how many?

    [teaser2678]

     
    • Jim Randell's avatar

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

      See also: Enigma 1177.

      If we suppose the smaller map has a scale of k kilometres per centimetre, then the dimensions of the smaller map are (100 / k) (west to east) and (75 / k) (south to north).

      If the point we are interested on the map has an easting of x and a northing of y (measured from the SW corner of the maps), then we have the following equations:

      k.x = 75 − y
      k.y = 100 − x

      If we consider possible integer values for k, we can determine values for x and y.

      y = 75 − k.x
      k.y = 75k − k².x
      100 − x = 75k − k².x
      x(k² − 1) = 75k − 100
      x = 25 (3k − 4) / (k² − 1)

      So we can look for values of k that give an integer value for x.

      This Python program runs in 49ms.

      Run: [ @replit ]

      from enigma import (irange, div, fdiv, printf)
      
      for k in irange(2, 75):
        x = div(25 * (3 * k - 4), k * k - 1)
        if x is None: continue
        y = fdiv(100 - x, k)
        printf("k={k} x={x} y={y:g}")
      

      Solution: The scale of the smaller map is 1cm = 6km.

      So the smaller map measures 16.67 cm by 12.5 cm.

      The marked point is 10 km from the western edge of the maps, and 15 km from the southern edge of the maps.

      On the larger map the distances are (10cm, 15cm) on the smaller map the distances are (1.67cm, 2.5cm).

      Like

  • Unknown's avatar

    Jim Randell 5:12 pm on 4 August 2020 Permalink | Reply
    Tags:   

    Teaser 2700: Spell check 

    From The Sunday Times, 22nd June 2014 [link] [link]

    This is Teaser 2700. The number 2700 is not particularly auspicious,  but it does have one unusual property. Notice that if you write the number in words and you also express the number as a product of primes you get:

    TWO THOUSAND SEVEN HUNDRED = 2 2 3 3 3 5 5

    The number of letters on the left equals the sum of the factors on the right! This does happen occasionally. In particular it has happened for one odd-numbered Teaser in the past decade.

    What is the number of that Teaser?

    [teaser2700]

     
    • Jim Randell's avatar

      Jim Randell 5:13 pm on 4 August 2020 Permalink | Reply

      There is a handy utility function [[ int2words() ]] in the enigma.py library, that converts a number to it’s “standard” English equivalent.

      This Python program runs in 53ms.

      Run: [ @repl.it ]

      from enigma import irange, int2words, factor, printf
      
      # check odd numbers
      for n in irange(2699, 1, step=-2):
        # count the letters
        s = int2words(n)
        c = sum(x.isalpha() for x in s)
        # sum the factors
        fs = factor(n)
        t = sum(fs)
        if c == t:
          printf("{n} = \"{s}\", {c} letters; factors = {fs}, sum = {t}")
          break
      

      Solution: Teaser 2401.

      2401 = “two thousand four hundred and one”, which has 28 letters, and:

      2401 factorises as (7, 7, 7, 7), which sums to 28.

      Earlier odd numbers with the same property: 1725, 1029, 693, 585, 363, 153, 121, 25.

      The next time it will occur is at 3087.

      Teaser 3087 should be published in November 2021.

      Like

  • Unknown's avatar

    Jim Randell 12:11 pm on 2 August 2020 Permalink | Reply
    Tags: by: J. D. Robson   

    Brain-Teaser 4: [Digit substitution] 

    From The Sunday Times, 19th March 1961 [link]

    The following is the test paper which Tom showed up:

    8 + 7 = 62
    59 + 4 = 50
    5 + 3 = 5
    11 × 1 = 55
    12 + 8 = 23

    Naturally Miss Monk put five crosses on it. But Tom was indignant; for he was a self-taught boy and proud of his achievement. After some persuasion his teacher agreed to test him orally in addition and multiplication. This time Tom answered every question correctly.

    Miss Monk was puzzled and then horrified when she discovered that although Tom had learnt to use arabic symbols he had ascribed to each an incorrect value. Fortunately he knew the meaning of + and × [and =].

    If Tom were presented with the written problem: 751 × 7, what would his answer be?

    This puzzle was originally published with no title.

    [teaser4]

     
    • Jim Randell's avatar

      Jim Randell 12:12 pm on 2 August 2020 Permalink | Reply

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

      The following Python program runs in 55ms.

      Run: [ @repl.it ]

      from enigma import (SubstitutedExpression, substitute, map2str, printf)
      
      # the 5 expressions
      exprs = [
        "{8} + {7} = {62}",
        "{59} + {4} = {50}",
        "{5} + {3} = {5}",
        "{11} * {1} = {55}",
        "{12} + {8} = {23}"
      ]
      
      # and the final expression
      expr = "{751} * {7}"
      
      # solve the expressions using the alphametic solver
      p = SubstitutedExpression(exprs, answer=expr, verbose=0)
      for (s, ans) in p.solve():
        # and convert the answer back into symbols
        r = dict((str(v), int(k)) for (k, v) in s.items())
        x = substitute(r, str(ans))
        # output solution
        printf("{expr} -> {texpr} = {ans} <- {{{x}}} / {s}", texpr=p.substitute(s, expr), s=map2str(s, sep=" ", enc=""))
      

      Solution: Tom’s written answer would be 0622.

      The map from Tom’s digits to standard digits is:

      0→7
      1→3
      2→4
      3→0
      4→2 (or 5)
      5→9
      6→1
      7→8
      8→6
      9→5 (or 2)

      Tom uses 4 and 9 for the standard digits 2 and 5, but we can’t determine which way round.

      To work out the answer, Tom would interpret the sum: 751 × 7, as: 893 × 8, which gives a result of 7144, but Tom would write this down as: 0622.

      Like

    • GeoffR's avatar

      GeoffR 8:38 pm on 2 August 2020 Permalink | Reply

      
      # Equations
      # use a + b = cd 
      # for 8 + 7 = 62
      
      # use ef + g = eh
      # for 59 + 4 = 50
      
      # use e + j = e
      # for 5 + 3 = 5
      
      # use ii * i = ee 
      # for 11 * 1 = 55
      
      # use id + a = dj
      # for 12 + 8 = 23
      
      # use bei * b = 751 * 7 for Tom's result
      
      from itertools import permutations
      
      digits = set(range(10))
      
      # 1st stage permutation
      for p1 in permutations (digits, 4):                       
        a, b, c, d = p1
        if a + b != 10*c + d: continue
      
        # 2nd stage permutation
        qs = digits.difference(p1)
        for p2 in permutations(qs, 4):
          e, f, g, h = p2
          if e == 0: continue
          if 10*e + f + g != 10*e + h: continue
      
          # 3rd stage permutation
          qs2 = qs.difference(p2)
          for p3 in permutations(qs2):
            i, j = p3
            
            if e + j != e: continue
            if 11 * i * i != 11 * e: continue
            if 10*i + d + a == 10*d + j:
                # Tom's answer
                res =(100*b + 10*e + i) * b
                print(f"Tom's answer = {res}")
                
                print(a,b,c,d,e,f,g,h,i,j)
                #     6 8 1 4 9 5 2 7 3 0
      
                # Tom's answer is 7144 = hcdd in solution,
                # which is 0622 with variables used in original equations
           
                print(f"Equation 1: {a} + {b} = {10*c+d}")
                print(f"Equation 2: {10*e+f} + {g} = {10*e+h}")
                print(f"Equation 3: {e} + {j} = {e}")
                print(f"Equation 4: {11*i} * {i} = {11*e}")
                print(f"Equation 5: {10*i+d} + {a} = {10*d+j}")
                print()
                
      # Actual Equations
      # Equation 1: 6 + 8 = 14
      # Equation 2: 92 + 5 = 97 or 95 + 2 = 97
      # Equation 3: 9 + 0 = 9
      # Equation 4: 33 * 3 = 99
      # Equation 5: 34 + 6 = 40
      
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:28 pm on 31 July 2020 Permalink | Reply
    Tags: by: Bill Scott   

    Teaser 3019: William’s prime 

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

    William was searching for a number he could call his own. By consistently replacing digits with letters, he found a number represented by his name: WILLIAM.

    He noticed that he could break WILLIAM down into three smaller numbers represented by WILL, I and AM, where WILL and AM are prime numbers.

    He then calculated the product of the three numbers WILL, I and AM.

    If I told you how many digits there are in the product, you would be able to determine the number represented by WILLIAM.

    What number is represented by WILLIAM?

    [teaser3019]

     
    • Jim Randell's avatar

      Jim Randell 4:40 pm on 31 July 2020 Permalink | Reply

      We can use the [[ SubstitutedExpresison() ]] solver to solve the alphametic problem, and [[ filter_unique() ]] function to collect the answer (both from the enigma.py library).

      This Python program runs in 71ms.

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, filter_unique, nsplit, unpack, printf)
       
      # the alphametic problem
      p = SubstitutedExpression(
        [ "is_prime(WILL)", "is_prime(AM)" ],
        answer="(WILLIAM, WILL * I * AM)",
      )
       
      # find results unique by the length of the product in the answer
      rs = filter_unique(p.answers(verbose=0), unpack(lambda w, p: len(nsplit(p)))).unique
       
      # output solution
      for (w, p) in rs:
        printf("WILLIAM = {w} [product = {p}]")
      

      Solution: WILLIAM = 4177123.

      If the value of I is unconstrained there are 579 solutions to the alphametic puzzle. (If we don’t allow I to be prime this number is reduced to 369 solutions, but the answer to the puzzle remains unchanged).

      114 give a 1-digit product (when I is 0), 221 give a 7-digit product, 243 give a 6-digit product, and 1 solution gives a 5-digit product, so this gives us the answer to the problem.

      As suggested by the puzzle’s title, 4177123 is prime.


      Using the recently added [[ accumulate ]] parameter for the [[ SubstitutedExpression() ]] solver in the enigma.py library (and the even more recently added annotated return value from [[ filter_unique() ]]), we can write a run-file that solves this puzzle:

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "is_prime(WILL)"
      "is_prime(AM)"
      #"not is_prime(I)"  # [optional] can require I is not prime
      
      --answer="(WILLIAM, WILL * I * AM)"
      
      # find answers that are unique by the number of digits in the product
      --code="unique = lambda s: filter_unique(s, unpack(lambda w, p: ndigits(p))).unique"
      --accumulate="unique"
      --verbose=A
      

      Like

    • GeoffR's avatar

      GeoffR 8:53 am on 4 August 2020 Permalink | Reply

      I found I had to test for the three constraints individually (for the three constraints following this line) by remmimg out two of the three constraints to test the other constraint. Using all the constraints together gave multiple solutions.
      i.e.
      % Three tests for product – if 5, 6 or 7 digits long

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:W; var 1..9:I; var 1..9:L; 
      var 1..9:A; var 1..9:M;
      
      constraint all_different([W, I, L, A, M]);
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      var 1000..9999: WILL = 1000*W + 100*I + 11*L;
      
      var 10..99: AM = 10*A + M;
      
      var 1000000..9999999: WILLIAM = 1000000*W + 100000*I + 10000*L 
      + 1000*L + 100*I + 10*A + M;
      
      constraint is_prime(AM) /\ is_prime(WILL);
      
      % find min and max range of product WILL * I * AM
      % minimum value of product = 2133 * 1 * 45 = 95,985 
      % maximum value of product = 9877 * 8 * 65 = 5,136,040 
      % so product (WILL * I * AM) is either 5, 6 or 7 digits long
      
      var 95985..5136040: prod = WILL * I * AM; 
      
      % Three tests for product - if 5, 6 or 7 digits long 
      constraint (95985 < prod  /\ prod < 100000);    % 5 digits
      %constraint (100000 < prod /\ prod < 999999);   % 6 digits     
      %constraint (1000000 < prod /\ prod < 5136040); % 7 digits 
      
      solve satisfy;
      
      output["WILLIAM = " ++ show(WILLIAM) ++ 
      ", product = " ++ show(prod) ];
      
      

      This is a part manual / part programme solution, but it does get the answer – but it is not an ideal programme solution.

      @Jim: Do you think there could be a better fix in MiniZinc to get a full programme solution?
      Unfortunately we don’t seem to have the len(str(int)) fix in MiniZinc.

      Like

      • Jim Randell's avatar

        Jim Randell 11:53 am on 4 August 2020 Permalink | Reply

        @Geoff: MiniZinc does have log10(), which can be used to determine the number of digits in a number.

        But I would probably have MiniZinc generate all the solutions to the alphametic, and then use Python to analyse the output.

        Like

    • Frits's avatar

      Frits 12:03 am on 6 August 2020 Permalink | Reply

      Same as Jim’s but for me easier to understand.
      Ideal would be a kind of HAVING clause in the SubstitutedExpression
      (something like HAVING COUNT(len(nsplit(WILL * I * AM)) == 1).

      from enigma import SubstitutedExpression, nsplit, printf
        
      # the alphametic problem
      p = SubstitutedExpression(
        [ 
         "is_prime(WILL)", "is_prime(AM)",
        ],
        answer="(len(nsplit(WILL * I * AM)), WILLIAM, WILL * I * AM)",
        verbose=0,
      )
      
      # collect all answers and maintain frequency 
      answs = []
      mltpl = {} 
      for (_, ans) in p.solve(verbose=0): 
          answs.append(ans)
          mltpl[ans[0]] = ans[0] in mltpl
      
      # only print answers with a unique first element
      for (ans) in answs:
          if not mltpl[ans[0]]:  
              printf("WILLIAM = {ans[1]}, WILL * I * AM = {ans[2]}")
      

      Like

    • Frits's avatar

      Frits 3:19 pm on 6 August 2020 Permalink | Reply

      prms = {2, 3, 5, 7}
      # P1 = AM primes
      P1 = {x for x in range(13, 97, 2) if all(x % p for p in prms)} 
      
      prms = {2, 3, 5, 7, 11} | P1
      # P2 = WILL primes, exclude primes WXYZ where Y!=Z and where X = 0
      P2 = {x for x in range(1001, 9999, 2) if all(x % m for m in prms)
              and str(x)[-1] == str(x)[-2] and str(x)[1] != '0'}
                 
      
      answs = {}
      mltpl = {} 
      for p2 in P2:
          I = str(p2)[1]
          # WIL digits have to be different
          if len(list(set(str(p2)[0:3]))) < 3: continue
          for p1 in P1:
              # WILAM digits have to be different
              if len(list(set(str(p1)+str(p2)[0:3]))) < 5: continue
              prodlen = len(str(p2 * int(I) * p1))
              mltpl[prodlen] = prodlen in mltpl
              # store first prodlen entry
              if not mltpl[prodlen] : 
                 answs[prodlen] = str(p2) +" " + I + " " + str(p1)
      
      print("WILL I AM")
      for ans in answs:
          # only print answers with a unique product length
          if not mltpl[ans]:  
              print(answs[ans])
       

      Like

    • GeoffR's avatar

      GeoffR 11:05 pm on 2 December 2020 Permalink | Reply

      from collections import defaultdict
      from itertools import permutations
      from enigma import is_prime
      
      D = defaultdict(list)
      
      for P in permutations('123456789', 5):
          w, i, l, a, m = P
          will, am = int(w + i + l + l), int(a + m)
          if not is_prime(will) or not is_prime(am):
              continue
          i = int(i)
          product = str(will * i * am)
          D[len(product)] += [(will, i, am)]
      
      for k, v in D.items():
          # there must be a unique number of digits in the product
          if len(v) == 1:
              will, i, am = v[0][0], v[0][1], v[0][2]
              william = 1000 * will + 100 * i + am
              print(f"WILLIAM = {william}")
      
      # WILLIAM = 4177123
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 3:14 pm on 30 July 2020 Permalink | Reply
    Tags:   

    Teaser 2699: Low power 

    From The Sunday Times, 15th June 2014 [link] [link]

    I have written down an addition sum and then consistently replaced digits by letters, with different letters used for different digits. The result is:

    KILO + WATT = HOUR

    Appropriately enough, I can also tell you that WATT is a perfect power.

    Find the three-figure LOW number.

    [teaser2699]

     
    • Jim Randell's avatar

      Jim Randell 3:15 pm on 30 July 2020 Permalink | Reply

      Originally I wrote a program to collect the 4-digit powers and then use the [[ SubstitutedSum() ]] to solve the alphametic sum and look for solutions where WATT is one of the powers. This runs in 68ms.

      Run: [ @replit ]

      from enigma import (defaultdict, irange, inf, SubstitutedSum, join, printf)
      
      # collect powers x^k that are 4-digits
      powers = defaultdict(list)
      for k in irange(2, inf):
        if 2**k > 9999: break
        for x in irange(1, inf):
          n = x**k
          if n < 1000: continue
          if n > 9999: break
          powers[n].append((x, k))
      
      # solve the substituted sum
      p = SubstitutedSum(['KILO', 'WATT'], 'HOUR', d2i={ 0: 'KWHL' })
      for s in p.solve():
        WATT = int(p.substitute(s, "WATT"))
        ps = powers.get(WATT, None)
        if ps:
          LOW = p.substitute(s, "LOW")
          printf("LOW={LOW} [{p.text} -> {t}, WATT = {ps}]",
            t=p.substitute(s, p.text),
            ps=join((join((x, '^', k)) for (x, k) in ps), sep=", "),
          )
      

      Solution: LOW = 592.

      The sum is:

      6159 + 2744 = 8903
      2744 = 14^3

      But it is less typing to use the [[ SubstitutedExpression() ]] solver to solve the alphametic sum and check that WATT is an exact power. The following run file executes in 106ms, so it’s still quite quick.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "KILO + WATT = HOUR"
      "any(is_power(WATT, k) for k in irange(2, 13))"
      
      --answer="LOW"
      

      We only need to check powers up to 13 as 2^14 = 16384 is 5 digits.

      There is a second possible solution to the sum:

      2907 + 3844 = 6751
      3844 = 62^2

      But this gives LOW a value of 073, so is disallowed.

      Like

    • GeoffR's avatar

      GeoffR 8:56 pm on 30 July 2020 Permalink | Reply

      from itertools import permutations
      digits = set(range(10))
      
      for num in range(2, 99):
        for p in range(2, 14):
          watt = num ** p
          if watt >= 10000:
            break
          if  watt >= 1000:
      
            # Find <WATT> digits first
            w, a, t, _t = (int(c) for c in str(watt))
            dig_left = digits.difference({w, a, t})
            if _t == t and len(dig_left) == 7:
      
              # permute remaining digits
              for q in permutations (dig_left):
                k, i, l, o, h, u, r = q
                kilo = 1000*k + 100*i + 10*l + o
                hour = 1000*h + 100*o + 10*u + r
                if kilo + watt == hour:
                  low = 100*l + 10*o + w
                  if  100 < low < 1000:
                    print(f"LOW = {low}")
                    print(f"Sum is {kilo} + {watt} = {hour}")
      
      # LOW = 592
      # Sum is 6159 + 2744 = 8903
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 6:00 pm on 27 July 2020 Permalink | Reply
    Tags:   

    Brainteaser 1312: A Times Teaser is … 

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

    The five teams in our local league each play each other once in the course of the season. After seven of the games this season a league table was calculated, and part of it is shown below (with the teams in alphabetical order). Three points are awarded for a win and point [to each side] for a draw. In our table the digits 0 to 6 have been consistently replaced by letters.

    Over half the matches so far have had a score of 1-1.

    List all the results of the games (teams and scores).

    [teaser1312]

     
    • Jim Randell's avatar

      Jim Randell 6:01 pm on 27 July 2020 Permalink | Reply

      This Python program uses the [[ Football() ]] helper class from the enigma.py library. It runs in 84ms.

      Run: [ @replit ]

      from enigma import (Football, irange, update)
      
      # scoring system
      football = Football(points=dict(w=3, d=1))
      
      # available digits
      digits = set(irange(0, 6))
      
      # the columns of the table
      (table, gf, ga) = (dict(w="??T??", d="?TE??", l="AIA??", points="?SR?S"), "?MS?I", "?EE??")
      
      # allocate match outcomes
      for (ms, d) in football.substituted_table(table, vs=digits):
      
        # 7 of the (10) games have been played
        if not (sum(m != 'x' for m in ms.values()) == 7): continue
      
        # choose a value for M
        for M in digits.difference(d.values()):
          d_ = update(d, [('M', M)])
      
          # allocate scorelines
          for ss in football.substituted_table_goals(gf, ga, ms, d=d_, teams=[1, 2]):
      
            # over half (4 or more) of the matches were 1-1
            if not (sum(s == (1, 1) for s in ss.values()) > 3): continue
      
            # output solution
            football.output_matches(ms, ss, teams="CFRTU", d=d_)
      

      Solution: The results in the 7 played matches are: C vs F = 1-1; C vs R = 1-1; F vs R = 0-1; F vs T = 4-0; F vs U = 0-1; R vs T = 1-1; R vs U = 1-1.

      The C vs T, C vs U, T vs U matches are yet to be played.

      Like

    • Frits's avatar

      Frits 2:01 pm on 29 July 2020 Permalink | Reply

      I tried to solve the problem with a SubstitutedExpression.
      I succeeded but had to solve all major logic myself.

      #          WON  DRAWN LOST FOR AGAINST POINTS
      # CITY      C*    G*   A    N*    P*     V*
      # FOREST    B*    T    I    M     E      S 
      # ROVERS    T     E    A    S     E      R
      # TOWN      D*    H*   K*   O*    Q*     W*
      # UNITED    F*    J*   L*   I     U*     S
      #
      # TEASRCBDFGHIJKLMNOPQUVW
      # 13046010121211052125121
      #
      
      # T * 3 + E = R and E != R ---> T > 0 
      # A+I+A < 4 --> A = 0 or I = 0 --> E > 0
      # E > 0 and R < 7 and A < 2 
      # --> T = 1 and A = 0 --> sorted(I,E) = (2,3)
      # --> sorted(S,R,M) = (4,5,6)
      # sorted(I,E) = (2,3) but also E >= I --> I = 2, E = 3
      # B * 3 + T = S --> B = 1, S = 4 --> sorted(R,M) = (5,6) 
      # Forest: 1 won, 1 drawn, 2 lost, against 3 
      # --> they lost twice with 0-1 and drew 1-1 --> they won (M-1)-0
      # --> 1 won, 2 lost thus in total we have 4 draws and 3 wins/losses
      # Rovers: 1 won, 3 drawn, 0 lost, goals made 4, points R 
      # --> R = 6, M = 5
      # Rovers - Forest: no draw (otherwise there are 4 win/loss games)
      # --> Rovers - Forest: 1-0 
      # --> Rovers - City: 1-1
      # --> Rovers - Town: 1-1
      # --> Rovers - United: 1-1
      # United drew 1-1 against Rovers, made 2 goals and has 4 points 
      # --> they also had a win
      # --> United - Forest: 1-0, F = 1, J = 1, L = 0, U = 1
      # Forest and Rovers both played 4 times (total 7 games)
      # --> City, Town and United didn't play each other but played twice
      # --> City didn't lose (A = 0) 
      # --> City - Forest 1-1 --> N = 2, P = 2
      # Town played 2 games, one draw only 
      # --> Town - Forest: 0-4
      

      I wonder if in MiniZinc core van be achieved.

      Like

  • Unknown's avatar

    Jim Randell 10:41 am on 26 July 2020 Permalink | Reply
    Tags: by: P. J. Kellner (aged 14)   

    Brain-Teaser 3: People on a bus 

    From The Sunday Times, 12th March 1961 [link]

    A number of people got on a bus at a bus terminal.

    At the first stop one third of the passengers got off, and eight got on.

    At the second stop one third of the passengers got off, and twelve got on.

    At the third stop half the passengers got off, and fourteen got on.

    At the fourth stop half the passengers got off, and twelve got on.

    At the fifth stop, which was the other terminal, all the passengers got off.

    The fare was 2d. per adult per stop, and 1d. per child per stop. During the journey the conductor took four times as much money from adults as from children. The number of shillings taken equalled the number of passengers who got on at the start.

    How many were there?

    Note: 1 shilling = 12d.

    [teaser3]

     
    • Jim Randell's avatar

      Jim Randell 10:41 am on 26 July 2020 Permalink | Reply

      This Python program runs in 51ms.

      Run: [ @repl.it ]

      from enigma import irange, div, printf
      
      # consider the stops in ss, specified as (a, b, k)
      # where at each stop a/b of the passengers get off and k more get on
      # return a list of the number of passengers arriving at each stop
      def stops(ss, M=100):
        # consider initial 
        for n in irange(1, M):
          ns = [n]
          try:
            for (a, b, k) in ss:
              n -= div(n * a, b)
              n += k
              ns.append(n)
            yield ns
          except TypeError:
            continue
      
      # consider possible stops given in the puzzle
      for ns in stops([(1, 3, 8), (1, 3, 12), (1, 2, 14), (1, 2, 12)]):
        # so the number of chargeable stops <t> is...
        t = sum(ns)
        # if there are <a> adult stops and <b> child stops then:
        #   t = a + b
        # and 4 times as much money was taken from adults as children
        # when the adults pay 2d/stop and children 1d/stop
        #   2a = 4b -> a = 2b
        #   -> t = 3b
        b = div(t, 3)
        if b is None: continue
        a = 2 * b
        # the number of shillings (= 12d) taken is the same as the initial
        # number of passengers
        s = div(2 * a + b, 12)
        if not(s == ns[0]): continue
        # output solution
        printf("{ns} -> {t} stops = {a} adult + {b} child; {s} shillings")
      

      Solution: The initial number of passengers (and the number of shillings taken) is 15.

      The numbers of passengers arriving at each stop are:

      Stop 1: 15
      Stop 2: 18
      Stop 3: 24
      Stop 4: 26
      Stop 5: 25

      Like

    • Frits's avatar

      Frits 7:02 pm on 26 July 2020 Permalink | Reply

      from enigma import SubstitutedExpression, printf, irange
       
      # the alphametic puzzle
      p = SubstitutedExpression([
          # No. people arriving at stop 1: AB 
          # No. people arriving at stop 2: CD = 2/3*AB + 8   
          "(2 * AB) + 24  == 3 * CD",
          # No. people arriving at stop 3: EF = 4/9*AB + 52/3  
          "(4 * AB) + 156 == 9 * EF",
          # No. people arriving at stop 4: GH = 2/9*AB + 68/3  
          "(2 * AB) + 204 == 9 * GH",
          # No. people arriving at stop 5: IJ = 1/9*AB + 70/3  
          "AB + 210       == 9 * IJ",
          # Total fare (MN: no. children, OP: no. adults)
          "12 * AB == (MN + 2 * OP)",
          # 2.4 * AB is the amount of pennies children had to pay
          # thus AB has to be a multiple of 5
          "AB == 5 * KL",
          # AB + CD + EF + GH + IJ sums up to 22/9 * AB + 214/3
          # Total number of people on the bus is 22/9 * AB + 214/3
          "22 * AB + 642 == 9 * (MN + OP)",
        ],
        answer="AB,CD,EF,GH,IJ",
        d2i="",          # allow number representations to start with 0
        distinct="",     # allow variables with same values
      )
      
      # Print answers
      for (_, ans) in p.solve(verbose=0): 
        for i in irange(0, 4):
          printf("Number of people arriving at stop {i+1}: {ans[i]}")
        printf("answer = {ans[0]}")
      

      Please let me know how to reply without losing the formatting of the pasted code.
      My previous attempt to add comments in the p = SubstitutedExpression([…]) construct failed as I put them between the double quotes.

      Like

      • Frits's avatar

        Frits 7:24 pm on 26 July 2020 Permalink | Reply

        I forgot I don’t have to solve the equations myself.
        Another simpler version.

        from enigma import SubstitutedExpression, printf, irange
         
        # the alphametic puzzle
        p = SubstitutedExpression([
            # No. people arriving at stop 1: AB 
            # No. people arriving at stop 2: CD = 2/3*AB + 8   
            "(2 * AB) + 24 == 3 * CD",
            # No. people arriving at stop 3: EF = 2/3*CD + 12  
            "(2 * CD) + 36 == 3 * EF",
            # No. people arriving at stop 4: GH = 1/2*EF + 14  
            "EF + 28 == 2 * GH",
            # No. people arriving at stop 5: IJ = 1/2*GH + 12  
            "GH + 24 == 2 * IJ",
            # Total fare (MN: no. children, OP: no. adults)
            "12 * AB == (MN + 2 * OP)",
            # 2.4 * AB is the amount of pennies children had to pay
            # thus AB has to be a multiple of 5
            "AB == 5 * KL",
            # Total number of people on the bus is AB + CD + EF + GH + IJ
             "AB + CD + EF + GH + IJ == MN + OP",
          ],
          answer="AB,CD,EF,GH,IJ",
          d2i="",          # allow number respresentations to start with 0
          distinct="",     # allow variables with same values
        )
        
        # Print answers
        for (_, ans) in p.solve(verbose=0): 
          for i in irange(0, 4):
            printf("Number of people arriving at stop {i+1}: {ans[i]}")
          printf("answer = {ans[0]}")
        

        @Jim: I prefer to have all the code in one module iso a main module and a run module.
        Does it matter (like for performance)?

        Like

      • Jim Randell's avatar

        Jim Randell 9:52 pm on 26 July 2020 Permalink | Reply

        @Frits: When posting code in WordPress you can use:

        [code language="python"]
        ...
        [/code]
        

        I wrote some notes up on the site I manage for New Scientist Enigma puzzles [ @EnigmaticCode ], the same notes hold true for the S2T2 site.

        Like

  • Unknown's avatar

    Jim Randell 4:59 pm on 24 July 2020 Permalink | Reply
    Tags:   

    Teaser 3018: Debased 

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

    Sarah writes down a four-digit number then multiplies it by four and writes down the resultant five-digit number. She challenges her sister Jenny to identify anything that is special about these numbers. Jenny is up to the challenge as she identifies two things that are special. She sees that as well as both numbers being perfect squares she also recognises that if the five-digit number was treated as being to base 7 it would, if converted to a base 10 number, be the same as the original four-digit number.

    What is the four-digit number?

    [teaser3018]

     
    • Jim Randell's avatar

      Jim Randell 5:08 pm on 24 July 2020 Permalink | Reply

      It is straightforward to implement this directly.

      The following Python program runs in 52ms.

      Run: [ @replit ]

      from enigma import (powers, int2base, printf)
      
      # consider 4-digit squares, where 4n is a 5-digit number
      for n in powers(50, 99, 2):
        # what is n in base 7?
        n7 = int2base(n, base=7)
        # which if interpreted in base 10 is 4n
        if int2base(4 * n) == n7:
          printf("n={n} [= {n7} (base 7)]")
      

      Solution: The four digit number is: 2601.

      2601 = 51², so the answer is found on the second number tested by the program.

      4×2601 = 10404, and 10404 (base 7) = 2601 (base 10).

      Or, as a single Python expression:

      >>> peek(n for n in powers(50, 99, 2) if int2base(n, base=7) == int2base(4 * n))
      2601
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:40 pm on 30 July 2020 Permalink | Reply

        Or, using the [[ SubstitutedExpression() ]] solver from the enigma.py library:

        Run: [ @replit ]

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        --answer="ABCD"
        --distinct=""
        
        # the 4-digit number is ABCD, and it is a square
        "is_square(ABCD)"
        
        # multiplied by 4 gives a 5 digit number
        "9999 < 4 * ABCD"
        
        # interpreting 4 * ABCD as a base 7 number gives ABCD
        "int2base(ABCD, base=7) == int2base(4 * ABCD, base=10)"
        

        Like

    • GeoffR's avatar

      GeoffR 7:54 am on 26 July 2020 Permalink | Reply

      
      % A Solution in MiniZinc 
      include "globals.mzn";
       
      % digits for 4 digit number to base 10
      var 1..9:A; var 0..9:B; var 0..9:C; var 0..9:D; 
      var 1000..9999: ABCD = 1000*A + 100*B + 10*C + D;
      
      % digits for 5 digit number in base 7
      var 1..6:E; var 0..6:F; var 0..6:G; var 0..6:H; var 0..6:I;
      var 10000..99999: EFGHI = 10000*E + 1000*F + 100*G + 10*H + I;
      
      predicate is_sq(var int: y) =
        let {
           var 1..ceil(sqrt(int2float(ub(y)))): z
        } in
         z*z = y;
      
      % 5-digit number is four times the 4-digit number
      constraint 4 * ABCD == EFGHI;
      
      % Both 4-digit and 5-digit numbers are squares
      constraint is_sq(ABCD) /\ is_sq(EFGHI);
      
      % ABCD (base 10) = EFGHI (base 7)
      constraint ABCD == I + 7*H + 7*7*G + 7*7*7*F  + 7*7*7*7*E;
      
      solve satisfy;
      
      output [ "Four digit number (base 10) is " ++ show(ABCD) 
      ++ "\nFive digit number (base 7) is "++ show(EFGHI) ]; 
      
      

      Like

    • GeoffR's avatar

      GeoffR 8:20 pm on 27 July 2020 Permalink | Reply

      
      from enigma import is_square, nsplit
      
      for n in range(50,100):
        if is_square(n*n):
          N4 = n*n      # 4-digit number (base 10)
          N5 = 4*n*n    # 5-digit number (base 7)
          
          # split 5-digit number N5 into digits
          a, b, c, d, e = nsplit(N5)
          
          # check N4 = N5 in specified bases
          if N4 == e + d*7 + c*7**2 + b*7**3 + a*7**4:
            print(f"4-digit number(base 10)= {n**2}")
            print(f"5-digit number(base 7)= {4*n**2}")
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 8:07 am on 28 July 2020 Permalink | Reply

      I have updated my programme to include a check that the digits (7,8,9) are not included in the 5-digit number before it is converted to base 7. In practice, the answer is found very early.

      
      from enigma import is_square, nsplit
      
      for n in range(50, 100):
        if is_square(n * n):
          N4 = n * n      # 4-digit number (base 10)
          N5 = 4 * n * n    # 5-digit number (base 7)
      
          # split 5-digit number N5 into digits
          a, b, c, d, e = nsplit(N5)
      
          # check digits 7,8,9 are not in N5, base 7
          if any(x in (a,b,c,d,e) for x in (7,8,9)):
            continue
          
          # check N4 = N5 in specified bases
          if N4 == e + d*7 + c*7**2 + b*7**3 + a*7**4:
            print(f"4-digit number(base 10)= {n**2}")
            print(f"5-digit number(base 7)= {4*n**2}")
      
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 2:53 pm on 29 July 2020 Permalink | Reply

        @GeoffR: That’s why in my code I interpret a base 7 representation as base 10. There is no need to check for invalid digits, as every base 7 digit is a valid base 10 digit.

        Like

  • Unknown's avatar

    Jim Randell 3:43 pm on 23 July 2020 Permalink | Reply
    Tags:   

    Teaser 2684: How safe? 

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

    Fed up with being burgled, George and Martha have bought a new safe. They remember its six-figure combination as three two-figure primes. Martha noted that the product of the six different digits of the combination was a perfect square, as was the sum of the six. George commented further that the six-figure combination was actually a multiple of the difference between that product and sum.

    What is the six-figure combination?

    [teaser2684]

     
    • Jim Randell's avatar

      Jim Randell 3:43 pm on 23 July 2020 Permalink | Reply

      If the combination can be considered as three 2-digit primes, then it cannot contain 0 (as a 2-digit prime cannot begin or end with 0).

      This Python program looks for 6 different (non-zero) digits that have a square for both their sum and product, and looks for 6-digit multiples of the difference between these that use the same digits, and can be split into three 2-digit prime numbers. It runs in 52ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, multiply, is_square, divc, is_prime, nsplit, printf)
      
      # look for 6 different digits
      for ds in subsets(irange(1, 9), size=6, select="C"):
        # the sum and product must both be squares
        s = sum(ds)
        p = multiply(ds)
        if not (is_square(s) and is_square(p)): continue
        (d, ds) = (p - s, set(ds))
        # consider 6-digit multiples of d
        for n in irange(d * divc(100000, d), 999999, step=d):
          # does it use the same digits?
          if ds.difference(nsplit(n)): continue
          # does it split into primes?
          if not all(is_prime(p) for p in nsplit(n, base=100)): continue
          # output solution
          printf("{n} [s={s} p={p} d={d}]")
      

      Solution: The combination is 296143.

      There is only one set of 6 different digits that have a square for both their sum and product: 1, 2, 3, 4, 6, 9 (sum = 5², product = 36²). So the combination is a rearrangement of these digits that is a multiple of 1271, that can be split into three 2-digit primes. In fact 296143 is the only multiple of 1271 that uses the required digits, so the prime test is superfluous.

      Like

    • GeoffR's avatar

      GeoffR 8:38 pm on 23 July 2020 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C; 
      var 1..9:D; var 1..9:E; var 1..9:F; 
      
      constraint all_different([A,B,C,D,E,F]);
      
      % three two-figure primes
      var 11..97: AB = 10*A + B;
      var 11..97: CD = 10*C + D;
      var 11..97: EF = 10*E + F;
      
      var 720..60480: dig_prod = A * B * C * D * E * F;
      var 21..39: dig_sum = A + B + C + D + E + F;
      
      % Safe combination is ABCDEF
      var 100000..999999: comb = 100000*A + 10000*B + 1000*C
      + 100*D + 10*E + F;
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      predicate is_sq(var int: y) =
        let {
           var 1..ceil(sqrt(int2float(ub(y)))): z
        } in
         z*z = y;
      
      % the combination is split into three 2-digit primes
      constraint is_prime(AB) /\ is_prime(CD) /\ is_prime(CD);
      
      % digit product and digit sum are both squares
      constraint is_sq(A * B * C * D * E * F);
      constraint is_sq(A + B + C + D + E + F);
      
      % the six-figure combination was actually a multiple of 
      % the difference between that product and sum.
      constraint comb mod (dig_prod - dig_sum) == 0;
      
      solve satisfy;
      
      output["Safe combination is " ++ show(comb)
      ++ "\nDigits product = " ++ show(dig_prod)];
      
      % Safe combination is 296143
      % Digits product = 1296
      % ----------
      % ==========
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 4:44 pm on 24 July 2020 Permalink | Reply

      I carried out some further tests to see if there were any constraints which were not essential to get the correct answer.

      I found any one of the following MiniZinc constraints could be removed, and the single correct answer obtained:

      1) requirement for digits to be all different
      2) requirement for three 2-digit primes
      3) requirement for sum of digits to be a square
      4) requirement product of digits to be a square.

      However, I could not remove more than one of these constraints and get the correct answer.

      Like

  • Unknown's avatar

    Jim Randell 1:23 pm on 21 July 2020 Permalink | Reply
    Tags:   

    Teaser 2683: House-to-house 

    From The Sunday Times, 23rd February 2014 [link] [link]

    Tezar Road has houses on one side only, numbered consecutively. The Allens, Browns, Carrs and Daws live in that order along the road, with the Allens’ house number being in the teens. The number of houses between the Allens and the Browns is a multiple of the number of houses between the Browns and the Carrs. The number of houses between the Allens and the Carrs is the same multiple of the number of houses between the Carrs and the Daws. The Daws’ house number consists of the same two digits as the Allens’ house number but in the reverse order.

    What are the house numbers of the four families?

    [teaser2683]

     
    • Jim Randell's avatar

      Jim Randell 1:23 pm on 21 July 2020 Permalink | Reply

      In order to get a unique solution we require “multiple” to be a “proper multiple” (i.e. not ×1).

      This Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (irange, divisors, div, printf)
      
      # A is in [13, 19]
      for d in irange(3, 9):
        A = 10 + d
        # D is the reverse of A
        D = 10 * d + 1
        # C divides AD in to m-1:1
        n = D - A - 2
        for m in divisors(n):
          if not (2 < m < n): continue
          C = D - (n // m) - 1
          # B divides AC into the same ratio
          x = div(C - A - 2, m)
          if x is None: continue
          B = C - x - 1
          # output solution
          printf("A={A} B={B} C={C} D={D} [ratio = {m}:1]", m=m - 1)
      

      Solution: The house numbers are: 19, 64, 76, 91.

      There are 44 houses between A and B, and 11 between B and C.

      There are 56 houses between A and C, and 14 between C and D.

      Allowing m = 1 also gives the following solutions:

      A=15, B=24, C=33, D=51
      A=19, B=37, C=55, D=91

      Like

  • Unknown's avatar

    Jim Randell 4:30 pm on 17 July 2020 Permalink | Reply
    Tags:   

    Teaser 3017: Mr. Green’s scalene mean machine 

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

    My maths teacher, Mr. Green, stated that the average of the squares of any two different odd numbers gives the hypotenuse of a right-angled triangle that can have whole-number unequal sides, and he told us how to work out those sides.

    I used my two sisters’ ages (different odd prime numbers) to work out such a triangle, then did the same with my two brothers’ ages (also different odd prime numbers). Curiously, both triangles had the same three-figure palindromic hypotenuse. However, just one of the triangles was very nearly a 45° right-angled triangle (having a relative difference between the adjacent side lengths of less than 2%).

    In ascending order, what were my siblings’ ages?

    [teaser3017]

     
    • Jim Randell's avatar

      Jim Randell 5:34 pm on 17 July 2020 Permalink | Reply

      We don’t need to work out how to calculate the non-hypotenuse sides of the triangle (although I have worked out a rule which works).

      This Python program groups together Pythagorean triples by the hypotenuse, then looks for groups that have a palindromic hypotenuse, and more than one corresponding triangle. It then works out pairs of odd squares that average to the hypotenuse, and looks for two of these pairs where each number in the pair is prime. Then we need to look for a corresponding triangle where the non-hypotenuse sides are almost equal. And that gives us our solution.

      This Python program runs in 55ms.

      Run: [ @replit ]

      from enigma import (pythagorean_triples, is_palindrome, nsplit, group, unpack, isqrt, compare, subsets, flatten, is_prime, printf)
      
      # generate decompositions of n into the sum of two squares
      def sum_of_squares(n):
        (i, j) = (isqrt(n), 0)
        while not (i < j):
          r = compare(i * i + j * j, n)
          if r == 0: yield (j, i)
          if r != -1: i -= 1
          if r != 1: j += 1
      
      # collect pythagorean triples grouped by hypotenuse
      tris = group(
        pythagorean_triples(999),
        st=unpack(lambda x, y, z: z > 99 and is_palindrome(nsplit(z))),
        by=unpack(lambda x, y, z: z)
      )
      
      # look for multiple triangles that share a palindromic hypotenuse
      for (z, xyzs) in tris.items():
        if not (len(xyzs) > 1): continue
        # and look for multiple pairs of squares that average to the hypotenuse
        avgs = list((x, y) for (x, y) in sum_of_squares(2 * z) if x < y)
        if not (len(avgs) > 1): continue
        printf("[{z} -> {xyzs} -> {avgs}]")
        # look for triangles with x, y within 2% of each other
        t45ish = list((x, y, z) for (x, y, z) in xyzs if 100 * y < 102 * x)
        if not (0 < len(t45ish) < len(xyzs)): continue
        # collect two of the pairs for the ages
        for ages in subsets(avgs, size=2):
          ages = sorted(flatten(ages))
          # and check they are odd primes
          if not all(x % 2 == 1 and is_prime(x) for x in ages): continue
          # output solution
          printf("z={z} -> t45ish = {t45ish} -> ages = {ages}")
      

      Solution: The ages are: 13, 17, 29, 31.

      Removing the test for the ages being odd and prime does not give any additional solutions.

      If the two odd numbers are a and b (where a < b), then the following is a Pythagorean triple:

      x = (b² − a²) / 2
      y = ab
      z = (a² + b²) / 2

      where z is the hypotenuse. (All we actually require is that a and b have the same parity).

      Applying this rule to the two pairs (13, 31) and (17, 29) we get the following triangles:

      (13, 31) → sides = (396, 403, 565); angles = (44.5°, 45.5°, 90.0°)
      (17, 29) → sides = (276, 493, 565); angles = (29.2°, 60.8°, 90.0°)

      The first of these triangles is close to being a (45°, 45°, 90°) triangle, and the other one is quite close to being a (30°, 60°, 90°) triangle – the standard shapes for set squares.

      Like

    • GeoffR's avatar

      GeoffR 8:15 am on 21 July 2020 Permalink | Reply

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      % Siblings ages in teaser
      var 2..97:S1; var 2..97:S2; var 2..97:B1; var 2..97:B2;
      constraint all_different ([S1, S2, B1, B2]);
      
      constraint S1 < S2 /\ B1 < B2;
      
      constraint is_prime(S1) /\ is_prime(S2)
      /\ is_prime(B1) /\ is_prime(B2);
      
      % Palindromic hypotenuse
      var 100..999: HYP;
      
      constraint HYP div 100 == HYP mod 10 
      /\ HYP div 10 mod 10 != HYP mod 10;
      
      % Two other sides with a palindromic hypotenuse
      var 100..999: side1; var 100..999: side2;
      
      constraint side1 > side2
      /\ all_different([HYP, side1, side2]);
      
      % Mr Green's triangle formula
      constraint B1*B1 + B2*B2 = 2 * HYP
      /\ S1*S1 + S2*S2 = 2 * HYP;
      
      % The triangle sides are less than 2% different in size
      constraint side1 * side1 + side2 * side2 == HYP * HYP
      /\ 100 * (side1 - side2) < 2 * side1;
      
      solve satisfy;
      
      output [ "Sisters' ages are " ++ show(S1) ++ ", " ++ show(S2)]
      ++ [ "\nBrothers' ages are " ++ show(B1) ++ ", " ++ show(B2)]
      ++ [ "\nTriangle solution sides are " ++ show(HYP) ++ ", " ++ show(side1)]
      ++ [", " ++ show(side2) ];
      
      % Ascending siblings ages are 13,17,29,31
      
      % Sisters' ages are 17, 29
      % Brothers' ages are 13, 31
      % Triangle solution sides are 565, 403, 396
      % time elapsed: 0.05 s
      % ----------
      % Sisters' ages are 13, 31
      % Brothers' ages are 17, 29
      % Triangle solution sides are 565, 403, 396
      % time elapsed: 0.05 s
      % ----------
      % ==========
      
      

      I found the prime age constraint of the siblings was not essential for this solution.

      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