Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 3:56 pm on 30 December 2021 Permalink | Reply
    Tags:   

    Teaser 3093: Shout snap! 

    From The Sunday Times, 2nd January 2022 [link] [link]

    My grandson and I play a simple card game. We have a set of fifteen cards on each of which is one of the words ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, SHOUT and SNAP. We shuffle the cards and then display them one at a time. Whenever two consecutive cards have a letter of the alphabet occurring on both we race to shout “Snap!”. In a recent game there were no snaps. I counted the numbers of cards between the “picture-cards” (J, Q, K) and there was the same number between the first and second picture-cards occurring as between the second and third. Also, the odd-numbered cards (3, 5, 7, 9) appeared in increasing order during the game.

    In order, what were the first six cards?

    [teaser3093]

     
    • Jim Randell's avatar

      Jim Randell 4:48 pm on 30 December 2021 Permalink | Reply

      The following Python 3 program runs in 478ms.

      Run: [ @replit ]

      from enigma import subsets, intersect, join, printf
      
      # the cards
      cards = "ACE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE TEN JACK QUEEN KING SHOUT SNAP".split()
      
      # which cards snap?
      snap = set()
      for (x, y) in subsets(cards, size=2):
        if intersect([x, y]):
          snap.update([(x, y), (y, x)])
      
      # generate sequences with no snaps
      def generate(cards, xs=[], pics=[], subseq=None):
        # are we done?
        if not cards:
          yield xs
        else:
          # choose the next card
          for (i, x) in enumerate(cards):
            if xs and (x, xs[-1]) in snap: continue
      
            # check subsequence cards appear in order
            ss = subseq
            if x in subseq:
              if x != subseq[0]: continue
              ss = ss[1:]
      
            # check picture cards appear equally distanced
            ps = pics
            if x in {'JACK', 'QUEEN', 'KING'}:
              ps = ps + [len(xs)]
              if len(ps) == 3 and ps[2] - ps[1] != ps[1] - ps[0]: continue
      
            # solve for the remaining cards
            yield from generate(cards[:i] + cards[i + 1:], xs + [x], ps, ss)
      
      # find solutions
      for ss in generate(cards, subseq=['THREE', 'FIVE', 'SEVEN', 'NINE']):  
        printf("{ss}", ss=join(ss, sep=" "))
      

      Solution: The first six cards were: EIGHT, SNAP, THREE, KING, ACE, SHOUT.

      And the full sequence is:

      EIGHT, SNAP, THREE, KING, ACE, SHOUT, FIVE, TWO, QUEEN, SIX, TEN, FOUR, SEVEN, JACK, NINE

      There are 4 cards between KING and QUEEN, and 4 cards between QUEEN and JACK.

      Like

      • NigelR's avatar

        NigelR 3:50 pm on 31 December 2021 Permalink | Reply

        A typical brute force approach from me. It is neither pretty nor quick, but it gets the job done (eventually!). I could probably have mined a bitcoin using less processing effort.

        
        # STBT 3093
        
        from itertools import permutations as perm
        
        cards=['ACE','TWO','THREE','FOUR','FIVE','SIX', 'SEVEN', 'EIGHT', 'NINE', 'TEN', 'JACK', 'QUEEN', 'KING','SHOUT','SNAP']
        oddnums=['THREE','FIVE','SEVEN','NINE']
        odds=[]
        evens=[]
        
        def oddtest(self): #test self for numbers 3-9 in correct sequence
            ind = [self.index(z) for z in oddnums]
            for z in range(0,3):
                if ind[z]>ind[z+1]:return False
            return True 
        
        def pictest(): #test picture cards for consistent spacing
            j,q,k=seq.index('JACK'),seq.index('QUEEN'),seq.index('KING')
            if q<j and q<k:return False #Q must be between J & K for equal spacing since q is an even and other two are odds
            if q>j and q>k:return False
            if abs(q-j)!=abs(k-q): return False  #check for equal spacing
            return True 
        
        def seqtest(): #test seq for no shared letters in adjacent cards
            for i in range(0,14):
                for z in seq[i]:
                    if z in seq[i+1]:return False
            return True
        
        seq=cards
        evens=[x for x in cards if 'E' in x] # there are 8 cards with letter E, so can only be placed on even slots to avoid adjacency
        odds=[x for x in cards if x not in evens]
        
        for x in list(perm(evens)):
            if not oddtest(x): continue
            for y in list(perm(odds)):
                for i in range(0,8): seq[i*2]=x[i]
                for i in range(1,8): seq[(2*i)-1]=y[i-1]
                if not pictest():continue
                if not seqtest():continue
                print ('The first six cards drawn in order were:',seq[0:6])
        
        

        Like

        • Jim Randell's avatar

          Jim Randell 10:54 am on 2 January 2022 Permalink | Reply

          My first program was quite slow too. I generated all sequences without a “snap” and then applied the remaining conditions.

          To improve the speed I moved the conditions inside the [[ generate() ]] function, and that got the code down to running in less than a second.

          Your observation that there are 8 cards with an E, and 7 cards without (so they must be interleaved, starting with an E), speeds up my code to 287ms.

          We replace line 20 in my program with:

                if xs:
                  # can't snap with the previous card
                  if (x, xs[-1]) in snap: continue
                else:
                  # first card must have an E
                  if not ('E' in x): continue
          

          Like

          • Brian Gladman's avatar

            Brian Gladman 4:37 pm on 4 January 2022 Permalink | Reply

            @Jim I went down exactly the same path and arrived at a close to identical solution to your own.

            I missed the significance of cards with ‘E’s until it was pointed out here and this provides a major improvement in performance.

            If I add this to your version after line 18:

                pos_even = not (len(xs) & 1)
            

            and this after line 20:

                  if pos_even and 'E' not in x: continue
            

            the run time for me (using profile )drops by a factor of over 20.

            Like

          • Jim Randell's avatar

            Jim Randell 9:51 pm on 4 January 2022 Permalink | Reply

            Using the observation that the “with E” and “without E” sets of cards must be interleaved allows us to go back to the simpler approach where we generate (interleaved) sets of cards, and then check them for the remaining conditions, and still have the program run in a short time.

            from enigma import (cproduct, intersect, filter2, join, printf)
            
            # the cards
            cards = "ACE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE TEN JACK QUEEN KING SHOUT SNAP".split()
            
            # partition the cards into 2 sets
            (cs1, cs2) = filter2((lambda x: 'E' in x), cards)
            assert len(cs1) == len(cs2) + 1
            
            # which cards snap?
            snap = set()
            for (x, y) in cproduct([cs1, cs2]):
              if intersect([x, y]):
                snap.update([(x, y), (y, x)])
            
            # generate sequences with no snaps, interleaving cs1 and cs2
            def generate(cs1, cs2, xs=[]):
              # are we done?
              if not cs1:
                yield xs
              else:
                # choose the next card
                for (i, x) in enumerate(cs1):
                  if xs and (x, xs[-1]) in snap: continue
                  # solve for remaining cards
                  yield from generate(cs2, cs1[:i] + cs1[i + 1:], xs + [x])
            
            # find solutions
            for ss in generate(cs1, cs2):
              pos = lambda x: ss.index(x)
              # check THREE, FIVE, SEVEN, NINE appear in order
              if not (pos('THREE') < pos('FIVE') < pos('SEVEN') < pos('NINE')): continue
              # check JACK, QUEEN, KING are evenly spaced
              (i, j, k) = sorted(pos(x) for x in ['JACK', 'QUEEN', 'KING'])
              if not (j - i == k - j): continue
              # output solution
              printf("{ss}", ss=join(ss, sep=" "))
            

            Like

  • Unknown's avatar

    Jim Randell 10:23 am on 30 December 2021 Permalink | Reply
    Tags:   

    Teaser 2855: Fab four 

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

    Here are four numbers, where each is the same fixed whole-number multiple of the previous one. However, in some places I have consistently replaced digits by letters — and in the other places the digits have been replaced by [asterisks]. In this way the numbers have become the fabulous four:

    JOHN
    P*UL
    R***O
    ****GE

    What number is my GROUP?

    [teaser2855]

     
    • Jim Randell's avatar

      Jim Randell 10:24 am on 30 December 2021 Permalink | Reply

      I used the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this puzzle.

      I added lower case letters (which don’t have to be distinct from each other) to fill in the gaps in the words.

      The following run file executes in 83ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --distinct="EGHJLNOPRU"
      --reorder=0
      
      "{JOHN} * {z} = {PaUL}"
      "{PaUL} * {z} = {RingO}"
      "{RingO} * {z} = {jeorGE}"
      
      --answer="{GROUP}"
      

      Solution: GROUP = 57209.

      The expressions are:

      1238 × 8 = 9904
      9904 × 8 = 79232
      79232 × 8 = 633856

      alphametically:

      JOHN × N = PPUL
      PPUL × N = RPOHO
      RPOHO × N = EHHNGE

      Like

  • Unknown's avatar

    Jim Randell 10:03 am on 28 December 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 894: Prime consideration 

    From The Sunday Times, 24th September 1978 [link]

    At our local the other night I made the acquaintance of a chap called Michael and his wife Noelle. We talked a good bit about our respective families, our activities and our hobbies. When I happened to mention my interest in mathematical puzzles to Michael he said that he knew one at first hand which might give me some amusement. He proceeded to tell me that although he himself, his parents (who were born in different years), Noelle and their children (featuring no twins, triplets, etc.) had all been born in the nineteen-hundreds and none of them in a leap year, the numerical relationship between their years of birth was nevertheless in two respects a bit unusual.

    “In the first place”, he said, “just one of us has our year of birth precisely equal to the mean of all the others’ years of birth. But more remarkably the difference between my father’s year of birth and that of any one of the rest of us is a prime, and the same is true of that of my mother. And she, like Noelle here, had no child after she had passed her twenties”.

    And then with a grin he concluded, “So now you’ll know about the whole family and even, if you want, be able to figure out just how old Noelle is without having to be rude and ask her!”

    In which year was Noelle born? And how many children does she have?

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

    [teaser894]

     
    • Jim Randell's avatar

      Jim Randell 10:03 am on 28 December 2021 Permalink | Reply

      There are no multiple births for Noelle, but she could manage to have 2 children in a calendar year (by having one early in the year, and one late in the year).

      Also, if she was born at the end of the year (as is suggested by her name), it is possible to squeeze a child (or two) in during the calendar year containing her 30th birthday.

      In order to get a unique solution, we need to assume Michael and Noelle have more than 1 child (justified by his use of “children”).

      This Python program runs in 52ms.

      Run: [ @replit ]

      from enigma import (defaultdict, irange, Primes, subsets, div, printf)
      
      # check for leap years
      is_leap = lambda y: y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)
      
      # primes less than 100
      primes = Primes(100)
      
      # find possible 19xx years
      years = list(y for y in irange(1900, 1978) if not is_leap(y))
      
      # look for viable mother -> children maps
      kids = defaultdict(list)
      for (m, c) in subsets(years, size=2):
        k = c - m
        if 16 < k < 31:
          kids[m].append(c)
      
      # choose a year for M's mother
      for (MM, Ms) in kids.items():
        # all other years must have a prime difference to MM
        ys1 = set(y for y in years if abs(y - MM) in primes)
      
        # find a viable birth year for M
        for M in Ms:
          if not (M in ys1): continue
      
          # and for his father
          for MF in ys1:
            k = M - MF
            if not (k > 15 and k in primes): continue
            # all other years must also have a prime difference to MF
            ys2 = set(y for y in ys1 if abs(y - MF) in primes).difference([M, MF])
      
            # now find a year for N
            for N in ys2:
              ks = sorted(ys2.intersection(kids.get(N, [])))
              if not ks: continue
      
              # no multiple births, so (0, 1, 2) children in each year
              for ns in subsets((0, 1, 2), size=len(ks), select="M"):
                k = sum(ns)
                if k < 2: continue
                # ages
                ages = [MF, MM, M, N]
                for (n, v) in zip(ns, ks):
                  if n: ages.extend([v] * n)
                t = sum(ages)
                # exactly one of the birth years is the mean year
                m = div(t, len(ages))
                if m is None or ages.count(m) != 1: continue
      
                # output solution
                printf("MF={MF} MM={MM} -> M={M}; N={N} -> {k} kids; {ages} -> {m}")
      

      Solution: Noelle was born in 1943. She has 4 children.

      Michael’s father was born in 1900 (which was not a leap year), and his mother in 1902.

      Michael was born in 1931 (prime differences of 31 and 29 to his father and mother), and his mother was 28 or 29 when he was born.

      Noelle was born in 1943 (prime differences of 43 and 41 to Michael’s father and mother), towards the end of the year.

      Her four children were born in 1961 (one towards the beginning of the year, when Noelle was 17, and one towards the end of the year, when she was 17 or 18), and in 1973 (one towards the beginning of the year, when Noelle was 29, and one towards the end of the year, when she was still 29).

      Like

  • Unknown's avatar

    Jim Randell 9:14 am on 26 December 2021 Permalink | Reply
    Tags:   

    A Brain-Teaser: [Square convoy] 

    From The Sunday Times, 2nd January 1949 [link]

    Four ships in convoy are at the corners of a square with sides one mile long. Ship B is due north of A; C is due east of B, and D is due south of C. The four ships are each sailing north at 10 miles per hour. A motor-boat, which travels at 26 miles per hour, starts at A, delivers a message to B, then to C, then to D, and finally returns to A. The distances between the ships are traversed by the shortest possible routes, and no allowance need be made for time taken in turning.

    How far does the convoy advance whilst the motor-boat is going round?

    This is earliest of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961 that I have found. Prizes of 10 guineas and 5 guineas were awarded.

    This puzzle was originally published with no title.

    [teaser-1949-01-02] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:16 am on 26 December 2021 Permalink | Reply

      (1) A to B:

      The position of the motorboat is given by: 26t

      And the position of B is given by: (1 + 10t)

      These coincide when:

      26t = 1 + 10t
      16t = 1
      t = 1/16

      (2) B to C:

      The motorboat travels along the hypotenuse of a right-angled triangle, a distance: 26t.

      And C travels along another side, a distance: 10t.

      The remaining side being of length 1.

      (26t)² = (10t)² + 1²
      676t² = 100t² + 1
      t² = 1/576
      t = 1/24

      (3) C to D:

      The motorboat’s position is: −26t.

      D’s position is: −1 + 10t.

      −26t = −1 + 10t
      t = 1/36

      (4) D to A:

      A mirror image of B to C.

      So the total time taken is:

      T = 1/16 + 1/24 + 1/36 + 1/24
      T = 25/144

      And the distance travelled by each boat in the fleet:

      D = 10T
      D = 125/72
      D = 1.736 miles
      D = 110000 inches = 1 mile, 1295 yards, 20 inches

      Solution: The distance travelled by the fleet was 1 mile, 1295 yards, 20 inches.

      Like

  • Unknown's avatar

    Jim Randell 5:53 pm on 23 December 2021 Permalink | Reply
    Tags: ,   

    Teaser 3092: A Christmas Carol 

    From The Sunday Times, 26th December 2021 [link] [link]

    A Christmas Carol was published on 19/12/1843, when Bob Cratchit was in his forties, almost seven years after Jacob Marley’s death on Christmas Eve. On Marley’s last Christmas Day, a working day for them all as always, Scrooge said to Cratchit and Marley: “We three will work the same dates next year as this year, except that I’ll cover Cratchit’s birthday so he can have the day off, since Marley never works on his”. On Boxing Day, Scrooge decided to allocate extra work dates for next year to anyone whose number of work dates was going to be below the average of the three of them, bringing them up to exactly that average. Daily, up to and including New Year’s Eve, Scrooge repeated this levelling-up to the new average, never needing fractions of a day.

    What was Bob Cratchit’s full date of birth?

    In the book The Sunday Times Teaser Book 2 (2023), the “levelling-up” process is described as follows:

    … Scrooge decided to allocate extra work dates for the next year to the one whose number of work dates was going to be below the average of the three of them, bringing him up to exactly that average. Daily, up to and including New Year’s Eve, Scrooge repeated this levelling-up for that person to the new average, never needing fractions of a day.

    Which is intended to indicate that only one of the three is “levelled-up” each time the process is applied.

    [teaser3092]

     
    • Jim Randell's avatar

      Jim Randell 5:55 pm on 23 December 2021 Permalink | Reply

      I’m not sure I understand how this is meant to work as a puzzle.

      If Cratchit is in his forties on 1843-12-19, then he was born sometime between 1794 and 1803.

      And, if Marley died on 1836-12-24, his last Christmas Day would be 1835-12-25, so Scrooge was planning the dates for 1836.

      All we know for sure (for the initial schedule) is that they all work every Christmas Day, Marley never works on his birthday, and Scrooge will work on Cratchit’s birthday in 1836 but Cratchit won’t.

      So the number of possible workdays initially scheduled for 1836 is: Scrooge 2-366, Cratchit 1-365, Marley 1-365.

      I found 3429 initial assignments of numbers of work days in 1836 that would allow Scrooge to perform his procedure 6 times.

      For example if the initial number of allocated work days is (99, 268, 308) we have:

      26th: (99, 268, 308), mean = 225 → (225, 268, 308)
      27th: (225, 268, 308), mean = 267 → (267, 268, 308)
      28th: (267, 268, 308), mean = 281 → (281, 281, 308)
      29th: (281, 281, 308), mean = 290 → (290, 290, 308)
      30th: (290, 290, 308), mean = 296 → (296, 296, 308)
      31st: (296, 296, 308), mean = 300 → (300, 300, 308)

      But there doesn’t seem to be any specified conditions to allow us to narrow down these numbers to a set that would allow us to determine a specific date for Cratchit’s birthday.

      However, there is a particular date that suggests itself as the only one of the candidate birthdates that has a certain property, so maybe that is the required answer. And it is the same unique date that we get if we make certain assumptions about the behaviour of Scrooge.

      Solution: [See below]

      Like

      • Jim Randell's avatar

        Jim Randell 10:22 am on 1 January 2022 Permalink | Reply

        The published puzzle text is flawed.

        Apparently the setter intended that only one of the three would be “levelled up” each time the process is applied.

        This leads to a single levelling up process:

        26th: (1, 365, 366), mean = 244 → (244, 365, 366)
        27th: (244, 365, 366), mean = 325 → (325, 365, 366)
        28th: (325, 365, 366), mean = 352 → (352, 365, 366)
        29th: (352, 365, 366), mean = 361 → (361, 365, 366)
        30th: (361, 365, 366), mean = 364 → (364, 365, 366)
        31st: (364, 365, 366), mean = 365 → (365, 365, 366)

        So, before the “levelling up” process started the work days allocated were 1, 365, 366.

        Scrooge must be 366, as he is working an extra day compared to 1835, and there were 365 days in 1835. The extra day worked is the leap day in 1836, and this must also be Cratchit’s birthday.

        Cratchit must be 365, as Marley did not work on his birthday in 1835.

        Hence, Marley must be the 1. He only worked Christmas day.

        So the intended answer was:

        Solution: Bob Cratchit was born on 29th February 1796.

        And this was the answer I suspected, as it was the only date with a certain property in the required range (i.e. the only 29th February), without worrying about the “levelling up” stuff at all.

        Like

  • Unknown's avatar

    Jim Randell 7:30 am on 23 December 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 891: Ups and downs 

    From The Sunday Times, 3rd September 1978 [link]

    An electrician living in a block of flats has played a joke on the tenants by rewiring the lift. The buttons numbered 0 to 9 in the lift should correspond to the ground floor, first floor, etc., but he has rewired them so that although (for his own convenience) the buttons for the ground floor and his own floor work correctly, no other button takes you to its correct floor. Indeed when you get in the lift on the ground floor and go up, three of the buttons take you twice as high as they should, and two buttons take you only half as high as they should.

    The milkman is unaware of the rewiring and so early yesterday morning, rather bleary-eyed, he followed his usual ritual which consists of taking nine pints of milk into the lift, pushing button 9, leaving one pint of milk when the lift stops, pushing button 8, leaving one pint of milk when the lift stops, and so on in decreasing order until, having pushed button and having left his last pint, he usually returns to his van.

    However, yesterday when he tried to follow this procedure all seemed to go well until, having pushed button 1 , when the lift stopped he found a pint of milk already there. So he took the remaining pint back to his van, with the result that just one of the tenants (who lived on one of the floors below the electrician’s) did not get the pint of milk she’d expected.

    The surprising thing was that during the milkman’s ups and downs yesterday he at no time travelled right past the floor which he thought at that time he was heading towards.

    List the floors which received milk, in the order in which the milkman visited them.

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

    [teaser891]

     
    • Jim Randell's avatar

      Jim Randell 7:32 am on 23 December 2021 Permalink | Reply

      The electrician makes a journey by selecting buttons 9, 8, 7, …, 3, 2, 1, and in the process visits one of the floors twice and one of the floors no times. And the floor that is not visited is a lower number than the electrician’s floor (which is the only button that gives the correct floor).

      I assumed there are exactly 3 buttons that go to floor exactly twice the number on the button, and exactly 2 that go to a floor exactly half the number on the button.

      This Python 3 program chooses the buttons that go to floors twice and half their labelled values, and then fills out the rest of the mapping as it goes along. It runs in 53ms.

      Run: [ @replit ]

      from enigma import (irange, update, product, subsets, diff, singleton, trim, map2str, printf)
      
      # consider a journey made with buttons <bs>, visiting floors <fs> using map <m> (button -> floor)
      def journey(bs, fs, m):
        # are we done?
        if not bs:
          yield (fs, m)
        else:
          (b, f_) = (bs[0], fs[-1])
          # is the button assigned?
          f = m.get(b, None)
          if f is None:
            (ss, upd) = (irange(0, 9), 1)
          else:
            (ss, upd) = ([f], 0)
          # choose a target floor
          for f in ss:
            # if unassigned: cannot be the right floor, or twice or half
            if upd and (f == b or f == 2 * b or b == 2 * f): continue
            # cannot be a previously visited floor, except for the final floor
            if (f in fs) != (len(bs) == 1): continue
            # journey cannot pass the correct floor
            if not (f_ < b < f or f_ > b > f):
              m_ = (update(m, [(b, f)]) if upd else m)
              yield from journey(bs[1:], fs + [f], m_)
      
      # exactly 3 buttons go to twice the number, exactly 2 buttons go to half the number
      for (ds, hs) in product(subsets([1, 2, 3, 4], size=3), subsets([2, 4, 6, 8], size=2)):
        # remaining floors
        rs = diff(irange(1, 9), ds + hs)
        if len(rs) != 4: continue
      
        # choose the floor for the electrician
        for e in rs:
          if e == 1: continue
      
          # initial map
          m0 = { 0: 0, e: e }
          m0.update((k, 2 * k) for k in ds) # doubles
          m0.update((k, k // 2) for k in hs) # halfs
      
          # consider possible journeys for the milkman
          for (fs, m) in journey([9, 8, 7, 6, 5, 4, 3, 2, 1], [0], m0):
      
            # the unvisited floor is lower than e
            u = singleton(x for x in irange(1, 9) if x not in fs)
            if u is None or not (u < e): continue
      
            # output solution
            printf("{fs} {m}", fs=trim(fs, head=1, tail=1), m=map2str(m, arr='->'))
      

      Solution: The milkman visited floors: 7, 4, 2, 3, 5, 8, 6, 9.

      He then returns to floor 2, and finds he has already visited.

      The rewired map of button → floor is:

      0 → 0
      1 → 2 (twice)
      2 → 9
      3 → 6 (twice)
      4 → 8 (twice)
      5 → 5 (electrician’s floor)
      6 → 3 (half)
      7 → 2
      8 → 4 (half)
      9 → 7

      Like

  • Unknown's avatar

    Jim Randell 9:59 am on 21 December 2021 Permalink | Reply
    Tags:   

    Teaser 2849: Swift tailor 

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

    When measuring gentlemen’s chest sizes in inches, the tailors five foot long tape overlapped so that the set of numbers 1 to 9 was aligned with a consecutive set of higher numbers.

    Taking each pair of these nine aligned lower and higher numbers as a fraction the tailor saw that just two of the nine “fractions” were in their simplest form and did not cancel down (i.e. the pair of numbers had no common factor greater than one).

    All of this was also true when he measured the smaller waist size.

    What (in inches) were the gentleman’s chest and waist sizes?

    [teaser2849]

     
    • Jim Randell's avatar

      Jim Randell 10:00 am on 21 December 2021 Permalink | Reply

      Assuming the tape measure is graduated 1″ … 60″.

      The smallest possible measurement is: 1/10 … 9/18 corresponding to a measurement of 9″ (although that is ludicrously small). And the largest is: 1/52 … 9/60 (corresponding to 51″).

      This Python program runs in 46ms.

      Run: [ @replit ]

      from enigma import (irange, is_coprime, printf)
      
      # consider possible measurements
      for m in irange(9, 51):
        # count how many pairs are co-prime
        pairs = ((i, m + i) for i in irange(1, 9))
        ps = list(p for p in pairs if is_coprime(*p))
        # we are looking for measurements with 2 co-prime pairs
        if len(ps) == 2:
          printf("m={m} {ps}")
      

      Solution: The measurements are: chest = 42″; waist = 30″.

      When the measurement is 30″ we get the following lowest term fractions: 1/31, 7/37.

      When the measurement is 42″ we get the following lowest term fractions: 1/43, 5/47.

      Like

    • Hugh+Casement's avatar

      Hugh+Casement 1:12 pm on 21 December 2021 Permalink | Reply

      That works out at 76 and 107 cm: a fine figure of a man!
      Somehow I find 84 and 90 cm more realistic.

      Like

  • Unknown's avatar

    Jim Randell 10:13 am on 19 December 2021 Permalink | Reply
    Tags: by: Mr. A. C. Jolliffe   

    A Brain Teaser: Rose beds 

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

    In my garden I had, until recently, two square rose beds of different sizes. At my wife’s suggestion I modified the layout, making two square plots of equal size but leaving the area devoted to roses unchanged.

    My two next-door neighbours, as it happened, were also the possessors of square rose beds — each had three, one of a certain size with two equal beds of another size, the dimensions being different for the two neighbours. They, admiring the change in my garden, decided to follow suit. Each now has three square beds of equal size, but each has kept the same area as before devoted to roses. We have discovered that all our eight rose beds are now the same size, also that the side of every rose bed was and is an integral number of feet.

    Given that the final beds are of the smallest possible size fulfilling the conditions, how large were our original beds?

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. Three 1 guinea book tokens were offered for the first 3 correct solutions.

    [teaser-1955-12-25] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 10:13 am on 19 December 2021 Permalink | Reply

      I’m assuming “leaving the area devoted to roses unchanged”, means the total area of the plots after the change is the same as it was before.

      So equating the setters before and after areas we get:

      a² + b² = 2x²

      And using the same value of x for the neighbours we have:

      a² + 2b² = 3x²

      This program finds the smallest possible shared value of x, and the corresponding (a, b) values. It runs in 49ms.

      Run: [ @replit ]

      from enigma import (irange, inf, sq, is_square, printf)
      
      # find solutions for a^2 + k.b^2 = n
      def solve(k, n):
        for b in irange(1, inf):
          a2 = n - k * sq(b)
          if not (a2 > 0): break
          a = is_square(a2)
          if a is None: continue
          yield (a, b)
      
      # consider the final size
      for x in irange(1, inf):
      
        # find: a^2 + b^2 = 2x^2
        ss1 = list((a, b) for (a, b) in solve(1, 2 * sq(x)) if a < b)
        if not ss1: continue
      
        # find: a^2 + 2b^2 = 3x^2
        ss2 = list((a, b) for (a, b) in solve(2, 3 * sq(x)) if a != b)
        if not (len(ss2) > 1): continue
      
        # output solution
        printf("x={x}; ss1={ss1} ss2={ss2}")
        break
      

      Solution: The original beds were: setter = 7×7 + 23×23; neighbours = 25×25 + 2(11×11), 23×23 + 2(13×13).

      The size of the final beds is: 17×17 (the setter has 2, and each of the neighbours has 3).

      Like

  • Unknown's avatar

    Jim Randell 4:34 pm on 17 December 2021 Permalink | Reply
    Tags:   

    Teaser 3091: Birthday money 

    From The Sunday Times, 19th December 2021 [link] [link]

    My two daughters were born on the same day but seven years apart. Every birthday, with only one year’s exception, I have given them both five pounds for each year of their age. They are now grown-up, but I have continued to do this on their birthdays, except for the one year when I couldn’t afford to give either of them anything. Averaged out over all of their birthdays, including the one for which they received nothing, my elder daughter has now received 21 per cent more per birthday than her younger sister.

    How much in total have I given to my daughters as birthday presents?

    [teaser3091]

     
    • Jim Randell's avatar

      Jim Randell 5:08 pm on 17 December 2021 Permalink | Reply

      This Python program finds the solution constructively. It runs in 52ms.

      Run: [ @replit ]

      from enigma import (Rational, irange, unzip, printf)
      
      Q = Rational()
      
      # look for solutions
      def solve():
        # initial ages
        ages = [7, 0]
      
        # collect gifts (newest first)
        gifts = list(list(5 * x for x in irange(age, 1, step=-1)) for age in ages)
        totals = list(sum(x) for x in gifts)
      
        while True:
          # add in data for the next year
          for (i, _) in enumerate(ages):
            ages[i] += 1
            v = 5 * ages[i]
            gifts[i].insert(0, v)
            totals[i] += v
      
          # consider missing years
          for ms in unzip(gifts):
            # calculate averages without missing year
            avgs = list(Q(t - m, a) for (t, m, a) in zip(totals, ms, ages))
      
            # look for avgs[0] = 121% of avgs[1]
            if 100 * avgs[0] == 121 * avgs[1]:
              yield (ages, totals, ms)
      
      # find the first solution
      for (ages, totals, ms) in solve():
        t = sum(totals) - sum(ms)
        printf("total = {t}; ages = {ages}; missing={ms}")
        break
      

      Solution: The total of the gifts is £ 6660.

      The daughters are currently aged 40 and 33, and the missing amounts are £ 140 and £ 105, when the daughters were 28 and 21.

      In total the elder daughter has received £ 3960 (an average of £ 99 per year), and the younger has received £ 2700 (an average of £ 81 + 9/11 per year).

      And we have:

      121% of (81 + 9/11)
      = (121/100) × (900/11)
      = 99


      Analytically we see that the £ 5 multiplier cancels out and we are looking for values of:

      (T(n + 7) − (k + 7)) / (n + 7) = (121/100) (T(n) − k) / n

      where: n is the age of the younger daughter, and k ∈ [1, n] is her age in the missing year.

      This simplifies to:

      k = (3n² − 76n − 479)n / (6n + 242)

      And we can then just look for the first n that gives a viable value for k:

      from enigma import (irange, inf, div, T, printf)
      
      # consider age of youngest daughter
      for n in irange(1, inf):
        # calculate missing year
        k = div(((3 * n - 76) * n - 479) * n, 6 * n + 242)
        if not (k is None or k < 0 or k > n):
          # output solution
          t1 = T(n) - k
          t2 = T(n + 7) - (k + 7)
          t = 5 * (t1 + t2)
          printf("[n={n} k={k}] total = {t}; ages = ({n+7}, {n})")
          break
      

      Further analysis shows that in order for k to be positive we require: n > (38 + √2881) / 3 ≈ 30.56.

      And for k ≤ n we require: n ≤ 103/3 ≈ 34.33.

      So we only need to check k ∈ [31, 34], which we can do manually.

      n = 31 ⇒ k = 3.48…
      n = 32 ⇒ k = 11.87…
      n = 33 ⇒ k = 21
      n = 34 ⇒ k = 30.87…

      Like

    • GeoffR's avatar

      GeoffR 9:12 am on 18 December 2021 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Assume max daughters ages are 63 and 70 years and father is in his nineties.
      % Max UB amount - 1st daughter = 63 * 64 div 2 * 5 = 10080, say 10100
      % Max UB amount - 2nd daughter = 70 * 71 div 2 * 5 = 12425, say 12500
      
      var 1..63:D1; % max 1st daughter's age
      var 1..70:D2; % max 2nd daughter's age
      var 1..63: MA; % missed age year
      
      % Total gift amounts for two daughters
      var 5..10100: D1_tot;
      var 5..12500: D2_tot;
      var 10..22600: Total;
      
      % Elder daughter is 7 years older than her sister
      constraint D2 == D1 + 7;
      
      % Total gift amounts, allowing for the missing age year
      constraint D1_tot == 5 * D1 * (D1 + 1) div 2 - 5 * MA;
      constraint D2_tot == 5 * D2 * (D2 + 1) div 2 - 5 * (MA + 7);
      constraint Total == D1_tot + D2_tot;
      
      % Elder daughter received 21 per cent more per birthday than her sister
      % (D2_tot/D2) / (D1_tot/D1) = 121/100
      constraint 100 * D2_tot * D1 == 121 * D1_tot * D2;
      
      solve satisfy;
      
      output ["Total (£) = " ++ show(Total) ++ "\n" ++ "Daughter's ages are " ++ 
      show(D1) ++ " and " ++ show(D2) ++ " years"]; 
      
      
      
      

      Like

    • NigelR's avatar

      NigelR 3:23 pm on 19 December 2021 Permalink | Reply

      apologies Jim – I haven’t worked out how to post code properly. Is there a guide somewhere, please?

      acum,bcum=140,0  #140 is accumulated gifts for daughter 1 for birthdays 1-7
      for y in range(8,90): #start from 1st birthday for daughter 2
          acum+=5*y
          bcum+=5*(y-7)
          for i in range (8,y):  #iterate over previous years to remove 1 year at a time
              if abs(((acum-5*i)/y)-1.21*((bcum-(5*(i-7)))/(y-7)))<0.0001:
                  print("total gifted is:",(acum-5*i)+(bcum-5*(i-7)))
      

      Like

      • Jim Randell's avatar

        Jim Randell 3:44 pm on 19 December 2021 Permalink | Reply

        Hi,

        You can include code like this:

        [code language="python"]
        your code here
        [/code]
        

        For longer programs (80 lines for more) you can also include the collapse="true" parameter, to hide it until clicked on.

        Be careful though,it is not possible to edit a comment in WordPress once it is posted.

        Like

    • Tony Brooke-Taylor's avatar

      Tony Brooke-Taylor 7:31 am on 20 December 2021 Permalink | Reply

      With a bit of rearrangement of a formula for the ratio of averages you can pretty much solve this manually. Once you have the limits you can almost intuit the correct value for the younger daughter’s age but the program below loops until it finds a solution.

      # Find real roots of a quadratic equation with conventional coefficients a, b, c
      def quad_roots(a, b, c):
          r = (b ** 2 - 4 * a * c)
          if r >= 0:
              low = (-b - r**(1/2))/2/a
              up = (-b + r**(1/2))/2/a
              return low, up
      
      
      # Define missing_year as the age of the younger daughter when the gift was missed
      # Make this the subject of the equation formed from taking the ratio of averages given
      
      # The lower bound for the younger age can be inferred from the condition that missing_year exceeds 0
      low_lim = max([int(r) for r in quad_roots(3, -76, -479) if r > 0]) + 1
      # The upper bound for the younger age can be inferred from the condition that it exceeds missing_year
      up_lim = max([int(r) for r in quad_roots(3, -(76 + 6), -(479 + 242)) if r > 0]) + 1
      
      # Find the value for the age now of the younger daughter, such that missing_year is an integer
      for young_age, missing_year in ((n, (3 * n ** 2 - 76 * n - 479) / (6 * n + 242) * n) for n in range(low_lim, up_lim)):
          if missing_year % 1 == 0:
              total = 0
              for d in [0, 7]:
                  total += ((young_age + d) * ((young_age + d)+1)/2 - (missing_year + d))
              print("Total given to both daughters is", total * 5)
              break
      
      

      Like

  • Unknown's avatar

    Jim Randell 11:18 am on 16 December 2021 Permalink | Reply
    Tags: ,   

    Teaser 2844: Children’s children 

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

    The head teacher’s retirement celebration was attended by many former pupils. These included Adam, Brian, Colin and David, whose sons and grandsons had also attended the school – actually different numbers of grandsons for each of them, with Adam having the most. Their sons were Eric, Fred, George, Harry and Ivan, and the sons of the sons were John, Keith, Lawrence, Michael, Norman and Oliver.

    Altogether Adam and Brian had sons Eric, Fred and one other; altogether Adam and Colin had sons George and one other; altogether Eric, George and Harry had sons Keith, Michael, Norman and one other; and altogether Harry and Ivan had sons Lawrence, Norman and one other.

    The retirement gift was presented by the fathers of John’s and Oliver’s fathers.

    Who were they?

    As stated there are multiple solutions to this puzzle.

    This puzzle was not included in the published collection of puzzles The Sunday Times Brainteasers Book 1 (2019).

    There are now 600 puzzles available on S2T2.

    [teaser2844]

     
    • Jim Randell's avatar

      Jim Randell 11:19 am on 16 December 2021 Permalink | Reply

      As stated the puzzle has multiple solutions.

      The following Python program builds the 8 possible family trees that fit the described situation.

      It runs in 85ms.

      Run: [ @replit ]

      from enigma import (subsets, peek, product, map2str, join, printf)
      
      # map ks to vs
      def make_map(ks, vs):
        for ss in subsets(ks, size=len(vs), select='M'):
          d = dict((k, list()) for k in ks)
          for (k, v) in zip(ss, vs):
            d[k].append(v)
          yield d
      
      # find viable father -> sons mappings
      def check_fs(fs):
      
        # "A, B have sons E, F and one other"
        s = fs['A'] + fs['B']
        if not (len(s) == 3 and 'E' in s and 'F' in s): return False
      
        # "A, C have sons G and one other"
        s = fs['A'] + fs['C']
        if not (len(s) == 2 and 'G' in s): return False
      
        # looks OK
        return True
      
      # collect viable maps
      ffs = list(fs for fs in make_map('ABCD', 'EFGHI') if check_fs(fs))
      
      # find viable son -> grandsons maps
      def check_sg(sg):
          
        # "E, G, H have sons K, M, N and one other"
        s = sg['E'] + sg['G'] + sg['H']
        if not (len(s) == 4 and 'K' in s and 'M' in s and 'N' in s): return False
      
        # "H, I have sons L, N and one other"
        s = sg['H'] + sg['I']
        if not (len(s) == 3 and 'L' in s and 'N' in s): return False
      
        # looks OK
        return True
      
      # collect viable maps
      sgs = list(sg for sg in make_map('EFGHI', 'JKLMNO') if check_sg(sg))
      
      # find the father of x in map d
      def father(x, d):
        return peek(k for (k, v) in d.items() if x in v)
      
      # choose the maps
      for (fs, sg) in product(ffs, sgs):
      
        # A, B, C, D have different numbers of grandsons
        s = list(sum(len(sg[s]) for s in fs[f]) for f in 'ABCD')
        if len(set(s)) != 4: continue
        # A has the most
        if s[0] != max(s): continue
      
        # grandfather of J and O
        gJ = father(father('J', sg), fs)
        gO = father(father('O', sg), fs)
        # they must be different people
        if gJ == gO: continue
      
        # output solution
        f = lambda s: join(s, sep="+")
        fmt = lambda m: map2str((k, f(v)) for (k, v) in m.items())
        printf("{gs} [grandfather(J) = {gJ}; grandfather(O) = {gO}]", gs=f(sorted([gJ, gO])))
        printf("  father -> sons: {fs}", fs=fmt(fs))
        printf("  son -> grandsons: {sg}", sg=fmt(sg))
        printf()
      

      Solution: One of the presenters is Adam, but the other can be any of: Brian, Colin, or David.

      The originally published solution is: “Adam and David”, but a later correction was made (with Teaser 2846) noting there are three valid solutions to the puzzle (given above).


      However, there is a unique solution if the end of the puzzle is changed to:

      The retirement gift was presented by the paternal grandfather of John and Oliver.

      Who was he?

      We can change the sense of the test at line 68 to ensure that the grandfathers of John and Oliver are the same person, and we find there are 2 possible family trees that lead to this situation, but in both cases the grandfather is Brian.

      Like

    • Hugh+Casement's avatar

      Hugh+Casement 8:34 am on 17 December 2021 Permalink | Reply

      Without trying Jim’s program, I think it’s like this:
      A’s son is H whose sons are L, N, and either K or M. D’s son is I who has no son.
      C’s son is G whose son is M or K (whichever is not H’s son).
      Brian has sons E (who has no sons) and F who is the father of J and O.

      Like

  • Unknown's avatar

    Jim Randell 4:35 pm on 14 December 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 890: Round pond boat race 

    From The Sunday Times, 27th August 1978 [link]

    In our park is a circular pond exactly 50 metres in diameter, which affords delight to small boys who go down with ships.

    Two such youngsters, Arthur and Boris, took their model motor-cruisers there the other morning, and decided on a race. Each was to start his boat from the extreme North of the pond at the same moment, after which each was to run round the pond to await his boat’s arrival. The moment it touched shore its owner was to grab it by the bows and run with it directly to the South gate of the park, situated exactly 27 metres due South of the Southern edge of the pond. Both boats travelled at the same speed (1 metre in 3 seconds) but both owners, burdened with their respective craft, could manage only 3 metres in 4 seconds over the rough grass.

    When the race started, Arthur’s boat headed due South, but that of Boris headed somewhat East of South and eventually touched shore after travelling 40 metres in a straight line.

    Who arrived first at the gate, and by what time margin (to the nearest second)?

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

    [teaser890]

     
    • Jim Randell's avatar

      Jim Randell 4:36 pm on 14 December 2021 Permalink | Reply

      The boats start at S and travel to A and B, and are then carried to the gate at G. O is the centre of the pond, GX is tangent to the pond.

      For A, the distance travelled by the boat is SA = 50m, and the distance on foot is AG = 27m.

      So the total time taken is: 50 × 3 + 27 × 4/3 = 186 seconds.

      For B, we see that the triangle ABS is inscribed in a semicircle, so has a right angle at B, and the sides SA = 50m, SB = 40m (so AB = 30m), so if θ is the angle ASB we have:

      cos(θ) = 40/50 = 4/5

      We can then calculate the straight line distance BG using the cosine rule on triangle GSB:

      (BG)² = (GS)² + (BS)² − 2(GS)(BS)cos(θ)
      (BG)² = 77² + 40² − 2×77×40×4/5
      (BG)² = 2601
      BG = 51

      For B, the distance travelled by the boat is SB = 40m, and the straight line distance on foot is BG = 51m.

      So the total time taken is: 40 × 3 + 51 × 4/3 = 188 seconds.

      So it looks like A beats B by 2 seconds.

      However looking at the diagram, we note that the line BG actually passes through the pond, and so B would have to run around the circumference of the pool (along the arc BX), until he can make a straight line to G (along XG, a tangent to the circle).

      The straight line distance along the tangent, XG, is:

      (XG)² + 25² = (25 + 27)²
      (XG)² = 2079
      XG = 3√(231)

      We can calculate the angle SOB using the cosine rule:

      (SB)² = (OS)² + (OB)² − 2(OS)(OB)cos(𝛂)
      cos(𝛂) = (2×25² − 40²) / 2×25²
      cos(𝛂) = −7/25

      The amount of arc travelled, φ (the angle BOX), is then given by:

      φ = ψ − (𝛂 − 𝛑/2)

      Where ψ is the angle OGX, which we can calculate using the right angled triangle OXG:

      cos(ψ) = XG / (OB + BG)
      cos(ψ) = 3√(231)/52

      As expected, the difference made by following the arc instead of the straight line is not enough to change the result (which is given to the nearest second).

      from math import acos
      from enigma import (fdiv, sq, sqrt, pi, printf)
      
      # boat time
      boat = lambda d: d * 3
      
      # foot time
      foot = lambda d: fdiv(d, 0.75)
      
      # given distances
      R = 25
      SA = 2 * R
      SB = 40
      AG = 27
      
      # exact calculation?
      exact = 1
      
      # calculate time for A
      A = boat(SA) + foot(AG)
      printf("A = {A:.6f} sec")
      
      # calculate time for B
      SG = SA + AG
      cos_theta = fdiv(SB, SA)
      BG = sqrt(sq(SG) + sq(SB) - 2 * SG * SB * cos_theta)
      
      B = boat(40) + foot(BG)
      printf("B = {B:.6f} sec (approx)")
      
      if exact:
        # calculate XG
        XG = sqrt(sq(R + AG) - sq(R))
      
        # alpha is the angle SOB
        cos_alpha = fdiv(2 * sq(R) - sq(SB), 2 * sq(R))
        
        # psi is the angle OGX
        cos_psi = fdiv(XG, R + AG)
      
        # calculate phi (amount of arc)
        psi = acos(cos_psi)
        alpha = acos(cos_alpha)
        phi = psi - (alpha - 0.5 * pi)
      
        # calculate the exact distance for B
        BX = R * phi
        B = boat(40) + foot(BX + XG)
        printf("B = {B:.6f} sec (exact) [extra distance = {xB:.6f} m]", xB=BX + XG - BG)
        
      # calculate the difference
      d = abs(A - B)
      printf("diff = {d:.6f} sec")
      

      Solution: Arthur beats Boris by approximately 2 seconds.

      Following the arc is 3.95cm longer than the straight line distance, and adds 53ms to B’s time.

      The actual shortest distance from B to G can be expressed as:

      3\sqrt{231}\; +\; 25\cos ^{-1}\left( \frac{7}{52}\; +\; \frac{18\sqrt{231}}{325} \right)

      which is approximately 51.039494 m, compared to the straight line distance BG of 51 m.

      Like

      • galoisest's avatar

        galoisest 4:28 pm on 15 December 2021 Permalink | Reply

        Nice analysis Jim, I completely missed that the straight line goes through the pond.

        The simplest expression I can get for the difference including the arc is:

        100/3*(pi-acos(25/52)-2*acos(3/5)) + 4sqrt(231) – 66

        Like

        • Jim Randell's avatar

          Jim Randell 4:37 pm on 15 December 2021 Permalink | Reply

          It was more obvious in my original diagram (which had the line BG, instead of XG).

          But in this case it doesn’t matter if you spot this or not – the answer is unaffected. (I wonder if the setter intended us to calculate the exact distance, or just work out the length of BG).

          Like

  • Unknown's avatar

    Jim Randell 10:33 am on 12 December 2021 Permalink | Reply
    Tags: by: Mr. W. Blackman   

    Brain Teaser: Tea table talk 

    From The Sunday Times, 26th December 1954 [link]

    The Sales Manager of the Kupper Tea Company came into the Chief Accountant’s office. “I’ve got to report to the Chairman on the results of our new policy of selling pound packets only in four qualities only”, he said, “Which is the most popular brand — the 3s. 11d., 4s. 11d., 5s. 11d., or 6s. 11d.?”.

    “When reduced to pounds, shillings and pence”, replied the man of figures, the sales of each brand for last month differ from each other only in the pence column, and there only to the extent that the figures are 1d., 2d., 3d., and 4d., though not respectively”.

    “Very Interesting,” said the Sales Manager, but the Chairman will want a plain “yes” or “no” to whether we have reached our sales target. What is the answer to that?”.

    “The answer to that, my boy”, replied the Chief Accountant, “is all the further information you need to calculate the complete figures for yourself”.

    What are the figures?

    [Note: £1 = 20s, and 1s = 12d]

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. Prizes of £3, £2 and £1 were offered for the first 3 correct solutions.

    [teaser-1954-12-26] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 10:35 am on 12 December 2021 Permalink | Reply

      The following program steps through multiples of the largest price, and looks for multiples of the other prices which come in close to the same price, but with differing “pence” values.

      We find that there are many sets of 4 sales figures that differ only in the pence column, with values of (1, 2, 3, 4).

      If the sales target had been met (or exceeded) we would not know which of these to choose (if we find one set of figures that works, any larger set of figures would also work).

      However, if the sales target had not been met, and that is enough information to work out what the figures are, then we must be interested in the lowest set of possible sales figures, and the target must be higher than this, but less than the second lowest possible set of figures.

      The program runs in 166ms, so is fast enough, but this isn’t a suitable approach for a manual solution.

      Run: [ @replit ]

      from enigma import irange, inf, div, singleton, unpack, printf
      
      # pence -> (pounds, shillings, pence)
      def d2psd(n):
        (n, d) = divmod(n, 12)
        (p, s) = divmod(n, 20)
        return (p, s, d)
      
      # (pounds, shillings, pence) -> pence
      def psd2d(p, s, d):
        return 240 * p + 12 * s + d
      
      # solve for the specified prices, (A, B, C, D)
      def solve(A, B, C, D):
        # look for multiples of the largest price
        for n in irange(0, inf, step=D):
          (p, s, d) = d2psd(n)
          if 0 < d < 5:
            # so the other amounts were are interested in are...
            r = dict()
            for d_ in (1, 2, 3, 4):
              if d_ == d: continue
              k = singleton(x for x in (A, B, C) if div(psd2d(p, s, d_), x))
              if k:
                r[k] = (p, s, d_)
              else:
                break
            # have we found a value for each d?
            if len(r.keys()) == 3:
              r[D] = (p, s, d)
              yield r
      
      # prices (in ascending order)
      prices = list(psd2d(0, s, 11) for s in (3, 4, 5, 6))
      
      # look for the smallest solution
      for r in solve(*prices):
        for (k, (p, s, d)) in sorted(r.items(), key=unpack(lambda k, v: v)):
          n = div(psd2d(p, s, d), k)
          (_, ks, kd) = d2psd(k)    
          printf("({p}, {s}s, {d}d) = {n} pkts @ ({ks}s, {kd}d) each")
        printf()
        break
      

      Solution: The sales figures are:

      £53878, 1s, 1d = 182123 pkts @ 5s, 11d each
      £53878, 1s, 2d = 275122 pkts @ 3s, 11d each
      £53878, 1s, 3d = 219165 pkts @ 4s, 11d each
      £53878, 1s, 4d = 155792 pkts @ 6s, 11d each

      And the target must be above this.

      The next set of figures starts at £77098, 0s, 1d, so the target must be below this (otherwise there would be a choice of two sets of figures).

      Like

    • Hugh+Casement's avatar

      Hugh+Casement 2:10 pm on 12 December 2021 Permalink | Reply

      I wouldn’t like to have had to work that out in 1954, with no computer to hand.
      It would have been easier if the sales had been all the same: £68088 14s. 1d. is the LCM of the four packet prices, each a prime number of pence.

      In the days before the ubiquitous teabags, tea was normally sold in quarter-pound packets.
      I seem to remember a price of about 1s. 6d., but probably less in the ’50s.

      Like

    • GeoffR's avatar

      GeoffR 9:00 am on 14 December 2021 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Four standard sale prices (3s, 11d, 4s, 11d, 5s, 11d, 6s, 11d)
      int: P1 == 47; int: P2 == 59; int: P3 == 71; int: P4 == 83;
      
      % Amounts sold for each packet price
      var 100000..300000:pkt1; var 100000..300000:pkt2;
      var 100000..300000:pkt3; var 100000..300000:pkt4;
      
      constraint all_different([pkt1,pkt2,pkt3,pkt4]);
      
      % testing range for sales totals is £50,000 to £60,000
      % range values in old pence (240d = £1)
      var 12000000..14400000: T1;  
      var 12000000..14400000: T2;
      var 12000000..14400000: T3;
      var 12000000..14400000: T4;
      
      constraint T1 == pkt1 * P1 /\ T2 == pkt2 * P2
      /\ T3 == pkt3 * P3 /\ T4 == pkt4 * P4;
      
      constraint all_different([T1, T2, T3, T4]);
      
      % the four total sales values are 1d, 2d, 3d and 4d different 
      constraint max([T1, T2, T3, T4]) - min([T1, T2, T3, T4]) == 3;
      
      solve satisfy;
      
      output ["Totals(old pence) [T1, T2, T3, T4] = " ++ show([T1, T2, T3, T4]) 
      ++ "\nPackets (No.) = " ++ show([pkt1, pkt2, pkt3, pkt4])];
      
      % Totals(old pence) [T1, T2, T3, T4] = [12126799, 12126801, 12126800, 12126798]
      % Packets (No.) = [258017, 205539, 170800, 146106]
      % ----------
      % Totals(old pence) [T1, T2, T3, T4] = [12193116, 12193117, 12193114, 12193115]
      % Packets (No.) = [259428, 206663, 171734, 146905]
      % ----------
      % Totals(old pence) [T1, T2, T3, T4] = [12930734, 12930735, 12930733, 12930736]
      % Packets (No.) = [275122, 219165, 182123, 155792]  *** Published answer ***
      % ----------
      % Totals(old pence) [T1, T2, T3, T4] = [13664874, 13664872, 13664873, 13664871]
      % Packets (No.) = [290742, 231608, 192463, 164637]
      % ----------
      % ==========
      
      
      

      I worked in old decimal pence (240p = £1), looking for 1d differences in total sales values between 12,000,000 and 14,400,000 sales totals, in old pence. This search range is £50,000 to £60,000 in current money terms and was used to reduce search time. The Chuffed solver was used.

      Using Python’ divmod function for converting old money totals to current monetary values:

      T1 = 12930734 in old pence is:
      divmod(12930734,240) = (53878, 14) = £53878 1s 2d

      T2 = divmod(12930735,240)
      = (53878, 15) = £53878 1s 3d

      T3 = divmod(12930733,240)
      = (53878, 13) = £53878 1s 1d

      T4 = divmod(12930736,240)
      = (53878, 16) = £53878 1s 4d

      There were several solutions, including the published solution.

      Like

  • Unknown's avatar

    Jim Randell 1:17 pm on 10 December 2021 Permalink | Reply
    Tags:   

    Teaser 3090: Main line 

    From The Sunday Times, 12th December 2021 [link] [link]

    Anton and Boris live next to a railway line. One morning a goods train passed Anton’s house travelling south just as a slower train passed Boris’s house travelling north. The goods train passed Boris’s house at the same time as a passenger train, heading north at a speed that was half as fast again as the goods train. Similarly, as the slower train passed Anton’s house it passed a passenger train; this was heading south at a speed that was three times as great as that of the slower train.

    The passenger trains then passed each other at a point 25 kilometres from Anton’s house before simultaneously passing the two houses.

    All four trains travelled along the same route and kept to their own constant speeds.

    How far apart do Anton and Boris live?

    [teaser3090]

     
    • Jim Randell's avatar

      Jim Randell 1:38 pm on 10 December 2021 Permalink | Reply

      This is an exercise is generating and solving simultaneous equations. No programming necessary.

      If we suppose B lives a distance d from A.

      Initially (at time 0) if the goods train passes A travelling south at speed 2v, then it reaches B at a time of (d / 2v).

      At this time, the passenger train, with a speed of 3v passes B, heading north.

      And the slow train, travelling at speed 2fv (i.e. some fraction of v), reaches A at a time of (d / 2fv).

      And at this time a train travelling at 6fv passes A heading south.

      These trains pass at time t1 at a point 25 km south of A:

      t1 = (d / 2fv) + (25 / 6fv) = (3d + 25) / 6fv
      t1 = (d / 2v) + ((d − 25) / 3v) = (5d − 50) / 6v
      ⇒ f = (3d + 25) / (5d − 50)

      And then at time (t1 + t2) the two trains pass A and B:

      t2 = 25 / 3v
      t2 = (d − 25) / 6fv
      ⇒ f = (d − 25) / 50

      Equating these:

      (3d + 25) / (5d − 50) = (d − 25) / 50
      10(3d + 25) = (d − 25)(d − 10)
      30d + 250 = d² − 35d + 250
      d² − 65d = 0
      ⇒ d = 65

      So A and B are 65 km apart, and f = 4/5.

      We are not given any times, so we cannot determine the actual speeds of the trains.

      Solution: Anton and Boris live 65 km apart.

      Like

  • Unknown's avatar

    Jim Randell 11:27 am on 9 December 2021 Permalink | Reply
    Tags:   

    Teaser 2843: Child’s play 

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

    Liam has a set of cards numbered from one to twelve. He can lay some or all of these in a row to form various numbers. For example the four cards:

    6, 8, 11, 2

    give the five-figure number 68112. Also, that particular number is exactly divisible by the number on each of the cards used.

    In this way Liam used his set of cards to find another much larger number that was divisible by each of the numbers on the cards used — in fact he found the largest such possible number.

    What was that number?

    [teaser2843]

     
    • Jim Randell's avatar

      Jim Randell 11:29 am on 9 December 2021 Permalink | Reply

      To reduce the search space we can use some shortcuts.

      Considering the cards that are included in the number, if the “10” card is included, then it would have to appear at the end, and would preclude the use of cards “4”, “8”, “12”.

      Similarly if “5” is included, and any even card, then the number must end in “10” and the cards “4”, “8”, “12” are excluded.

      If any of the cards “3”, “6”, “9”, “12” are included then the overall digit sum must be a multiple of 3.

      Similarly if “9” is included, then the overall digit sum must be a multiple of 9.

      If any of the even cards (“2”, “4”, “6”, “8”, “10”) are used, the resulting number must also be even.

      This Python program uses a number of shortcuts. It considers the total number of digits in the number, as once a number is found we don’t need to consider any number with fewer digits. It runs in 263ms.

      Run: [ @replit ]

      from enigma import (enigma, irange, group, subsets, Accumulator, join, printf)
      
      # the cards
      cards = list(irange(1, 12))
      
      # map cards to their digit sum, and number of digits
      dsum = dict((x, enigma.dsum(x)) for x in cards)
      ndigits = dict((x, enigma.ndigits(x)) for x in cards)
      
      # find maximum arrangement of cards in ss
      def find_max(ss):
        # look for impossible combinations
        if (10 in ss) and (4 in ss or 8 in ss or 12 in ss): return
        even = any(x % 2 == 0 for x in ss)
        if (5 in ss and even) and (4 in ss or 8 in ss or 12 in ss): return
        # calculate the sum of the digits
        ds = sum(dsum[x] for x in ss)
        # check for divisibility by 9
        if (9 in ss) and ds % 9 != 0: return
        # check for divisibility by 3
        if (3 in ss or 6 in ss or 12 in ss) and ds % 3 != 0: return
        # look for max rearrangement
        r = Accumulator(fn=max)
        fn = (lambda x: (lambda x: (-len(x), x))(str(x)))
        for s in subsets(sorted(ss, key=fn, reverse=1), size=len, select="P"):
          if even and s[-1] % 2 != 0: continue
          # done, if leading digits are less than current max
          if r.value and s[0] != r.data[0] and str(s[0]) < str(r.value): break
          # form the number
          n = int(join(s))
          if all(n % x == 0 for x in ss): r.accumulate_data(n, s)
        return (r.value, r.data)
      
      # sort subsets of cards into the total number of digits
      d = group(subsets(cards, min_size=1), by=(lambda ss: sum(ndigits[x] for x in ss)))
      
      r = Accumulator(fn=max)
      for k in sorted(d.keys(), reverse=1):
        printf("[considering {k} digits ...]")
      
        for ss in d[k]:
          rs = find_max(ss)
          if rs is None: continue
          r.accumulate_data(*rs)
      
        if r.value: break
      
      printf("max = {r.value} [{s}]", s=join(r.data, sep=","))
      

      Solution: The number is 987362411112.

      The cards are: (1, 2, 3, 4, 6, 7, 8, 9, 11, 12).

      Like

      • Frits's avatar

        Frits 6:52 pm on 9 December 2021 Permalink | Reply

        If “5” is included then the number must end in “10” or “5” and the cards “4”, “8”, “12” are excluded (not relying on an even card). I got this from Brian’s solution on his puzzle site.

        Like

        • Jim Randell's avatar

          Jim Randell 10:04 am on 10 December 2021 Permalink | Reply

          @Frits: Good point.

          So if “5” or “10” are used, none of “4”, “8”, “12” can be used. And vice versa, if “4”, “8” or “12” are used, none of “5” or “10” can be used. So at least 3 digits must be absent, and we can start the search from 12 digits.

          Here’s a neater version of my code:

          from enigma import (enigma, irange, group, subsets, mlcm, Accumulator, join, printf)
          
          # the cards
          cards = list(irange(1, 12))
          
          # map cards to their digit sum, and number of digits
          dsum = dict((x, enigma.dsum(x)) for x in cards)
          ndigits = dict((x, enigma.ndigits(x)) for x in cards)
          
          # find maximum arrangement of cards in ss
          def find_max(ss):
            d = mlcm(*ss)
            even = any(x % 2 == 0 for x in ss)
            # look for max rearrangement
            r = Accumulator(fn=max)
            fn = (lambda x: (lambda x: (-len(x), x))(str(x)))
            for s in subsets(sorted(ss, key=fn, reverse=1), size=len, select="P"):
              if even and s[-1] % 2 != 0: continue
              # done, if leading digits are less than current max
              if r.value and s[0] != r.data[0] and str(s[0]) < str(r.value): break
              # form the number
              n = int(join(s))
              if n % d == 0: r.accumulate_data(n, s)
            return (r.value, r.data)
          
          # reject impossible subsets
          def check(ss):
            # look for impossible combinations
            if (5 in ss or 10 in ss) and (4 in ss or 8 in ss or 12 in ss): return False
            # calculate the sum of the digits
            ds = sum(dsum[x] for x in ss)
            # check for divisibility by 9
            if (9 in ss) and ds % 9 != 0: return False
            # check for divisibility by 3
            if (3 in ss or 6 in ss or 12 in ss) and ds % 3 != 0: return False
            # looks OK
            return True
          
          # sort subsets of cards into the total number of digits
          d = group(subsets(cards, min_size=1), st=check, by=(lambda ss: sum(ndigits[x] for x in ss)))
          
          r = Accumulator(fn=max)
          for k in sorted(d.keys(), reverse=1):
            printf("[considering {k} digits ...]")
          
            for ss in d[k]:
              rs = find_max(ss)
              if rs is None: continue
              r.accumulate_data(*rs)
          
            if r.value: break
          
          printf("max = {r.value} [{s}]", s=join(r.data, sep=","))
          

          Like

    • GeoffR's avatar

      GeoffR 2:30 pm on 10 December 2021 Permalink | Reply

      In this first solution, I assumed it was reasonable for the largest number to start with the digits 9,8,7 to minimise a solution run-time. It also found the seven possible solutions for Liam’s numbers from which the maximum can be found.

      from enigma import Timer
      timer = Timer('ST2843', timer='E')
      timer.start()
      
      from itertools import permutations
      
      # assume number from cards is ABCDEFGHIJ, with A = 9, B = 8 and C = 7
      # Also no digit in A..J is 5 or 10 - see previous postings
      cards = set((1, 2, 3, 4, 6, 7, 8, 9, 11, 12))
      
      # Assume first three digits must be 9, 8 and 7 to maximise Liam's number
      A, B, C = 9, 8, 7
      a, b, c = str(A), str(B), str(C)
      
      cards2 = cards.difference([A, B, C])
      
      Liam_nums = []
      
      # permute remainder of cards
      for p1 in permutations(cards2, 7):
        D, E, F, G, H, I, J = p1
        d, e, f, g = str(D), str(E), str(F), str(G)
        h, i, j = str(H), str(I), str(J)
        num = int(a + b + c + d + e + f + g + h + i + j)
        if all(num % d == 0 for d in (A, B, C, D, E, F, G, H, I, J)):
          if num not in Liam_nums:
            Liam_nums.append(num)
      
      # find largest number in the list
      print (f"Largest possible number = { max(Liam_nums)}")
      timer.stop()      
      timer.report()
      
      # Largest possible number = 987362411112
      #[ST2843] total time: 0.0151168s (15.12ms)
      
      print(Liam_nums)
      # There are only seven possible numbers in Liam's list:
      # [987162411312, 987111312264, 987241136112, 987211614312,
      #  987341621112, 987362411112, 987113624112]
      
      
      

      In the second solution, I did a full permutation solution for the 12 cards, which had an expected longer run-time, as there were over 6,300 potential numbers in the list.

      
      from enigma import Timer
      timer = Timer('ST2843', timer='E')
      timer.start()
      
      from itertools import permutations
      
      # Assume digits 5 & 10 not included (previous postings)
      # Assume 10-digit number is ABCDEFGHIJ
      cards = set((1, 2, 3, 4, 6, 7, 8, 9, 11, 12))
      
      Liam_nums = []
      
      for p1 in permutations(cards):
        A, B, C, D, E, F, G, H, I, J = p1
        a, b, c = str(A), str(B), str(C)
        d, e, f, g = str(D), str(E), str(F), str(G)
        h, i, j = str(H), str(I), str(J)
        num = int(a + b + c + d + e + f + g + h + i + j)
        if all(num % d == 0 for d in (A, B, C, D, E, F, G, H, I, J)):
          if num not in Liam_nums:
            Liam_nums.append(num)
      
      # find largest number in the list
      print (f"Largest possible number = { max(Liam_nums)}")
      timer.stop()      
      timer.report()
      
      #Largest possible number = 987362411112
      #[ST2843] total time: 9.1977548s (9.20s)
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:00 am on 7 December 2021 Permalink | Reply
    Tags: by: Bryan Thwaites   

    Brain-Teaser 889: Counting the hours 

    From The Sunday Times, 20th August 1978 [link]

    At school the other day, little Johnny was working with one of those boards with twelve clock-points regularly spaced round a circle against which should be put twelve counters showing the hours.

    He was, in fact, in a bit of a daze about the whole thing and the only hour he was absolutely dead sure of was 12 whose counter he correctly placed. As for the eleven others, if the truth be told, he just put them around at random.

    But Jill, his teacher, spotted some curious things. She first noticed that, however she chose a quartet of counters which formed the corners of a square, their sum was always the same.

    Next, she saw that if she formed numbers by multiplying together the counters at the corners of each square, one of those numbers was more than six times one of the others.

    She also observed that the counters in each quartet were, starting at the lowest, in ascending order of magnitude in the clockwise direction, and that 12 was not the only correct counter.

    In break, she reported all this to her colleague, Mary, adding “If I were to tell you, in addition, how many hours apart from the 12 Johnny had got right, you could tell me which they were”.

    Which were they?

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

    [teaser889]

     
    • Jim Randell's avatar

      Jim Randell 10:01 am on 7 December 2021 Permalink | Reply

      There are three squares that can be constructed, using the numbers in positions (12, 3, 6, 9), (1, 4, 7, 10), (2, 5, 8, 11). These partition the 12 numbers into 3 sets of 4.

      The total sum of the numbers is T(12) = 78, so values for each set must sum to 78/3 = 26.

      The following Python program runs in 48ms. (Internal runtime is 680µs).

      Run: [ @replit ]

      from enigma import (irange, subsets, diff, ediv, tuples, multiply, cproduct, filter_unique, unpack, printf)
      
      # find viable layouts
      def generate():
        ns = list(irange(1, 12))
        T = sum(ns)
        t = ediv(T, 3)
      
        # choose 3 numbers to go with 12 (and they must be in ascending order)
        n12 = 12
        for (n3, n6, n9) in subsets(diff(ns, [n12]), size=3):
          s1 = (n3, n6, n9, n12)
          if sum(s1) != t: continue
          p1 = multiply(s1)
      
          # choose numbers for (1, 4, 7, 10)
          for s2 in subsets(diff(ns, s1), size=4):
            if sum(s2) != t: continue
            p2 = multiply(s2)
      
            # remaining numbers
            s3 = diff(ns, s1 + s2)
            p3 = multiply(s3)
      
            # one of the products must be more than 6 times one of the others
            if not (max(p1, p2, p3) > 6 * min(p1, p2, p3)): continue
      
            # assign s1 to (1, 4, 7, 10) and s2 to (2, 5, 8, 11)
            fn = lambda s: tuples(s, 4, circular=1)
            for ((n1, n4, n7, n10), (n2, n5, n8, n11)) in cproduct([fn(s2), fn(s3)]):
              # construct the arrangement
              vs = [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12]
              # find the numbers in the correct positions
              ss = tuple(n for (i, n) in enumerate(vs, start=1) if i == n)
              # there should be more than 1
              if len(ss) > 1:
                yield (vs, ss)
      
      # knowing len(ss) allows you to determine ss
      r = filter_unique(generate(), unpack(lambda vs, ss: len(ss)), unpack(lambda vs, ss: ss))
      
      # output solutions
      for (vs, ss) in r.unique:
        printf("{vs} -> {ss}")
      

      Solution: Johnny also placed 4 and 10 in the correct positions (as well as 12).

      There are two positions that lead to this:

      They both have squares with values (1, 2, 11, 12), (3, 4, 9, 10), (5, 6, 7, 8). But in the second one the (5, 6, 7, 8) square is rotated through 180°.

      The sums of the squares are all 26, and the products are (264, 1080, 1680), and 1680 > 1584 = 6 × 264.


      We find there are 14 possible arrangements that give the situation described.

      10 of them have 12 plus either 5, 7, or 8 in the correct position.

      2 of them have 12 plus (4, 5, 10) or (4, 8, 10) in the correct position.

      And the remaining two are those shown above with 12 plus (4, 10) in the correct position.

      Like

      • Frits's avatar

        Frits 9:22 am on 8 December 2021 Permalink | Reply

        @Jim, you also could have used decompose() to find 4 numbers which sum to “t”.

        Like

    • GeoffR's avatar

      GeoffR 7:50 pm on 7 December 2021 Permalink | Reply

      # four square group positions are (12,3,6,9), (1,4,7,10), (2,5,8,11)
      # sum of digits (1-12) = 78, so sum of each group is 26
      
      hours = set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
      H12 = 12   # set correct counter for Hour 12
      
      from itertools import permutations
      
      # 1st square group positions (12,3,6,9)
      for p1 in permutations(hours, 3):
        # counters for hours H3, H6 and H9
        H3, H6, H9 = p1  
        if H3 + H6 + H9 + H12 != 26:continue
        if not (H3 < H6 < H9 < H12):continue
        q1 = hours.difference([H3, H6, H9])
        
        # 2nd  square group positions (1,4,7,10)
        for p2 in permutations(q1, 4):
          # counters for hours H1, H4, H7, H9
          H1, H4, H7, H10 = p2
          if H1 +  H4 + H7 + H10 != 26:continue
          if not (H1 < H4 < H7 < H10):continue  
          q2 = hours.difference(p1).difference(p2)
          
          # 3rd square group positions (2,5,8,11)
          for p3 in permutations(q2,4):
            # counters for hours H2, H5, H8, H11
            H2, H5, H8, H11 = p3
            if H2 + H5 + H8 + H11 != 26:continue
            if not (H2 < H5 < H8 < H11):continue
            
            # find multiples of the corners of three groups
            m1 = H3 * H6 * H9 * H12
            m2 = H1 * H4 * H7 * H10
            m3 = H2 * H5 * H8 * H11
            m1, m3 = min([m1, m2, m3]), max([m1, m2, m3])
            if m1 < m2 < m3:
              # check one multiple is more than 6X another multiple
              if m2 > 6 * m1 or m3 > 6 * m1:
                print(f" Three products are {m1}, {m2}, and {m3}.")
                print(f" (H1, H2, H3) = ({H1}, {H2}, {H3})")
                print(f" (H4, H5, H6) = ({H4}, {H5}, {H6})")
                print(f" (H7, H8, H9) = ({H7}, {H8}, {H9})")
                print(f" (H10, H11, H12) = ({H10}, {H11}, {H12})")
      
      # Three products are 264, 1080, and 1680.
      # (H1, H2, H3) = (3, 5, 1)
      # (H4, H5, H6) = (4, 6, 2)
      # (H7, H8, H9) = (9, 7, 11)
      # (H10, H11, H12) = (10, 8, 12)
      # Only counters for hours 4, 10 and 12 are in the correct position.
      
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 9:21 am on 8 December 2021 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % sum of digits 1..12 = 78, sum of each of three squares = 26
      % counter was correctly placed for 12 o'clock
      int: H12 == 12; 
      
      % Hour values
      var 1..11: H1; var 1..11: H2; var 1..11: H3; var 1..11: H4;
      var 1..11: H5; var 1..11: H6; var 1..11: H7; var 1..11: H8;
      var 1..11: H9; var 1..11: H10; var 1..11: H11; 
      
      constraint all_different ([H1,H2,H3,H4,H5,H6,H7,H8,H9,H10,H11,H12]);
      
      % Corners sum for 1st square  (positions 12,3,6,9)
      constraint H12 + H3 + H6 + H9 == 26;
      constraint H3 < H6 /\ H6 < H9 /\ H9 < H12;
      
      % Corners sum for 2nd square  (positions 1,4,7,10)
      constraint H1 + H4 + H7 + H10 == 26;
      constraint H1 < H4 /\ H4 < H7 /\ H7 < H10;
      
      % Corners sum for 3rd square  (positions 2,5,8,11)
      constraint H2 + H5 + H8 + H11 == 26;
      constraint H2 < H5 /\ H5 < H8 /\ H8 < H11;
      
      % Multiples of corner values - (sum 4 corners = 26, av < 7),
      % LB = 1*2*3*4 = 24, UB approx 7^4 = 2401, say 2400 
      var 24..2400:m1; var 24..2400:m2; var 24..2400:m3; 
      
      constraint m1 == H3 * H6 * H9 * H12;
      constraint m2 == H1 * H4 * H7 * H10;
      constraint m3 == H2 * H5 * H8 * H11;
      
      constraint m1 < m2 /\ m2 < m3;
      % one of the multiples was more than six times one of the others
      constraint m2 > 6 * m1 \/ m3 > 6 * m1;
      
      solve satisfy;
      
      % H12 = 12 << Hour 12 correct - stated value
      
      % H1 = 3;
      % H2 = 5;
      % H3 = 1;
      % H4 = 4;   << Hour 4 correct
      % H5 = 6;
      % H6 = 2;
      % H7 = 9;
      % H8 = 7;
      % H9 = 11;
      % H10 = 10;  << Hour 10 correct
      % H11 = 8;
      % m1 = 264;
      % m2 = 1080;
      % m3 = 1680;
      % % time elapsed: 0.15 s
      % ----------
      % ==========
      % Finished in 182msec
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:57 am on 5 December 2021 Permalink | Reply
    Tags: by: Mr F. V. Rowden   

    Brain Teaser: The cottage 

    From The Sunday Times, 23rd December 1956 [link]

    A man sold his country cottage, receiving the proceeds in £5 notes, and decided to divide the money among his four sons. He first changed the money Into £1 notes. “To you, Arthur”, he said, “I will give one-quarter. You, Bernard, will take one-quarter of the remainder. Charles can have one-quarter of what is left, and David one-fourth of the rest. You will then each take one-quarter of the remainder”.

    It was found that before each successive sum could be divided exactly by four, it was necessary to remove one pound note, which the father put in his pocket £5 in all.

    How much did the cottage realise?

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961.

    [teaser-1956-12-23] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 8:58 am on 5 December 2021 Permalink | Reply

      This is a “monkey and coconuts” problem (see: Enigma 258, Puzzle #86).

      This program finds the first few possible solutions. The first of these is the only one that gives a viable price for the cottage (according to the published solution).

      Run: [ @replit ]

      from enigma import (irange, inf, div, arg, printf)
      
      def solve():
        # consider selling price of the cottage
        for x in irange(5, inf, step=5):
          t = x
      
          # A's amount
          t -= 1
          A = div(t, 4)
          if A is None: continue
      
          # B's amount
          t -= A + 1
          B = div(t, 4)
          if B is None: continue
      
          # C's amount
          t -= B + 1
          C = div(t, 4)
          if C is None: continue
      
          # D's amount
          t -= C + 1
          D = div(t, 4)
          if D is None: continue
      
          # and each gets 1/4 of what is left
          t -= D + 1
          q = div(t, 4)
          if q is None: continue
      
          yield (x, A + q, B + q, C + q, D + q)
      
      # find the first few solutions
      n = arg(1, 0, int, "number of solutions to find")
      for (i, (x, A, B, C, D)) in enumerate(solve(), start=1):
        printf("[{i}] x={x}; A={A} B={B} C={C} D={D}")
        if i == n: break
      

      Solution: The value of the cottage was £2045.

      The father keeps £5 and the split of the remaining £2040 is: A=672, B=544, C=448, D=376.

      Like

      • Jim Randell's avatar

        Jim Randell 9:37 am on 5 December 2021 Permalink | Reply

        Using this analysis [@wolfram] we see that the solutions to the problem are given by:

        N = 1024k − 3

        for k = 1, 2, 3, … where N is a multiple of 5.

        k = 1: N = 1021
        k = 2: N = 2045 (*** SOLUTION ***)

        And we can write a more efficient program:

        from enigma import (irange, inf, ediv, arg, printf)
        
        def solve():
          for k in irange(1, inf):
            x = 1024 * k - 3
            if x % 5 != 0: continue
        
            # calculate amounts
            t = x - 1
            A = ediv(t, 4)
            t -= A + 1
            B = ediv(t, 4)
            t -= B + 1
            C = ediv(t, 4)
            t -= C + 1
            D = ediv(t, 4)
            t -= D + 1
            q = ediv(t, 4)
        
            yield (x, A + q, B + q, C + q, D + q)
        
        # find the first few solutions
        n = arg(1, 0, int, "number of solutions to find")
        for (i, (x, A, B, C, D)) in enumerate(solve(), start=1):
          printf("[{i}] x={x}; A={A} B={B} C={C} D={D}")
          if i == n: break
        

        Like

  • Unknown's avatar

    Jim Randell 4:56 pm on 3 December 2021 Permalink | Reply
    Tags:   

    Teaser 3089: An old square 

    From The Sunday Times, 5th December 2021 [link] [link]

    Archaeologists have excavated an ancient area that has the shape of a perfect square. Fragmentary documents give some information about its measurements: the number of major units along a side is less than the remaining number of minor units; there is a whole number of minor units in a major unit (no more than 10); and the area is a certain number of square major units plus a remainder of 15 square minor units.

    Expressed just in terms of minor units, how many are there along a side?

    [teaser3089]

     
    • Jim Randell's avatar

      Jim Randell 5:08 pm on 3 December 2021 Permalink | Reply

      If we suppose a major unit consists of x minor units (x ≤ 10), then the side of the square can be expressed as a major units plus b minor units (= ax + b minor units) where a < b < x.

      The following Python program runs in 45ms.

      Run: [ @replit ]

      from enigma import (irange, subsets, div, sq, printf)
      
      # consider x minor units in a major unit (no more than 10)
      # consider a major + b minor units (a < b < x)
      for (a, b, x) in subsets(irange(1, 10), size=3):
        # the side of the square is (ax + b) (minor units)
        s = a * x + b
        # can s^2 be expressed as k x^2 + 15?
        k = div(sq(s) - 15, sq(x))
        if k is not None:
          printf("x={x} a={a} b={b}; s={s} (= {a}x + {b}); s^2 = {k}x^2 + 15")
      

      Solution: The area is a square with each side measuring 41 minor units.

      So the entire area measures 41 × 41 = 1681 minor units.

      There are 7 minor units in a major unit (so the side of the square is 5 major + 6 minor units).

      And the area of the square can be expressed as 34 square major + 15 square minor units.

      Like

    • Frits's avatar

      Frits 3:28 pm on 4 December 2021 Permalink | Reply

      I have the feeling X might be logically determined as the Wolframalpha site for this equation only shows solutions for a specific positive X.

        
      #!/usr/bin/env python3 -m enigma -r
       
      SubstitutedExpression
       
      --base=11
       
      "B < X"
      
      # if X is even then A * X + B must be odd so B must be odd as well
      "X % 2 or B % 2"
      
      # if X is 10 then sq(A * X + B) ends on a 5 so B must be 5
      "X < 10 or B == 5"
      
      "A < B"
       
      "div(2 * A * B * X + B * B - 15, sq(X))"
       
      --answer="A * X + B"
      # if X is 5 then sq(A * X + B) ends on 0 or 5 so B must be 0 or 5, impossible
      --invalid="0|1|2|3|5,X"
      --invalid="0|1|10,B"
      # A can't be 8 as if X is 10 then B must be 5
      --invalid="0|8|9|10,A"
      #--verbose=256   # print generated code
      

      Like

    • GeoffR's avatar

      GeoffR 9:29 am on 5 December 2021 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % a major units, x minor units in one major unit and b minor units
      var 1..9:a; var 1..9:b; var 1..9:x;
      % S = side of perfect square
      var 1..50:S;
      
      constraint a < b  /\ b < x;
      constraint S == a * x + b;  
      
      % the area is a certain number of square major units
      % plus a remainder of 15 square minor units
      constraint S * S mod (x*x) == 15;
      
      solve satisfy;
      
      output["Side in minor units = " ++ show(S) ++ "  " 
      ++ "\n" ++ "a = " ++ show(a)  ++ ", b = " ++ show(b) ++ ", x = " ++ show(x)];
      
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 9:54 am on 5 December 2021 Permalink | Reply

        @GeoffR: x is “no more than 10”, so [[ var 1..10: x; ]] would be more appropriate.

        Like

    • GeoffR's avatar

      GeoffR 10:19 am on 5 December 2021 Permalink | Reply

      @Jim: Yes, correct. I was assuming x < 10.
      Fortunately, it does not affect the answer.

      Like

  • Unknown's avatar

    Jim Randell 8:42 am on 2 December 2021 Permalink | Reply
    Tags:   

    Teaser 2841: Crenellation aggregation 

    From The Sunday Times, 5th March 2017 [link] [link]

    The castle’s crenellated outer walls formed a pentagon, and on a family visit we decided to count the crenels. My son counted the number on each side and found that these totals on each side were five consecutive two figure numbers. My daughter and wife started together and then one of them walked clockwise around the walls and the other walked anticlockwise. They each counted the crenels they passed until they met. Their totals were two different prime numbers (with no prime number between the two). I consulted the tourist leaflet and found that the total number of crenels was in fact the product of three prime numbers.

    How many crenels were there in total?

    [teaser2841]

     
    • Jim Randell's avatar

      Jim Randell 8:43 am on 2 December 2021 Permalink | Reply

      We can express the the total number of crenels in three different ways (assuming no mistakes were made in the counting):

      t = 5n + 10 (where 10 ≤ n ≤ 95, so 60 ≤ t ≤ 485)
      t = p1 + p2 (where p1 and p2 are consecutive primes)
      t = q1 × q2 × q3 (where q1, q2, q3 are primes)

      This Python program looks at consecutive primes, until it finds a total that satisfies the remaining two expressions.

      It runs in 47ms.

      Run: [ @replit ]

      from enigma import (primes, tuples, div, printf)
      
      # consider consecutive pairs of primes (primes.between(29, 242) is sufficient)
      for (p1, p2) in tuples(primes.between(2, 485), 2):
        # form the total
        t = p1 + p2
        if t < 60: continue
        if t > 485: break
      
        # check it can be expressed as 5n + 10
        n = div(t - 10, 5)
        if n is None: continue
      
        # check it is the product of three prime numbers
        qs = primes.factor(t)
        if len(qs) != 3: continue
      
        # output solution
        printf("{t} = +({n}..{n4}) = {p1} + {p2} = *{qs}", n4=n + 4)
      

      Solution: There were 410 crenels in total.

      So we have:

      410 = 80 + 81 + 82 + 83 + 84
      410 = 199 + 211
      410 = 2 × 5 × 41

      Like

    • Frits's avatar

      Frits 10:59 pm on 2 December 2021 Permalink | Reply

      More analysis and using the 6n + 1 or 6n – 1 rule (just for fun).

      Other than 2 and 3, prime numbers must be of the form 6n + 1 or 6n – 1.

      Enigma function is_prime() is called only once.
      Function islice() is borrowed from enigma function first().

        
      from itertools import islice
      from enigma import is_prime
      
      # t = 5n + 10 (where 10 <= n <= 95, so 60 <= t <= 485)
      # t = p1 + p2 (where p1 and p2 are consecutive primes) ==> t is even
      # t = q1 × q2 × q3 (where q1, q2, q3 are primes and q1 <= q2 <= q3) 
      # t is even ==> q1 = 2, n is even and 30 <= q2 x q3 <= 242
      
      # n is even ==> n = 2 * m and 5 <= m <= 47
      # t = p1 + p2 = 2 * (q2 * q3) = 10m + 10 = (m + 1) * 10
      
      # t must end on a zero ==> q2 must be 5 and 6 <= q3 <= 48
      # t = 10 * q3 ==> q3 = m + 1
      
      # list of prime numbers from 6 up to 48 (used for q3)
      P = {3, 5, 7}
      P |= {x for x in range(11, 49, 2) if all(x % p for p in P)}
      P = {x for x in P if x > 5}
      
      # try to express next prime as 6k - 1 or 6k + 1
      def nextprime(n):
        # in this puzzle n always ends on a 5
        return list(islice([p for x in range(n // 6 + 1, 81) for i in [-1, 1] 
                    if is_prime(p := 6 * x + i) and p > n], 0, 1))[0]
      
      # list of prime numbers from 31 (prime before 5 x 7) up to 235 (5 x 47)
      P2 = {3, 5, 7, 11, 13, 17}
      P2 |= {x for x in range(19, 236, 2) if all(x % p for p in P2)}
      
      # try to express previous prime as 6k - 1 or 6k + 1
      def prevprime(n):
        # in this puzzle n always ends on a 5
        return list(islice([p for x in range(n // 6, 0, -1) for i in [1, -1] 
                    if (p := 6 * x + i) in P2 and p < n], 0, 1))[0]
      
      bef35 = prevprime(35) # prime number before 5 x lowest q3 prime
      P2 = {x for x in P2 if x >= bef35}
      
      # add one more prime (to be found by nextprime_P2() )
      np = nextprime(max(P2))
      P2.add(np)
      P2limit = np // 6 + 1  # variable to be used in nextprime_P2()
      
      # try to express next prime as 6k - 1 or 6k + 1
      def nextprime_P2(n):
        # in this puzzle n always ends on a 5
        return list(islice([p for x in range(n // 6 + 1, P2limit) for i in [-1, 1]
                    if (p := 6 * x + i) in P2 and p > n], 0, 1))[0]   
      
      # t = q1 x q2 x q3 = 2 x 5 x q3
      for q3 in P:
        t = 10 * q3  
        # find prime before half of t
        p1 = prevprime(5 * q3) 
        
        # t = p1 + p2 (where p1 and p2 are consecutive primes)
        if t - p1 not in P2: continue
        
        if nextprime_P2(5 * q3) != t - p1: continue
        
        print(t, "crenels in total")
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:30 pm on 3 December 2021 Permalink | Reply

        I’m considering adding [[ Primes.before(n) ]] and [[ Primes.after() ]] to the prime sieve class, which would give you the largest prime less than n and the smallest prime greater than n.

        [Note: These routines are now available in enigma.py]

        The puzzle could then be solved with:

        Run: [ @replit ]

        from enigma import (primes, printf)
        
        primes.expand(252)
        
        for q in primes.between(6, 48):
          m = 5 * q
          p1 = primes.before(m)
          p2 = primes.after(m)
          t = 2 * m
          if p1 + p2 == t:
            n = 2 * q - 2
            # output solution
            printf("t={t} = +({p1}, {p2}) = *(2, 5, {q}) = +({n}..{n4})", n4=n + 4)
        

        Like

  • Unknown's avatar

    Jim Randell 9:33 am on 30 November 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 888: Master pieces 

    From The Sunday Times, 13th August 1978 [link]

    The artist Pussicatto was exhibiting his new painting. It consisted of a 5-by-5 square of small squares with some of the small squares coloured black and the rest of the small squares coloured white.

    The forger Coppicatto sent six of his assistants to make copies of different parts of the painting. They returned with:

    Unfortunately five of the assistants could not remember which way up their  parts should go, and the other assistant, who gave his part the right way up, had copied the colour of one of the small squares wrongly. However the other five parts did cover the whole of the original painting.

    Reproduce the original Pussicatto painting.

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

    [teaser888]

     
    • Jim Randell's avatar

      Jim Randell 9:35 am on 30 November 2021 Permalink | Reply

      Considering the 5 pieces that are correct, but of unknown orientation. The entire painting is covered by these 5. In particular each of the corner sub-squares of the painting must correspond to 4 of the pieces (in some orientation), so we can look for those.

      The following Python 3 program runs in 110ms.

      Run: [ @replit ]

      from enigma import (irange, repeat, subsets, cproduct, join, printf)
      
      # the 6 pieces
      pieces = {
        1: (0, 1, 0, 0, 1, 0, 0, 1, 1),
        2: (1, 0, 1, 0, 0, 1, 0, 1, 1),
        3: (0, 1, 0, 1, 0, 0, 1, 1, 1),
        4: (0, 0, 1, 1, 0, 0, 0, 1, 1),
        5: (0, 0, 0, 0, 0, 1, 0, 1, 0),
        6: (1, 0, 0, 1, 1, 1, 0, 0, 1),
      }
      
      # rotate a piece
      rotate = lambda p: tuple(p[i] for i in (6, 3, 0, 7, 4, 1, 8, 5, 2))
      
      # return all 4 rotations
      rots = lambda p: repeat(rotate, p, 3)
      
      # placements for the corners
      corners = [(0, 0), (0, 2), (2, 0), (2, 2)]
      
      # place p at (x, y) in the grid
      # return a new grid or None
      def place(g, p, x, y):
        g_ = dict(g)
        for (j, v) in enumerate(p):
          (dx, dy) = divmod(j, 3)
          k = (x + dx, y + dy)
          v_ = g_.get(k)
          if v_ is None:
            g_[k] = v
          elif v_ != v:
            return None
        return g_
      
      # place pieces <ps> at locations <ls>
      def solve(ps, ls, g=dict()):
        # are we done?
        if not ps:
          yield g
        else:
          # place a piece in the next location
          (p, (x, y)) = (ps[0], ls[0])
          # consider possible rotations
          for p in rots(p):
            g_ = place(g, p, x, y)
            if g_ is not None:
              yield from solve(ps[1:], ls[1:], g_)
      
      # locate a piece from ps in grid g
      def locate(g, ps):
        for p in ps:
          for (x, y) in cproduct([(0, 1, 2), (0, 1, 2)]):
            if place(g, p, x, y):
              yield (x, y)
      
      # consider possible orderings of pieces
      for (p1, p2, p3, p4, p5, p6) in subsets(pieces.keys(), size=6, select="P"):
      
        # place the first 4 (in some orientation) in the corners
        for g in solve(list(pieces[i] for i in (p1, p2, p3, p4)), corners):
      
          # check the 5th piece fits in some orientation at some other location
          l5s = set(z for z in locate(g, rots(pieces[p5])) if z not in corners)
          if not l5s: continue
      
          # and the remaining piece has one square wrong but fits the right way up
          l6s = dict()
          for j in irange(0, 8):
            p = list(pieces[p6])
            p[j] ^= 1
            for z in locate(g, [p]):
              if z not in corners:
                l6s[z] = j
          if not l6s: continue
          if not any(a != b for (a, b) in cproduct([l5s, l6s.keys()])): continue
      
          # output solution
          printf("corners = [{p1}, {p2}, {p3}, {p4}]; other = {p5} @ {l5s}; wrong = {p6} @ {l6s}\n")
          for x in (0, 1, 2, 3, 4):
            printf("{row}", row=join((g[(x, y)] for y in (0, 1, 2, 3, 4)), sep=" ", enc="[]"))
          printf()
      

      Solution: The solution is as follows:

      There are 9 possible locations for a 3×3 sub-square of the 5×5 square. The 4 corners, the 4 edges, and the central sub-square.

      The corners consist of pieces 4, 5, 6, 1 (in suitable orientations), and piece 2 corresponds to the left edge sub-square. The central sub-square correspond to piece 3 (the right way up), except the cell marked “x” is the wrong colour.

      Note that each of the pieces 1 – 6 corresponds to a different 3×3 sub-square in the finished painting. If two pieces are allowed to correspond to the same sub-square, then this solution is not unique.

      The program produces 2 solutions corresponding to the same diagram. This is because piece 6 is the same when rotated through 180°.

      Like

    • Frits's avatar

      Frits 1:10 pm on 1 March 2024 Permalink | Reply

      When looking for similar puzzles to the 1994 IMO C1 question I stumbled upon this puzzle.

      @Jim, do you know a more elegant/compact alternative for diff_comb()?

      from enigma import SubstitutedExpression
      from itertools import product
      
      # the 6 pieces
      pieces = {
        1: (0, 1, 0, 0, 1, 0, 0, 1, 1),
        2: (1, 0, 1, 0, 0, 1, 0, 1, 1),
        3: (0, 1, 0, 1, 0, 0, 1, 1, 1),
        4: (0, 0, 1, 1, 0, 0, 0, 1, 1),
        5: (0, 0, 0, 0, 0, 1, 0, 1, 0),
        6: (1, 0, 0, 1, 1, 1, 0, 0, 1),
      }
      
      # rotate a piece clockwise
      rotate = lambda p: tuple(p[x] for x in [3 * i + j for j in range(2, -1, -1)
                                                        for i in range(3)])
      
      # return all 4 rotations
      def rotations(p):
        yield p
        for _ in range(3):
          p = rotate(p)
          yield p
      
      # dictionary of 3x3 box usage
      d_3x3 = dict()
      for k, v in pieces.items():
        for r in rotations(v):
          d_3x3[r] = d_3x3.get(r, set()) | {k}
      
      # placements for the corners
      corners = {(0, 0), (0, 2), (2, 0), (2, 2)}
      
      # get the four corner 3x3 boxes
      get_corners = lambda m: [topLeft_3x3(m, c) for c in corners]
      
      # check if a corner can be made by one of the pieces
      check_corner = lambda r: set() if r not in d_3x3 else d_3x3[r]
      
      # check if a combination with different values exists
      def diff_comb(s):
        for p in product(*s):
          if len(set(p)) == len(p):
            return True
        return False
      
      # return the 3x3 box values starting at the top left <tl>
      def topLeft_3x3(m, tl):
        (tlx, tly) = tl
        return tuple(m[tlx + x][tly + y]
                     for (x, y) in product((0, 1, 2), (0, 1, 2)))
      
      # perform checks for the two remaining pieces
      def check_oth(m, cs):
        # nine possible 3x3 boxes minus the four corner 3x3 boxes
        five3x3 = {topLeft_3x3(m, tl) for tl in product((0, 1, 2), (0, 1, 2))
                   if tl not in corners}
      
        # check all combinations of 4 corners
        for p in product(*cs):
          if len(set(p)) != len(p): continue
          # remaining two pieces
          rest = set(range(1, 7)).difference(p)
      
          found_rotated = []
          found_wrong = []
          for pc in rest:
            # check possible rotation (without the corners)
            for rot in [k for k, v in d_3x3.items() if pc in v]:
              if rot in five3x3:
                found_rotated.append((pc, rot))
      
            # check if a "right way up" piece has exactly 8 correct squares
            for a in five3x3:
              if sum([x == y for x, y in zip(a, pieces[pc])]) == 8:
               found_wrong.append((pc, a))
      
          # check options for 2 last pieces
          for (rn, r), (wn, w) in product(found_rotated, found_wrong):
            # different pieces
            if rn == wn: continue
            # both pieces must be at a different spot
            w_spots = {i for i, x in enumerate(five3x3) if x == w}
            r_spots = {i for i, x in enumerate(five3x3) if x == r}
            if len(w_spots | r_spots) < 2: continue
            return True
      
        return False
      
      A_Y = [chr(x) for x in range(ord("A"), ord("Y") + 1)]
      
      # 5x5 matrix of A-E, F-J, K-O, P-T, U-Y
      M = [[A_Y[5 * i + j] for j in range(5)] for i in range(5)]
      
      # all variable names in a 5x5 matrix layout (without quotes)
      mat = "((" + "), (".join(','.join(r) for r in M) + "))"
      
      answer = f"{mat}"
      
      exprs = []
      # check if every corner can be made from a piece
      for i, c in enumerate(get_corners(M), start=1):
        s = "(" + ', '.join(c) + ")"
        exprs.append(f"len(c{i} := check_corner({s})) > 0")
      
      # 4 corners use different pieces
      exprs.append("diff_comb([c1, c2, c3, c4])")
      
      # check remaining two pieces
      exprs.append(f"check_oth({mat}, [c1, c2, c3, c4])")
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        exprs,
        answer=answer,
        distinct="",
        env=dict(check_oth=check_oth,
                 check_corner=check_corner,
                 diff_comb=diff_comb),
        digits=range(0, 2),
        decl="global c1, c2, c3, c4",
        denest=2,
        reorder=0,    # necessary because of assignment statements
        verbose=0,    # use 256 to see the generated code
      )
      
      # print answers
      print("answer:")
      for ans in p.answers():
        for x in ans:
          print(f"{x}")
        print()     
      

      Like

      • Jim Randell's avatar

        Jim Randell 8:19 am on 12 June 2024 Permalink | Reply

        @Frits: You could use the [[ disjoint_cproduct() ]] function from Teaser 3220 to implement diff_comb.

        diff_comb(s)any(disjoint_cproduct(s))

        Like

        • Frits's avatar

          Frits 10:23 am on 12 June 2024 Permalink | Reply

          Thanks, I tested it. I had forgotten I had asked the question.

          Like

  • Unknown's avatar

    Jim Randell 11:03 am on 28 November 2021 Permalink | Reply
    Tags: by: Miss W. M. Jeffree   

    Brain Teaser: Noughts and crosses 

    From The Sunday Times, 23rd December 1956 [link]

    Each square is to be occupied by either a nought or a cross.

    The cross (or x) stands for one particular digit which you may discover for yourself.

    No light begins with nought.

    The following clues give the prime factors of the various lights, each letter representing a different prime:

    Across:
    (I) abcd
    (II) a²b²ef
    (III) ab²gh
    (IV) abij²k
    (V) a²bejl
    (VI) ab²ikm

    Down:
    (i) ab²ijkm
    (ii) a²beij²k
    (iii) ab²mnp
    (iv) a²bcde
    (v) abijkl

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. Prizes of £5, £3 and £1 were offered for the first 3 correct solutions.

    [teaser-1956-12-23] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 11:04 am on 28 November 2021 Permalink | Reply

      Once you get going I think it is easier to solve this puzzle by hand. But here is a fully programmed solution:

      The numbers (I) and (i) must be repdigits of the appropriate length with the appropriate prime factorisation patterns.

      This immediately gives us the required digit x, then we just have to fill out the remaining numbers, matching the factorisation patterns.

      This Python program runs in 49ms.

      Run: [ @replit ]

      from enigma import (irange, repdigit, singleton, intersect, subsets, nconcat, prime_factor, div, choose, diff, chain, multiply, nsplit, unzip, printf)
      
      # find factors of n, with multiplicities in ms
      def factor(n, ms={1, 2}):
        r = dict((k, list()) for k in ms)
        for (p, e) in prime_factor(n):
          if e not in r: return
          r[e].append(p)
        return r
      
      # consider values for the digit x
      for x in irange(1, 9):
      
        # (I) = xxxxx = a.b.c.d
        a1 = repdigit(5, x)
        fsa1 = factor(a1, {1})
        if fsa1 is None or not (len(fsa1[1]) == 4): continue
      
        # (i) = xxxxxx = a.b^2.i.j.k.m
        d1 = repdigit(6, x)
        fsd1 = factor(d1)
        if fsd1 is None or not (len(fsd1[2]) == 1 and len(fsd1[1]) == 5): continue
        b = singleton(fsd1[2])
        a = singleton(intersect([fsd1[1], fsa1[1]]))
      
        printf("x={x}; dn1={d1} ac1={a1}; a={a} b={b}")
      
        # calculate factorisations of x???? / (a.b)
        fs5 = dict()
        for ds in subsets((0, x), size=4, select="M", fn=list):
          ds.insert(0, x)
          n = nconcat(ds)
          n_ = div(n, a * b)
          if n_ is None: continue
          fs = factor(n_, {1, 2})
          if fs:
            fs5[n] = fs
      
        # look for values for (II), (III), (IV), (V)
        def fna6(a6):
          (a6, fs) = a6
          # there should be 4 single factors, one is b, none is a
          return (len(fs[2]) == 0 and len(fs[1]) == 4 and b in fs[1] and a not in fs[1])
      
        def fna5(a6, a5):
          (a5, fs) = a5
          # there should be 4 single factors, one is a, none is b
          return (len(fs[2]) == 0 and len(fs[1]) == 4 and a in fs[1] and b not in fs[1])
      
        def fna4(a6, a5, a4):
          (a4, fs) = a4
          # there should be 2 single factors and one double factor, [none is a or b]
          return (len(fs[2]) == 1 and len(fs[1]) == 2 and not intersect([[a, b], fs[1] + fs[2]]))
      
        def fna2(a6, a5, a4, a2):
          (a2, fs) = a2
          # there are no double factors, 4 single factors, one of them is a, one of them is b
          return (len(fs[2]) == 0 and len(fs[1]) == 4 and a in fs[1] and b in fs[1])
      
        def fna3(a6, a5, a4, a2, a3):
          (a3, fs) = a3
          # there no double factors, 3 single factors, one of them is b, none of them a
          return (len(fs[2]) == 0 and len(fs[1]) == 3 and b in fs[1] and a not in fs[1])
        
        for xs in choose(fs5.items(), [fna6, fna5, fna4, fna2, fna3], distinct=1):
          ((a6, fsa6), (a5, fsa5), (a4, fsa4), (a2, fsa2), (a3, fsa3)) = xs
          # determine factors
          j = singleton(fsa4[2])
          ik = fsa4[1]
          m = singleton(diff(fsa6[1], [b] + ik))
          e = singleton(diff(intersect([fsa2[1], fsa5[1]]), [a]))
          f = singleton(diff(fsa2[1], [a, b, e]))
          l = singleton(diff(fsa5[1], [a, e, j]))
          gh = diff(fsa3[1], [b])
          cd = diff(fsa1[1], [a, b])
      
          # check the down factorisations
          if not (d1 == multiply(chain([a, b, b, j, m], ik))): continue
          (_, d2, d3, d4, d5) = (nconcat(x) for x in unzip(nsplit(x) for x in (a1, a2, a3, a4, a5, a6)))
          if not (d2 == multiply(chain([a, a, b, e, j, j], ik))): continue
          if not (d4 == multiply(chain([a, a, b, e], cd))): continue
          if not (d5 == multiply(chain([a, b, j, l], ik))): continue
          np = div(d3, a * b * b * m)
          if np is None: continue
          np = factor(np, {1})
          if np is None or len(np[1]) != 2: continue
          np = np[1]
      
          # check all the numbers are different
          if len(chain([a, b, j, m, e, f, l], ik, gh, cd, np, fn=set)) != 15: continue
      
          printf("  (I)={a1} (II)={a2} (III)={a3} (IV)={a4} (V)={a5} (VI)={a6}")
          printf("  (i)={d1} (ii)={d2} (iii)={d3} (iv)={d4} (v)={d5}")
          printf("  -> j={j} ik={ik} m={m} e={e} f={f} l={l} gh={gh} cd={cd} np={np}")
          printf()
      

      Solution: The completed grid looks like this:

      We can determine the primes as:

      a = 2
      b = 3
      e = 5
      f = 367
      j = 11
      l = 101
      m = 37
      c × d = 41 × 271
      g × h = 47 × 71
      i × k = 7 × 13
      n × p = 17 × 53

      Note that although it may look like the prime factorisation may have been presented in numerical order, this is not the case for at least 2 of the clues.

      Like

      • Jim Randell's avatar

        Jim Randell 9:57 am on 29 November 2021 Permalink | Reply

        Here is my manual solution:

        (I) 11111 factorises as (41, 271), so that gives us two of a, b, c, d, and the remaining two come from the single digit x. So x = 6, and (a, b, c, d) = (2, 3, 41, 271) (in some order).

        (i) 666666 factorises as (2, 3², 7, 11, 13, 37), so b = 3, a = 2, and (i, j, k, m) = (7, 11, 13, 37), (c, d) = (41, 271).

        (iv) = (a², b, c, d, e) = 2 × (I) × e = 133332 × e, and consists of 6 digits chosen from (0, 6). The only possible prime value for e is 5, giving (iv) = 666660.

        (VI) = (a, b², i, k, m) = 18 × ikm. Choosing one of the values from (7, 11, 13, 37) for j we get:

        j = 7 ⇒ (VI) = 95238
        j = 11 ⇒ (VI) = 60606
        j = 13 ⇒ (VI) = 51282
        j = 37 ⇒ (VI) = 18018

        So j = 11, and (VI) = 60606.

        (IV) = (a, b, i, j², k) = 726 × ik. Choosing one of the values from (7, 13, 37) for m we get:

        m = 7 ⇒ (IV) = 349206
        m = 13 ⇒ (IV) = 188034
        m = 37 ⇒ (IV) = 66066

        So m = 37, ik = 7 × 13, and (IV) = 66066.

        (ii) = (a², b, e, i, j², k) = 660660.

        (II) = (a², b², e, f) = 180 × f, which matches 66?6?. The possible numbers are:

        (II) = 66660 ⇒ f = 370.3…
        (II) = 66060 ⇒ f = 367, prime!

        So, f = 367, and (II) = 66060.

        (V) = (a², b, e, j, l) = 660 × l, and matches 66?6?. The possibilities are:

        (V) = 66660 ⇒ l = 101
        (V) = 66066 ⇒ l = 100.1

        So l = 101 and (V) = 66660.

        (v) = (a, b, i, j, k, l) = 606606.

        (III) = (a, b², g, h) = 18 × gh, and matches 60?66. The possibilities are:

        (III) = 60666 ⇒ gh = 3370.3…
        (III) = 60066 ⇒ gh = 3337 = 47 × 71

        So (III) = 60066, and the grid is complete.


        The solution was published in the 6th January 1957 issue of The Sunday Times along with the following note:

        Some readers complained that this brain-teaser was too easy. Over eight hundred correct solutions were in fact received, but the number was in line with those recorded for earlier mathematical puzzles which appeared, at least, to be more diffcult.

        Like

    • Frits's avatar

      Frits 1:32 pm on 28 November 2021 Permalink | Reply

        
      from enigma import SubstitutedExpression, prime_factor, div, gcd
        
      # find multiplicities of factors of n
      mfac = lambda n: [[e for (p, e) in prime_factor(n)].count(i) for i in [1, 2]]
        
      # Z is cross digit  (1 - 9)
      #
      # A B C D E
      # F G H I J
      # K L M N O
      # P Q R S T 
      # U V W X Y
      # p q s t u
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # (I)   abcd
        "mfac(ABCDE * Z) == [4, 0]",
        # (II)  a²b²ef
        "mfac(FGHIJ * Z) == [2, 2]",
        # (III) ab²gh
        "mfac(KLMNO * Z) == [3, 1]",
        # (IV)  abij²k 
        "mfac(PQRST * Z) == [4, 1]",
        # (V)   a²bejl
        "mfac(UVWXY * Z) == [4, 1]",
        # (VI)  ab²ikm
        "mfac(pqstu * Z) == [4, 1]",
        
        # Down:
        # (i)   ab²ijkm
        "mfac(AFKPUp * Z) == [5, 1]",
        # (ii)  a²beij²k
        "mfac(BGLQVq * Z) == [4, 2]",
        # (iii) ab²mnp
        "mfac(CHMRWs * Z) == [4, 1]",
        # (iv)  a²bcde
        "mfac(DINSXt * Z) == [4, 1]",
        # (v)   abijkl
        "mfac(EJOTYu * Z) == [6, 0]",
        ],
        answer="([x * Z for x in [ABCDE, FGHIJ, KLMNO, PQRST, UVWXY, pqstu]]), \
                ([x * Z for x in [AFKPUp, BGLQVq, CHMRWs, DINSXt, EJOTYu]])",
        symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZpqstu",
        d2i=dict([(0, "ZABCDEFKPUp")] + 
                 [(k, "ABCDEFGHIJKLMNOPQRSTUVWXYpqstu") for k in range(2, 10)]),
        distinct="",
        env=dict(mfac=mfac),
        verbose=0,
      )
      
      # print answers
      for (_, ans) in p.solve():
        
        j = div(ans[1][0], ans[0][-1])            # (VI) and (i)
        if not j: continue
        
        l = div(j * ans[1][-1], ans[0][3])        # (IV) and (v)
        if not l: continue
        
        cd = div(j * l * ans[1][3], ans[0][4])    # (V) and (iv)
        if not cd: continue
        
        ab = div(ans[0][0], cd)                   # (I)
        if not ab: continue
        
        ik = div(ans[1][-1], j * l * ab)          # (v)
        if not ik: continue
        
        ef = div(ans[0][1], ab**2)                # (II) 
        if not ef: continue
       
        a2be = div(ans[0][4], j * l)              # (V) 
        if not a2be: continue
        
        bf = div(ans[0][1], a2be)                 # (II) 
        if not bf: continue
       
        f = gcd(ef, bf)
        e = ef // f
        b = bf // f
        a = ab // b
        m = div(ans[0][-1], a * b**2 * ik)        # (VI) 
        if not m: continue
        
        gh = div(ans[0][2], a * b**2)             # (III) 
        np = div(ans[1][2], a * b**2 * m)         # (iii) 
        print(" a b e  f   j  l   m  cxd  gxh ixk nxp\n", 
              a, b, e, f, j, l, m, cd, gh, ik, np, "\n")
        
        for a in ans[0]:
          print(a)
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:41 pm on 28 November 2021 Permalink | Reply

        A neat use of the [[ SubstitutedExpression() ]] solver.

        But do you think [[ mfac() ]] is strong enough? A number like 9240 would give an answer of [4, 0], but not match the pattern a⋅b⋅c⋅d.

        Although checking the numbers have the right number of single and double prime factors turns out to be enough to narrow down the solution space to a single candidate, even without matching up the primes in the patterns.

        Like

    • Frits's avatar

      Frits 9:37 pm on 28 November 2021 Permalink | Reply

      With a better “find multiplicities” function (thanks Jim).
      Most of the work is now done by [[ SubstitutedExpression() ]].

        
      from enigma import SubstitutedExpression, prime_factor, div, gcd as z
        
      # find multiplicities of factors of n
      def v(n, allowed):
        lst = [e for (p, e) in prime_factor(n)]
        # only consider factors with multiplicity 1 or 2
        if any(x > 2 for x in lst): return False
          
        return [lst.count(i) for i in [1, 2]] == allowed
      
      # multiply all items by n  
      def w(lst, n):
        return [x * n for x in lst]
        
      # Z is cross digit  (1 - 9)
      #
      # A B C D E
      # F G H I J
      # K L M N O
      # P Q R S T 
      # U V W X Y
      # p q s t u
      
      answer =  "(w([ABCDE, FGHIJ, KLMNO, PQRST, UVWXY, pqstu], Z)),"
      # a = ab / b 
      answer += "(div(ab * UVWXY * feh, FGHIJ * jl * lcg),"
      # b = bf / f 
      answer += "div(FGHIJ * jl * lcg, UVWXY * feh),"
      # e = ef / f  
      answer += "div(FGHIJ * Z, ab**2 * feh), " 
      answer += "feh, "                                                 # f
      answer += "jk, "                                                  # j
      answer += "lcg, "                                                 # l
      # m = ab²ikm / ab²ik)        
      answer += "div(pqstu * (UVWXY * feh), FGHIJ * EJOTYu),"
      answer += "div(ABCDE * Z, ab),"                                   # cd
      answer += "div(KLMNO * Z * UVWXY * feh, ab * FGHIJ * jl * lcg),"  # gh
      answer += "div(EJOTYu * Z, ab * jl * lcg),"                       # ik
      answer += "div(CHMRWs * Z * EJOTYu, ab * jl * lcg * pqstu)),"     # np
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # Up: 
        "v(ABCDE * Z, [4, 0])",   # (I)   abcd
        "v(FGHIJ * Z, [2, 2])",   # (II)  a²b²ef
        "v(KLMNO * Z, [3, 1])",   # (III) ab²gh
        "v(PQRST * Z, [4, 1])",   # (IV)  abij²k 
        "v(UVWXY * Z, [4, 1])",   # (V)   a²bejl
        "v(pqstu * Z, [4, 1])",   # (VI)  ab²ikm
        
        # Down:
        "v(AFKPUp * Z, [5, 1])",  # (i)   ab²ijkm
        "v(BGLQVq * Z, [4, 2])",  # (ii)  a²beij²k
        "v(CHMRWs * Z, [4, 1])",  # (iii) ab²mnp
        "v(DINSXt * Z, [4, 1])",  # (iv)  a²bcde
        "v(EJOTYu * Z, [6, 0])",  # (v)   abijkl
        
        # j = ab²ijkm / ab²ikm
        "div(AFKPUp, pqstu) = jk",           
        # l = j * abijkl / abij²k 
        "div(jk * EJOTYu, PQRST) = lcg",     
        # ab = abcd / (j*l*a²bcde / a²bejl) = (I) * (V) / (j * l * (iv) )
        "div(Z * ABCDE * UVWXY, jk * lcg * DINSXt) = ab",      
        # f = gcd(ef, bf)
        "z(div(FGHIJ * Z, ab**2), div(FGHIJ * jl * lcg, UVWXY)) = feh",  
        ],
        answer=answer,
        symbols="ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghjklpqstu",
        d2i=dict([(0, "ZABCDEFKPUp")] + 
                 [(k, "ABCDEFGHIJKLMNOPQRSTUVWXYpqstu") for k in range(2, 10)]),
        distinct="",
        env=dict(v=v,w=w,z=z),
        verbose=0,
      )
      
      # check and print answers
      for (_, ans) in p.solve():
        
        # check if all items are numbers
        if any(x is None for x in ans[1]): continue
        # check last 4 numbers on being a product of 2 primes
        if any(not v(x, [2, 0]) for x in ans[1][-4:]) : continue
        
        print("a b e  f   j  l   m  cxd  gxh ixk nxp") 
        print(*ans[1])
        
        for a in ans[0]:
          print(a)
      

      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