Tagged: by: J H Perryman Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 11:17 am on 31 December 2023 Permalink | Reply
    Tags: by: J H Perryman   

    Brain teaser 988: Pythagoras was never like this 

    From The Sunday Times, 28th June 1981 [link]

    In accordance with tradition the retiring President of the All Square Club set members a special competition. Titled as above, it required new meanings for the signs “+” and  “=” as in:

    6² + 1² = 19²

    indicating that both sides could be taken to represent 361.

    The number 361 was then called a “Special Pythogorean Square” (SPS) and the numbers 36 and 1 its “contributing squares”.

    Other examples given were:

    7² + 27² = 223²
    15² + 25² = 475²

    giving 49729 and 225625 as Special Pythogorean Squares.

    Members were invited to submit series of Special Pythogorean Squares which they could claim to be unique in some way. Each number in their series had to be less than a million, and no two numbers in their series could have the same number of digits.

    After much deliberation the prize was awarded to the member who submitted a series of Special Pythogorean Squares which he correctly claimed was the longest possible list of such numbers all with the same second contributing square.

    What (in increasing order) were the numbers in the winning series?

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

    [teaser988]

     
    • Jim Randell's avatar

      Jim Randell 11:17 am on 31 December 2023 Permalink | Reply

      So we treat “+” and “=” as string operations, being concatenation and equality respectively.

      This Python program generates all SPSs below 1 million, and then constructs maximal length sequences which share the same suffix square.

      It runs in 61ms. (Internal runtime is 2ms).

      Run: [ @replit ]

      from enigma import (
        defaultdict, Accumulator, irange, inf, sq, cproduct, join, printf
      )
      
      # generate SPSs
      def generate(N):
        # record squares (as strings)
        d = dict()
        for i in irange(0, inf):
          n = sq(i)
          if not (n < N): break
          s = str(n)
          d[s] = i
      
          # split the square into prefix and suffix
          for j in irange(1, len(s) - 1):
            (pre, suf) = (s[:j], s[j:])
            (pi, si) = (d.get(pre), d.get(suf))
            if pi is None or si is None: continue
            printf("[{n} = {pre} + {suf} -> {i}^2 = {pi}^2 + {si}^2]")
            yield (s, pre, suf)
      
      # collect SPSs: <suffix> -> <length> -> [<SPS>...]
      d = defaultdict(lambda: defaultdict(list))
      for (s, pre, suf) in generate(1000000):
        d[suf][len(s)].append(s)
      
      # find maximal length sequences sharing the same suffix
      r = Accumulator(fn=max, collect=1)
      for (suff, v) in d.items():
        r.accumulate_data(len(v.keys()), suff)
      printf("maximal sequence length = {r.value}")
      
      # output maximal length sequences
      for k in r.data:
        printf("suffix = {k}")
        for ss in cproduct(d[k].values()):
          printf("-> {ss}", ss=join(ss, sep=", ", enc="()"))
      

      Solution: The winning series was: 49, 169, 3249, 64009, 237169.

      Which are constructed as follows:

      49 = 4 + 9 → 7^2 = 2^2 + 3^2
      169 = 16 + 9 → 13^2 = 4^2 + 3^2
      3249 = 324 + 9 → 57^2 = 18^2 + 3^2
      64009 = 6400 + 9 → 253^2 = 80^2 + 3^2
      237169 = 23716 + 9 → 487^2 = 154^2 + 3^2

      Each has a suffix square of 9.

      Like

      • Frits's avatar

        Frits 4:55 pm on 3 January 2024 Permalink | Reply

        Slower.

            
        from enigma import SubstitutedExpression
        
        # the alphametic puzzle
        p = SubstitutedExpression(
          [
            # ABC^2 concatenated with DEF^2 = square
            "ABC > 0",
            # RHS must be less than a million
            "(d2 := DEF**2) < 10 ** (6 - len(str(a2 := ABC**2)))",
            "is_square(int(str(a2) +  str(d2)))",
          ],
          answer="ABC, DEF",
          d2i="",
          distinct="",
          reorder=0,
          verbose=0,    # use 256 to see the generated code
        )
        
        # store answers in dictionary (key = suffix)
        d = dict()
        for a, b in p.answers():
          d[b] = d.get(b, []) + [a]
        
        # find maximal length sequences sharing the same suffix
        m = len(max(d.values(), key=lambda k: len(k)))
        
        # assume more than one answer is possible
        print("answer:", ' or '.join(str(x) for x in
             [sorted(int(str(v**2) + str(k**2)) for v in vs)
                     for k, vs in d.items() if len(vs) == m]))
        

        Like

        • Jim Randell's avatar

          Jim Randell 10:25 pm on 4 January 2024 Permalink | Reply

          @Frits: Would this work if there were multiple SPSs with the same length and suffix?(e.g. if 99856+9 were a valid SPS).

          Like

          • Frits's avatar

            Frits 6:39 pm on 5 January 2024 Permalink | Reply

            @Jim, you are right. This wouldn’t work as I forgot the condition “no two numbers in their series could have the same number of digits”.

            Like

  • Unknown's avatar

    Jim Randell 10:20 am on 5 March 2023 Permalink | Reply
    Tags: by: J H Perryman   

    Brainteaser 1071: Particular palindromes 

    From The Sunday Times, 13th February 1983 [link]

    When either of a [particular] pair of numbers which differ by twenty-two is added to its own square, the result is a numerical palindrome between ten million and a hundred million.

    One way of finding the pair could be, of course, by extensive trial-and-error, working through the six thousand or so possibilities that lie in the given range, and electronic assistance would surely be appreciated in these circumstances.

    It should come as some relief, therefore, to puzzlers not so equipped to know that an answer can be found by performing less than twenty simple calculations (in which a calculator may nevertheless be usefully employed), none involving the square of a four-digit number, and that a further similar number of such calculations can show that no other answer is possible.

    What is the pair?

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

    [teaser1071]

     
    • Jim Randell's avatar

      Jim Randell 10:20 am on 5 March 2023 Permalink | Reply

      A brute force approach considers 6838 values, which is what the following Python program does.

      It runs in 61ms. (Internal runtime is 4.7ms).

      Run: [ @replit ]

      from enigma import (irange, inf, is_npalindrome, printf)
      
      # function to apply
      f = lambda x: x * (x + 1)
      
      # consider possible numbers
      vs = dict()
      for n in irange(0, inf):
        v = f(n)
        if v < 10000000: continue
        if v > 100000000: break
        # is the result a palindrome?
        if is_npalindrome(v):
          # have we recorded (n - 22)?
          v2 = vs.get(n - 22)
          if v2 is not None:
            # output solution
            printf("f({n}) = {v}; f({n2}) = {v2}", n2=n - 22)
          # record this value
          vs[n] = v
      

      Solution: The numbers are 5291, 5313.

      We have:

      5313 − 5291 = 22

      f(5291) = 27999972
      f(5313) = 28233282

      In fact these are the only two 8-digit palindromes that are the product of 2 consecutive integers.

      Like

      • Jim Randell's avatar

        Jim Randell 10:45 am on 5 March 2023 Permalink | Reply

        This puzzle was included in the book Microteasers (1986) by Victor Bryant.

        Here is the BBC BASIC program given in the text. It looks for palindromic 8-digit numbers that are the product of two consecutive integers, and prints them out. So it doesn’t look for a pair of numbers that differ by 22.

        >LIST
            5 REM Victor Bryant program for Teaser 1071
            6 REM from "Microteasers" (1986)
           10 REM We want N + N^2 to be an 8-digit palindrome
           15 start% = TIME
           20 N = 3000
           30 REPEAT
           40   N = N + 1
           50   M = N + N*N
           60   L = LEN(STR$(M))
           70   IF L <> 8 THEN 150
           80   REM We now work out the Dth digit from the right and the Dth digit from the left and see if they are the same
           90   D = 0
           91   REPEAT
           92     D = D + 1
          100     left$ = MID$(STR$(M), D, 1)
          110     right$ = MID$(STR$(M), 9 - D, 1)
          120   UNTIL D = 4 OR left$ <> right$
          130   REM if left$ = right$ when we get to this line then M = palindrome
          140   IF left$ = right$ THEN PRINT N, M
          150 UNTIL L > 8
          155 PRINT "Time = ";(TIME - start%) / 100;"s"
        >RUN
              5291  27999972
              5313  28233282
        Time = 322.32s
        >
        

        Like

      • Jim Randell's avatar

        Jim Randell 3:40 pm on 5 March 2023 Permalink | Reply

        A little bit of analysis can significantly reduce the number of cases tested:

        In order for n(n + 1) to be a palindrome it cannot be divisible by 10, so neither n nor (n + 1) can be divisible by 5 (as one of them will be even).

        And if (n + 22)(n + 23) is a palindrome, neither (n + 22) nor (n + 23) can be divisible by 5.

        So the residue of n mod 5 cannot be 0, 2, 3, 4. Hence it must be 1.

        So we only need to consider n values of the form (5k + 1).

        Also if n(n + 1) is an 8-digit palindrome, say ABCDDCBA then we have:

        n(n + 1) = 10000001.A + 1000010.B + 100100.C + 11000.D
        n(n + 1) = 11.(909091.A + 90910.B + 9100.C + 1000.D)

        So one of n, (n + 1) must be divisible by 11. (And also one of (n + 22), (n + 23) but that follows directly).

        So here is a program that only considers 248 candidate numbers, and has an internal runtime of 491µs.

        Run: [ @replit ]

        from enigma import (irange, inf, is_npalindrome, nsplit, printf)
        
        # function to apply
        f = lambda x: x * (x + 1)
        
        # consider possible numbers
        for n in irange(1, inf, step=5):
          if n % 11 not in {0, 10}: continue
          # apply the function
          v = f(n)
          if v < 10_000_000: continue
          v2 = f(n + 22)
          if v2 > 100_000_000: break
          # is the result a palindrome?
          if is_npalindrome(v) and is_npalindrome(v2):
            printf("f({n}) = {v}; f({n2}) = {v2}", n2=n + 2)
        

        In particular using this approach I was able to write a modified version of Victor’s BBC BASIC program that reduced the runtime from 5m22s (Victor’s original program) to 13.9s (my modified program).

        >LIST
           10 REM Modification of Victor Bryant program for Teaser 1071
           20 REM from "Microteasers" (1986)
           30 REM We want N + N^2 to be an 8-digit palindrome
           40 start% = TIME
           50 FOR N = 3161 TO 9999 STEP 5
           60   IF N MOD 11 <> 0 AND N MOD 11 <> 10 THEN 140
           70   P = N + N*N
           80   IF NOT FNpal(STR$(P)) THEN 140
           90   M = N + 22
          100   Q = M + M*M
          110   IF NOT FNpal(STR$(Q)) THEN 140
          120   PRINT N, P
          130   PRINT M, Q
          140 NEXT N
          150 PRINT "Time = ";(TIME - start%) / 100;"s"
          160 END
          170 REM check for 8 character palindrome
          180 DEF FNpal(M$)
          190   IF LEN(M$) <> 8 THEN =FALSE
          200   FOR D = 1 TO 4
          210     left$ = MID$(M$, D, 1)
          220     right$ = MID$(M$, 9 - D, 1)
          230     IF left$ <> right$ THEN =FALSE
          240   NEXT D
          250   =TRUE
        >RUN
              5291  27999972
              5313  28233282
        Time = 13.9s
        

        But the setter must have some other analysis in mind to reduce the number of calculations to less than 20.

        Like

    • GeoffR's avatar

      GeoffR 1:29 pm on 5 March 2023 Permalink | Reply

      
      # testing for (num + square(num)) between 10 million and 100 million approx.
      # LB = 3161 + 3161 ** 2 = 9,995,082
      # UB = 9999 + 9999 ** 2 = 99,990,000
      for n in range(3161, 10000):
        sum_prod = n + n ** 2
        # check if this number is a numerical palindrome
        if str(sum_prod) == str(sum_prod)[::-1]:
          # Second number is 22 more than first number
          n2 = n + 22
          sum_prod2 = n2 + n2 ** 2
          # check if this number is a numerical palindrome
          if str(sum_prod2) == str(sum_prod2)[::-1]:
            print(f"Pair of numbers are {n} and {n2}.")
            print(f"Associated Palindromes are {sum_prod} and {sum_prod2}.")
              
      # Pair of numbers are 5291 and 5313.
      # Associated Palindromes are 27999972 and 28233282.
      
      

      Like

    • GeoffR's avatar

      GeoffR 8:57 am on 6 March 2023 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Pair of required numbers 
      var 3161..9999:N1;
      var 3161..9999:N2;
      
      % Palindrome digits for both palindromes
      var 1..9:A; var 0..9:B; var 0..9:C; var 0..9:D;
      var 1..9:G; var 0..9:H; var 0..9:I; var 0..9:J; 
       
      % Two eight digit palindromes
      var 10000000..99999999:ABCDDCBA = A * pow(10,7) + B * pow(10,6) + C * pow(10,5)
      + D * pow(10,4) + D * pow(10,3) + C * pow(10,2) + B * pow(10,1) + A;
      
      var 10000000..99999999: GHIJJIHG = G * pow(10,7) + H * pow(10,6) + I * pow(10,5)
      + J * pow(10,4) + J * pow(10,3) +  I * pow(10,2) + H * pow(10,1) + G;
      
      % Palindrome formation
      constraint N1 + N1 * N1 == ABCDDCBA;   % 1st 8-digit palindrome
      
      % Pair of numbers differ by twenty-two
      constraint N2 == N1 + 22;
      constraint N2 + N2 * N2 == GHIJJIHG;   % 2nd 8-digit palindrome
      
      solve satisfy;
      
      output ["Two required numbers are " ++ show(N1) ++ " and " ++ show(N2) 
      ++ "\n" ++ "Palindromes are " ++ show(ABCDDCBA) ++ " and " ++ show(GHIJJIHG)];
      
      % Two required numbers are 5291 and 5313
      % Palindromes are 27999972 and 28233282
      % ----------
      % ==========
      
      
      

      Like

    • John Crabtree's avatar

      John Crabtree 4:59 pm on 8 March 2023 Permalink | Reply

      f(N) = N(N+1) = “ABCDDCBA” = 0 (mod 11) as the sum of alternating digits is the same
      f(N+22) – f(N) = 44N + 506
      The first and last digits do not change. The second digit from the left, B, must either be the same or increase by 1.
      If it stays the same, 44N + 506 = 0 (mod 100) with no solution.
      If it increases by 1, 44N + 506 = 10 (mod 100) which leads to N = 16 mod 25

      41*42 = 66*67 = 22 (mod 100)
      sqrt(22) = 4.690 and sqrt(23) = 4.795
      4741 = 0 (mod 11), but f(4741+22) must start with 22 and is invalid
      4766 = 3 (mod 11)

      16*17 = 91*92 = 72 (mod 100)
      sqrt(27) = 5.196 and sqrt(28) = 5.291
      5191 = 10 (mod 11), but is too small
      5216 = 2 (mod 11)
      5291 = 0 mod 11, and f(5191+22) must start with 28

      And so the numbers must be 5291 and 5313.
      At this point the calculator is very useful to find that
      f(5291) = 27,999,922 and f(5313) = 28,233,282

      There are not many calculations in this solution. I do not understand the setter’s statement about needing as many calculations again to show that the solution is unique. That is included above.

      Like

      • John Crabtree's avatar

        John Crabtree 5:50 am on 10 March 2023 Permalink | Reply

        Simplifying, N = -34 + 275k = 10 (mod 11); or N = 66 + 275k = 0 (mod 11)
        With AB = 22 or 27, there are only two possible values of N:
        4741 = 4675 (17 * 275) + 66, but f(4741 + 22) is invalid
        5291 = 5225 (19 * 275) + 66, and f(5291 + 22) = f(5313) is valid

        Like

  • Unknown's avatar

    Jim Randell 9:48 am on 9 September 2021 Permalink | Reply
    Tags: by: J H Perryman   

    Brain-Teaser 859: Sick transit 

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

    The State of Inertia, in a last-ditch effort to revive an ailing economy, has decided to go ahead with the controversial innovation of adding two new digits

    POW! (Written ↑)
    WHAM! (Written ↓)

    thus symbolising the stark alternatives facing the nation.

    In a massive referendum on the relative merits, the country came down in favour of POW! carrying the greater weight and accordingly WHAM! is interposed in a lower position than POW! among the ten old digits, the usual order of which is retained.

    Teething troubles from the consequential change to duodecimal-based arithmetic and to the new values of some of the old digits, are minimised by the free provision to everyone of school-age or over of PEST, an appropriate Pocket Electronic Summary Tabulator.

    To enable a check to be made on the correct working of the instruments every PEST comes with the answers to 35 × 64 and 54 × 66, one consisting entirely of the new shapes and the other of neither of them.

    What are the two answers ?

    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.

    [teaser859]

     
    • Jim Randell's avatar

      Jim Randell 9:49 am on 9 September 2021 Permalink | Reply

      Using P for “POW!” and W for “WHAM!”:

      If we interpret “interposed” to mean that P and W are inserted between 2 existing symbols, then we find a unique solution.

      The following Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, base2int, int2base, join, printf)
      
      # categorise a string
      # 'new' -> entirely composed of new symbols
      # 'old' -> entirely composed of old symbols
      # 'mix' -> a mixture of new and old symbols
      def categorise(s):
        s = set(s)
        assert bool(s)
        if s.issubset('PW'): return 'new'
        if not s.intersection('PW'): return 'old'
        return 'mix'
      
      # make possible base 12 symbol sets
      for (w, p) in subsets(irange(1, 9), size=2):
        syms = list("0123456789")
        syms.insert(p, 'P')
        syms.insert(w, 'W')
      
        # convert between strings and numbers
        s2n = lambda s: base2int(s, base=12, digits=syms)
        n2s = lambda n: int2base(n, base=12, digits=syms)
      
        # calculate: A = '35' * '64', B = '54' * '66'
        A = n2s(s2n('35') * s2n('64'))
        B = n2s(s2n('54') * s2n('66'))
        # one is entirely composed of new symbols and one entirely composed of old
        if set(map(categorise, (A, B))) == {'old', 'new'}:    
          printf("digits 0-11 = {syms}", syms=join(syms))
          printf("  35 * 64 = {A}; 54 * 66 = {B}")
      

      Solution: The two sums are: 35 × 64 = 2445 and 54 × 66 = ↓↑↑↓.

      The digits from 0 to 11 are represented by:

      0 1 2 3 W 4 5 P 6 7 8 9

      So the sums are:

      35 × 64 = 2445 [Inertial base 12]
      36 × 85 = 2556 [standard base 12]
      42 × 101 = 4242 [standard decimal]

      54 × 66 = WPPW [Inertial base 12]
      65 × 88 = 4774 [standard base 12]
      77 × 104 = 8008 [standard decimal]

      However, if we allow W and P to be inserted anywhere (but still with W before P), we find an additional solution using the following digits:

      W 0 1 2 3 P 4 5 6 7 8 9

      And the sums are:

      35 × 64 = 2194 [Inertial base 12]
      47 × 86 = 32B6 [standard base 12]
      55 × 102 = 5610 [standard decimal]

      54 × 66 = PPWW [Inertial base 12]
      76 × 88 = 5500 [standard base 12]
      90 × 104 = 9360 [standard decimal]

      We can see this additional solution by using the following call to [[ subsets() ]] in line 15:

      subsets(irange(0, 10), size=2, select='R')
      

      Like

  • Unknown's avatar

    Jim Randell 9:47 am on 20 August 2019 Permalink | Reply
    Tags: by: J H Perryman   

    Brainteaser 1659: Prime cuts 

    From The Sunday Times, 26th June 1994 [link]

    My new season ticket number is a prime obtained by rearranging five consecutive digits. By cutting off successive single digits from alternate ends I can steadily reduce this to a four-figure prime, a three-figure prime, a two-figure prime and finally a one-figure prime.

    You too can use some short cuts to find my season ticket number.

    This puzzle is included in the book Brainteasers (2002).

    [teaser1659]

     
    • Jim Randell's avatar

      Jim Randell 9:48 am on 20 August 2019 Permalink | Reply

      If we denote the 5-digit number ABCDE, then we can see that all of (C, BCD, ABCDE) must be prime.

      And depending on whether we start truncating on the left or the right one of (CD, BCDE) or (BC, ABCD) must be prime.

      We can write these conditions as a set of alphametic constraints and use the general alphametic solver [[ SubstitutedExpression() ]] from the enigma.py library to solve them.

      This run file executes in 146ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      --answer="ABCDE"
      
      # the digits are consecutive
      "max(A, B, C, D, E) - min(A, B, C, D, E) = 4"
      
      # odd length truncations
      "is_prime(ABCDE)"
      "is_prime(BCD)"
      "is_prime(C)"
      
      # even length truncations
      "(is_prime(BCDE) and is_prime(CD)) or (is_prime(ABCD) and is_prime(BC))"
      

      Solution: The ticket number is 68597.

      It is composed from the digits 5, 6, 7, 8, 9, and can be truncated to give the following primes: 68597, 8597, 859, 59, 5.

      Like

    • GeoffR's avatar

      GeoffR 12:47 pm on 20 August 2019 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn"; 
      
      % Pattern of Primes
      % C
      % CD
      % BCD
      % BCDE
      % ABCDE
      
      var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D; var 1..9:E;
      
      constraint all_different([A,B,C,D,E]);
      
      % the five digit prime number contain consecutive digits
      var set of int : s5 = {A,B,C,D,E};
      constraint max ([A,B,C,D,E]) - min([A,B,C,D,E]) == card(s5) - 1;
       
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      var 10..99: CD = 10*C + D;
      var 100..999: BCD = 100*B + 10*C + D;
      var 1000..9999: BCDE = 1000*B + 100*C + 10*D + E;
      var 10000..99999: ABCDE = 10000*A + 1000*B + 100*C + 10*D + E;
      
      constraint is_prime(C) /\ is_prime(CD) /\ is_prime(BCD)
      /\ is_prime (BCDE) /\ is_prime(ABCDE);
      
      solve satisfy;
      
      output["Primes numbers are " ++ show(ABCDE) ++ ", " 
      ++ show(BCDE) ++ ", " ++ show(BCD) ++ ", " ++ show(CD) ++ 
      " and " ++ show(C) ];
      
      % Primes numbers are 68597, 8597, 859, 59 and 5
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 12:51 pm on 21 April 2019 Permalink | Reply
    Tags: by: J H Perryman   

    Brainteaser 1557: An alphabetical jigsaw 

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

    I have some jigsaw pieces which fit together to form a 5-by-5 square with the letters all the right way up. Here is an attempt to make the jigsaw:

    The attempt has failed because the last piece does not fit the right way up. In fact it is possible to make the 5-by-5 jigsaw so that the 25 different letters spell six words in reading order.

    What is this arrangement?

    This puzzle appears in the book Brainteasers (2002). The wording above is taken from the book, but is only slightly changed from the original puzzle.

    [teaser1557]

     
    • Jim Randell's avatar

      Jim Randell 12:52 pm on 21 April 2019 Permalink | Reply

      There are two parts to the program. The first is concerned with fitting the pieces into the grid. The second part is then reading the letters in the grid as a collection of words.

      This Python program runs in 6.6s, but most of the time is taken up with collecting the word list. I used a collection of allowable Scrabble words, which gives 49,595 viable words.

      Run: [ @replit ] (using just the selected words)

      from itertools import product
      from collections import (Counter, defaultdict)
      from enigma import (irange, find, chunk, flatten, join, printf)
      
      (X, Y) = (1, 7)
      
      pieces = [
        { 0: 'H', Y: 'Z', X + Y: 'F', X + Y + Y: 'X' },
        { 0: 'P', X: 'S', X + X: 'C' },
        { 0: 'I', Y: 'J', Y + Y: 'O' },
        { 0: 'G', X: 'Y', X + Y: 'M' },
        { 0: 'T', Y: 'V', Y + Y: 'N' },
        { 0: 'E', Y: 'K', X + Y: 'W' },
        { 0: 'U', Y: 'R', X + Y: 'D' },
        { 0: 'A', X: 'L', X + Y: 'B' },
      ]
      
      # create the empty grid
      grid = [None] * (Y * Y)
      for (x, y) in product(irange(1, 5), irange(1, 5)):
        grid[x + y * Y] = '.'
      
      # fit piece p into grid g at index i
      # return new grid or None
      def fit(g, p, i):
        g = g.copy()
        for (k, v) in p.items():
          x = g[i + k]
          # non-usable square?
          if x != '.': return None
          g[i + k] = v
        return g
      
      def solve(ps=pieces, g=grid):
        # are we done?
        if not ps:
          # return the completed (unpadded) grid
          yield list(r[1:-1] for (i, r) in enumerate(chunk(g, Y), start=1) if 1 < i < Y)
        else:
          # find an empty square
          i = find(g, '.')
          if i != -1:
            for (j, p) in enumerate(ps):
              g2 = fit(g, p, i)
              if g2 is not None:
                yield from solve(ps[:j] + ps[j + 1:], g2)
      
      # list of words to examine
      file = "words.txt"
      
      # collect letters from the pieces
      letters = Counter()
      for p in pieces: letters.update(p.values())
      
      # collect words that can be formed from the letters
      words = defaultdict(list)
      for w in open(file).readlines():
        w = w.strip().upper()
        if not (Counter(w) - letters):
          words[w[0]].append(w)
      
      printf("[collected {n} words from {file}]", n=sum(len(v) for v in words.values()))
      
      # form text t into a sequence of words
      def check(t, s=[]):
        if not t:
          yield s
        else:
          for w in words[t[0]]:
            if t.startswith(w):
              yield from check(t[len(w):], s + [w])
      
      
      # fit the pieces into the grid
      for g in solve():
        # attempt to form the letters into words
        wss = list(check(join(flatten(g))))
        if wss:
          # output the grid
          for r in g:
            printf("{r}", r=join(r, sep=" "))
          # output the words
          for ws in wss:
            if len(ws) == 6:
              printf("words: {ws}", ws=join(ws, sep=", "))
          printf()
      

      Solution: The completed jigsaw looks like this:

      So the 6 words are: GYP, SCHMALTZ, FIB, VEX, JUNK, WORD.

      There are 64 ways to fit the pieces into the grid, but only one of them makes a set of viable words.

      There are 2 fifteen letter words in the list that can be made from the collection of letters in the puzzle: DERMATOGLYPHICS, UNCOPYRIGHTABLE.

      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