Tagged: by: E. R. J. Benson Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:29 am on 3 March 2026 Permalink | Reply
    Tags: by: E. R. J. Benson   

    Brain teaser 1006: Seafood dinner 

    From The Sunday Times, 8th November 1981 [link]

    The exclusive Ichthyophage Club was invited to hold its annual dinner at a resort on the Costa Fortuna last summer. Several members accepted the invitation, and each was asked to bring one of the local seafood specialities as their contribution to the feast.

    The fare consisted of:

    • the local edible starfish, which has five legs,
    • the squid, which has ten,
    • and octopus.

    Each guest provided one such creature, and in fact more people provided octopus than provided starfish.

    A chef was hired locally. He arranged the food so that each guest received a plateful consisting of the same number of legs — at least one leg from each species — but no guest’s plate was made up in the same way as any other guest’s plate. In other words, no guest had the same combination of legs on his plate as any other guest did.

    When the food had been arranged in this way, the chef was grieved to find that all the legs had been used, leaving no edible fragments for him to take home to his family.

    How many guests were there?

    How many brought starfish, how many brought squid, and how many brought octopus?

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

    [teaser1006]

     
    • Jim Randell's avatar

      Jim Randell 9:29 am on 3 March 2026 Permalink | Reply

      Suppose there were n guests, and x of them brought a starfish, y of them brought a squid, z of them brought an octopus (each with their full complement of legs).

      And they are each served a different arrangement of k legs, then:

      n = x + y + z
      k = (5x + 10y + 8z) / n

      This Python program considers increasing numbers of guests, and looks for viable decompositions that allow a different arrangement of legs to be served to each of them.

      It runs in 74ms. (Internal runtime is 334µs).

      from enigma import (enigma, Enumerator, irange, inf, div, subsets, unzip, printf)
      
      # set defaults for decompose
      decompose = enigma.partial(enigma.decompose, increasing=0, sep=0, min_v=1)
      
      # solve the puzzle for <n> guests
      def solve(n):
        # each guest brings one of:
        # starfish (5 legs) = x; squid (10 legs) = y; octopus (8 legs) = z
        for (x, y, z) in decompose(n, 3):
          # "more people provided octopus than starfish"
          if not (z > x): continue
      
          # calculate the number of legs per guest
          ts = (5*x, 10*y, 8*z)
          k = div(sum(ts), n)
          if k is None: continue
      
          # look for a set of n decompositions
          for ss in subsets(decompose(k, 3), size=n):
            # that give the correct number of leg types
            ns = tuple(sum(vs) for vs in unzip(ss))
            if not (ns == ts): continue
            # return solution
            yield ((x, y, z), k, ss)
            break  # one decomposition is enough
      
      # consider the number of guests
      for n in irange(3, inf):
        # look for a viable scenario with <n> guests
        sols = Enumerator(solve(n))
        for ((x, y, z), k, ss) in sols:
          printf("{n} guests ({x} starfish, {y} squid, {z} octopus) -> {k} legs per plate")
          printf("-> plates = {ss}")
        # are we done?
        if sols.count > 0: break
      

      Solution: There were 8 guests. 2 brought starfish, 3 brought squid, 3 brought octopus.

      The total number of legs available was: 2×5 + 3×10 + 3×8 = 64 legs.

      And so each of the guests was served 8 legs.

      The distinct served plates were (starfish legs, squid legs, octopus legs):

      (1, 1, 6)
      (1, 2, 5)
      (1, 3, 4)
      (1, 4, 3)
      (1, 5, 2)
      (1, 6, 1)
      (2, 4, 2)
      (2, 5, 1)
      total = (10, 30, 24)

      Although the program only looks for one way to make up the plates, this is in fact the only way for n = 8.

      Like

  • Unknown's avatar

    Jim Randell 6:28 pm on 14 July 2024 Permalink | Reply
    Tags: by: E. R. J. Benson   

    Brainteaser 1081: Trifling troubles 

    From The Sunday Times, 24th April 1983 [link]

    Fred and I usually do the washing and drying up in our commune. Fred always washes the largest saucepan first, then the second largest, and so on down to the smallest. In this way I can dry them and store them away in the order they were washed, by putting one saucepan inside the previously washed saucepan, ending up with a single pile.

    Yesterday, the cook misread the quantities for the sherry trifle recipe, with the result that Fred got the washing up order completely wrong. For example, he washed the second smallest saucepan immediately after the second largest; and the smallest saucepan immediately before the middle-sized saucepan (i.e. the saucepan with as many saucepans larger than it as there are smaller).

    I realised something was wrong when, having started to put the saucepans away in the usual manner, one of the saucepans did not fit the previously washed saucepan; so I started a second pile. Thereafter each saucepan either fitted in to one, but only one pile, or did not fit into any pile, in which case I started another pile.

    We ended up with a number of piles, each containing more than one saucepan. The first pile to be completed contained more saucepans than the second, which contained more than the third etc.

    In what order were the saucepans washed up? (Answers in the form a, b, c, …, numbering the saucepans from 1 upwards, 1 being the smallest).

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

    [teaser1081]

     
    • Jim Randell's avatar

      Jim Randell 6:29 pm on 14 July 2024 Permalink | Reply

      There must be an odd number of pans (in order for there to be a middle sized one), and there must be at more than 3 pans (so that the second largest is different from the second smallest).

      This Python program considers possible orderings of pans that fit the conditions, and then attempts to organise them into piles that meet the requirements.

      It runs in 169ms. (Internal runtime is 96ms).

      from enigma import (irange, inf, subsets, is_decreasing, printf)
      
      # organise the sequence of pans into piles
      def organise(ss):
        (ps, i) = ([], 0)
        for n in ss:
          # find placement for n
          js = list(j for (j, p) in enumerate(ps) if n < p[-1])
          if len(js) > 1: return None
          if len(js) == 1:
            ps[js[0]].append(n)
          else:
            ps.append([n])
        return ps
      
      # solve for n pans
      def solve(n):
        assert n % 2 == 1
        # allocate numbers to the pans
        ns = list(irange(1, n))
        k = 0
        # choose an ordering for the pans
        for ss in subsets(ns, size=len, select='P'):
          # check ordering conditions:
          # the 2nd smallest (2) is washed immediately after the 2nd largest (n - 1)
          # the smallest (1) is washed immediately before the middle ((n + 1) // 2)
          if not (ss.index(2) == ss.index(n - 1) + 1): continue
          if not (ss.index(1) + 1 == ss.index((n + 1) // 2)): continue
          # organise the pans into piles
          ps = organise(ss)
          if ps and len(ps) > 1 and is_decreasing([len(p) for p in ps] + [1], strict=1):
            # output solution
            printf("{n} pans; {ss} -> {ps}")
            k += 1
        return k
      
      # solve for an odd number of pans > 3
      for n in irange(5, inf, step=2):
        k = solve(n)
        if k:
          printf("[{n} pans -> {k} solutions]")
          break
      

      Solution: The order of the pans was: 9, 8, 2, 1, 5, 4, 3, 7, 6.

      Which gives 3 piles:

      [9, 8, 2, 1]
      [5, 4, 3]
      [7, 6]

      To explore larger number of pans it would probably be better to use a custom routine that generates orders with the necessary conditions, rather than just generate all orders and reject those that are not appropriate.

      Like

      • Ruud's avatar

        Ruud 5:52 pm on 15 July 2024 Permalink | Reply

        I am working on a fast recursive algorithm and find four solutions for n=9
        9 7 6 1 , 5 4 3 , 8 2
        9 7 6 3 , 8 2 1 , 5 4
        9 7 6 4 , 8 2 1 , 5 3
        9 8 2 1 , 5 4 3 , 7 6
        The last one matches yours. But aren’t the others valid? Did I miss a condition?

        Like

        • Jim Randell's avatar

          Jim Randell 6:22 pm on 15 July 2024 Permalink | Reply

          @Ruud: In your first three sequences when you get the 2 pan you could put it into either of two available piles, so they are not viable sequences.

          Like

      • Ruud's avatar

        Ruud 6:54 am on 16 July 2024 Permalink | Reply

        I have solved this with a recursive algorithm, which is significantly faster than Jim’s but still very slow when the number of pans becomes >= 15.
        Here’s the code:

        from itertools import count
        
        def wash(remaining_pans, last_pan, piles, washed_pans):
            if not remaining_pans:
                if all(len(piles1) > len(piles2) > 1 for piles1, piles2 in zip(piles, piles[1:])):
                    print(piles, washed_pans)
                return
            if len(piles) > max_number_of_piles:
                return
        
            for pan in remaining_pans:
                if pan != 2 and last_pan == (n - 1):
                    continue
                if pan != (n + 1) // 2 and last_pan == 1:
                    continue
                if pan == 2 and last_pan != (n - 1):
                    continue
                if pan == (n + 1) // 2 and last_pan != 1:
                    continue
                selected_pile = None
                for pile in piles:
                    if pile[-1] > pan:
                        if selected_pile is None:
                            selected_pile = pile
                        else:
                            selected_pile = None
                            break
                if selected_pile is None:
                    wash(
                        last_pan=pan,
                        remaining_pans=[ipan for ipan in remaining_pans if ipan != pan],
                        piles=[pile[:] for pile in piles] + [[pan]],
                        washed_pans=washed_pans + [pan],
                    )
                else:
                    wash(
                        last_pan=pan,
                        remaining_pans=[ipan for ipan in remaining_pans if ipan != pan],
                        piles=[pile + [pan] if pile == selected_pile else pile[:] for pile in piles],
                        washed_pans=washed_pans + [pan],
                    )
        
        
        for n in count(5,2):
            print(f'number of pans = {n}')
            max_number_of_piles=0
            cum_pile_height=0
            for pile_height in count(2):
                cum_pile_height+=pile_height
                max_number_of_piles+=1
                if cum_pile_height>=n: 
                    break
            wash(last_pan=0, remaining_pans=range(1, n + 1), piles=[], washed_pans=[])
        

        Like

        • Frits's avatar

          Frits 4:52 pm on 18 July 2024 Permalink | Reply

          @Ruud,

          “cum_pile_height” probably must be initialized to 1 to get the correct pile_height for n = 15.

          With some more checks (like “if len(piles) > 1 and len(piles[-1]) >= len(piles[-2]): return”) your program runs faster:

           
          pypy TriflingTroubles1.py
          2024-07-18 17:47:39.184252 number of pans = 5
          2024-07-18 17:47:39.186248 n = 5 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:39.188241 n = 5 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:39.191231 number of pans = 7
          2024-07-18 17:47:39.191231 n = 7 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:39.192234 n = 7 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:39.192234 n = 7 pile_height = 4 max_number_of_piles = 3 cum_pile_height = 10
          2024-07-18 17:47:39.194224 number of pans = 9
          2024-07-18 17:47:39.194224 n = 9 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:39.194224 n = 9 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:39.195221 n = 9 pile_height = 4 max_number_of_piles = 3 cum_pile_height = 10
          [[9, 8, 2, 1], [5, 4, 3], [7, 6]] [9, 8, 2, 1, 5, 4, 3, 7, 6]
          2024-07-18 17:47:39.202204 number of pans = 11
          2024-07-18 17:47:39.204206 n = 11 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:39.205194 n = 11 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:39.206192 n = 11 pile_height = 4 max_number_of_piles = 3 cum_pile_height = 10
          2024-07-18 17:47:39.206192 n = 11 pile_height = 5 max_number_of_piles = 4 cum_pile_height = 15
          2024-07-18 17:47:39.267028 number of pans = 13
          2024-07-18 17:47:39.267460 n = 13 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:39.268462 n = 13 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:39.271365 n = 13 pile_height = 4 max_number_of_piles = 3 cum_pile_height = 10
          2024-07-18 17:47:39.271365 n = 13 pile_height = 5 max_number_of_piles = 4 cum_pile_height = 15
          2024-07-18 17:47:39.467843 number of pans = 15
          2024-07-18 17:47:39.468983 n = 15 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:39.469985 n = 15 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:39.472540 n = 15 pile_height = 4 max_number_of_piles = 3 cum_pile_height = 10
          2024-07-18 17:47:39.472540 n = 15 pile_height = 5 max_number_of_piles = 4 cum_pile_height = 15
          2024-07-18 17:47:40.190655 number of pans = 17
          2024-07-18 17:47:40.191653 n = 17 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:40.192626 n = 17 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:40.195803 n = 17 pile_height = 4 max_number_of_piles = 3 cum_pile_height = 10
          2024-07-18 17:47:40.195803 n = 17 pile_height = 5 max_number_of_piles = 4 cum_pile_height = 15
          2024-07-18 17:47:40.196803 n = 17 pile_height = 6 max_number_of_piles = 5 cum_pile_height = 21
          2024-07-18 17:47:51.650183 number of pans = 19
          2024-07-18 17:47:51.651412 n = 19 pile_height = 2 max_number_of_piles = 1 cum_pile_height = 3
          2024-07-18 17:47:51.652742 n = 19 pile_height = 3 max_number_of_piles = 2 cum_pile_height = 6
          2024-07-18 17:47:51.655439 n = 19 pile_height = 4 max_number_of_piles = 3 cum_pile_height = 10
          2024-07-18 17:47:51.656182 n = 19 pile_height = 5 max_number_of_piles = 4 cum_pile_height = 15
          2024-07-18 17:47:51.656182 n = 19 pile_height = 6 max_number_of_piles = 5 cum_pile_height = 21 
          

          Like

          • Frits's avatar

            Frits 4:58 pm on 18 July 2024 Permalink | Reply

            @Ruud, Sorry, my first statement is incorrect (I forgot that each pile has to have more than one saucepan)

            Like

    • Frits's avatar

      Frits 4:43 pm on 18 July 2024 Permalink | Reply

      Runs within a second for up to 21 saucepans.

      from itertools import combinations
      from math import ceil
      
      # triangular root
      trirt = lambda x: 0.5 * ((8 * x + 1)**.5 - 1.0)
      
      # check validity of sequence <s>
      def check(n, p, s):
        if not s: return False
        # check second smallest and second largest
        if (n28 := (2 in s) + ((n - 1) in s)) == 0: return True
        if n28 == 1: return False
        return not((p2 := s.index(2)) <= 0 or s[p2 - 1] != n - 1)
        
      def solve(n, h, ns, p, m, ss=[]):
        if not ns:
          yield ss
          return # prevent indentation
       
        # determine which numbers we have to select (besides smallest number)
        base = [x for x in ns[:-1] if x != h] if p == 1 else ns[:-1]
        if p == 2: # second pile starts with <h>
          base = [x for x in base if x < h]
      
        mn = max(0, ceil(trirt(len(ns))) - 1 - (p == 2))
        mx = min(len(base), n if not ss else len(ss[-1]) - 1)  
        for i in range(mn, mx + 1):
          for c in combinations(base, i):
            c += (ns[-1], )              # suffix lowest remaining number
            if p == 2 and h not in c: 
              c = (h, ) + c              # second pile starts with <h>
            if (m_ := m - len(c)) == 1:  # each containing more than one saucepan
              continue
            if check(n, p, c):           # check second smallest and second largest
              yield from solve(n, h, [x for x in ns if x not in c], p + 1, m_,
                               ss + [c])
            
      M = 21                             # maximum number of saucepans
      for n in range(5, M + 1, 2):
        print(f"number of saucepans: {n}")
        for c in solve(n, (n + 1) // 2, range(n, 0, -1), 1, n):
          print("-----------------", c)
      

      Like

      • Ruud's avatar

        Ruud 5:49 am on 19 July 2024 Permalink | Reply

        @Frits
        Can you explain this code/algortithm. The one letter varibiables don’t really help …

        Like

      • Frits's avatar

        Frits 2:37 pm on 19 July 2024 Permalink | Reply

        @Jim, please replace my code with this version with more comments and using header:

        I noticed that each pile can be regarded as the result of a combinations() call with descending saucepan numbers.

        from itertools import combinations
        from math import ceil
        
        # triangular root
        trirt = lambda x: 0.5 * ((8 * x + 1)**.5 - 1.0)
        
        # check validity of sequence <s> (2nd smallest 2nd largest logic)
        def check(n, s):
          if not s: return False
          # check second smallest and second largest
          if (totSecSmallSecLarge := (2 in s) + ((n - 1) in s)) == 0: return True
          if totSecSmallSecLarge == 1: return False        # we need none or both
          # the 2nd smallest must be washed immediately after the 2nd largest 
          return not((p2 := s.index(2)) == 0 or s[p2 - 1] != n - 1)
        
        # add a new pile of saucepans
        # <n> = original nr of saucepans (middle one <h>), <ns> = saucepan nrs left,
        # <p> = pile nr, <r> = nr of saucepans left (rest), <ss> = piles
        def solve(n, h, ns, p, r, ss=[]):
          if not ns: # no more saucepans left
            yield ss
            return # prevent indentation
         
          # determine which numbers we have to use below in combinations() 
          # (besides smallest number)
          base = [x for x in ns[:-1] if x != h] if p == 1 else ns[:-1]
          if p == 2: # second pile starts with <h>
            base = [x for x in base if x < h] # only saucepan nrs below <h>
        
          # if triangular sum 1 + 2 + ... + y reaches nr of saucepans left <r> then we
          # need at least y + 1 saucepans (if r = tri(y) - 1 we only need y saucepans)
          mn = max(0, int(trirt(r)) - (p == 2))    
          # use less saucepans than in previous pile
          mx = min(len(base), n if not ss else len(ss[-1]) - 1)  
          
          # loop over number of saucepans to use for the next/this pile
          for i in range(mn, mx + 1):      # nr of saucepans needed besides smallest
            # pick <i> descending saucepan numbers for next/this pile
            for c in combinations(base, i):
              c += (ns[-1], )              # suffix lowest remaining saucepan number
              if p == 2 and h not in c:    # second pile starts with <h>
                c = (h, ) + c              
              if (r_ := r - len(c)) == 1:  # each containing more than one saucepan
                continue
              if check(n, c):              # check second smallest and second largest
                yield from solve(n, h, [x for x in ns if x not in c], p + 1, r_,
                                 ss + [c])
              
        M = 21                             # maximum number of saucepans (adjustable)
        for n in range(5, M + 1, 2):       
          print(f"number of saucepans: {n}")
          # use a descending sequence of numbers n...1 so that combinations() will
          # also return a descending sequence of saucepan numbers
          for c in solve(n, (n + 1) // 2, range(n, 0, -1), 1, n):
            print("-----------------", c)
        

        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