Recent Updates Page 60 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 8:36 am on 6 August 2019 Permalink | Reply
    Tags:   

    Brainteaser 1635: Double anagram 

    From The Sunday Times, 9th January 1994 [link]

    In the woodwork class the ABLEST students made STABLE TABLES.

    In the arithmetic class the cleverest students took those three six-letter words which were anagrams of each other, and they then assigned a different digit to each of the six letters involved. Substituting letters for digits then gave them three six-figure numbers.

    They found that one of the numbers was the sum of the other two. Furthermore, no matter what alternative substitution of digits they had used, they could never have achieved this coincidence with a lower sum.

    (a) Which word was the sum?
    (b) What was its numerical value?

    This puzzle is included in the book Brainteasers (2002). The text was changed slightly, but the puzzle remains the same.

    [teaser1635]

     
    • Jim Randell's avatar

      Jim Randell 8:37 am on 6 August 2019 Permalink | Reply

      This program uses the [[ SubstitutedExpression() ]] solver from the enigma.py library to solve the alphametic problem, and an [[ Accumulator() ]] object to find the smallest solution. It runs in 299ms.

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, Accumulator, sprintf as f, join, printf)
      
      # the three words
      words = ("ABLEST", "STABLE", "TABLES")
      
      # the alphametic puzzle
      ws = join(words, sep=",", enc="()")
      p = SubstitutedExpression(f("sum({ws}) == 2 * max({ws})"), answer=ws)
      
      # look for a minimal solution
      r = Accumulator(fn=min)
      
      # solve the alphametic
      for ans in p.answers(verbose=0):
        # find the result of the sum
        s = max(ans)
        # and which word is the result
        i = ans.index(s)
        r.accumulate_data(s, i)
      
      # output the minimal solution
      printf("{word} = {r.value} [of {r.count} possible sums]", word=words[r.data])
      

      Solution: TABLES = 417582.

      The corresponding alphametic sum is: ABLEST + STABLE = TABLES.

      There are 18 possible solutions to this alphametic.

      Like

    • GeoffR's avatar

      GeoffR 1:29 pm on 6 August 2019 Permalink | Reply

      I tried the three possible addition sums, looking for the smallest total. Two of these sums proved unsatisfiable. The third addition sum gave two possible values for TABLES, the smallest of which agreed with Jim’s solution.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9: A; var 0..9: B; var 0..9: L;
      var 0..9: E; var 1..9: S; var 1..9: T;
      
      constraint all_different([A, B, L, E, S, T]);
      
      var 100000..999999: ABLEST = 100000*A + 10000*B + 1000*L + 100*E + 10*S + T;
      var 100000..999999: STABLE = 100000*S + 10000*T + 1000*A + 100*B + 10*L + E;
      var 100000..999999: TABLES = 100000*T + 10000*A + 1000*B + 100*L + 10*E + S;
      
      %constraint ABLEST == STABLE + TABLES;
      %solve minimize(ABLEST);
      %output ["ABLEST = " ++ show(ABLEST) ];  % =====UNSATISFIABLE=====
      
      %constraint STABLE == ABLEST + TABLES;
      %solve minimize(STABLE);
      %output ["STABLE = " ++ show(STABLE) ];  % =====UNSATISFIABLE=====
      
      constraint TABLES == ABLEST + STABLE;
      solve minimize(TABLES);
      output ["TABLES = " ++ show(TABLES)  ++ " = " ++ show(ABLEST) ++ " + " ++ show(STABLE)];
      
      % TABLES = 428571 = 285714 + 142857
      % ----------
      % TABLES = 417582 = 175824 + 241758  << smallest answer for value of TABLES
      % ----------
      % ==========
      
      
      

      Like

      • John Crabtree's avatar

        John Crabtree 6:21 pm on 8 August 2019 Permalink | Reply

        With TABLES as the sum, ABLE = 999(T-S) – 100S – 10T. This enables the desired solution, as well as all of the possible 18 found by Jim, to be easily found.

        Like

    • Frits's avatar

      Frits 2:36 pm on 27 July 2020 Permalink | Reply

      Substituted expression which returns only one solution (minimized on first part of answer):

      # The following function has been manually added to enigma.py
      #
      # def substituted_expression_minimum(*args, **kw):
      #   if 'verbose' not in kw: kw['verbose'] = 0
      #   answs = []
      #   for r in SubstitutedExpression(*args, **kw).solve():
      #     answs.append(r)
      #   answs.sort(key=lambda x: x[1])  
      #   if len(answs) > 0:
      #       yield answs[0]
      
      
      from enigma import substituted_expression_minimum, printf, irange
      
      # the alphametic puzzle
      p = substituted_expression_minimum(\
          [\
          # if one element in (X,Y,Z) is sum of 2 others then sum(X,Y,Z) = 2*max(X,Y,Z)
          # in this way we don't have to specify it is Z=X+Y, Y=X+Z or X=Y+Z 
          "TABLES + ABLEST + STABLE == 2 * max(TABLES, ABLEST, STABLE)",\
          "max(TABLES, ABLEST, STABLE) = HIGNUM",\
          "not(is_duplicate(TABLES))",\
          "not(is_duplicate(ABLEST))",\
          "not(is_duplicate(STABLE))"],\
          distinct="",   # needed for HIGNUM \   
          answer="HIGNUM, ABLEST, STABLE, TABLES",\
          verbose=0)      
                                
      for (_, ans) in p: 
        if ans[1] == ans[0]: printf("ABLEST is the sum with value {ans[1]}")
        if ans[2] == ans[0]: printf("STABLE is the sum with value {ans[2]}")
        if ans[3] == ans[0]: printf("TABLES is the sum with value {ans[3]}")
      

      Like

      • Frits's avatar

        Frits 3:12 pm on 27 July 2020 Permalink | Reply

        @Jim:

        Any idea why I needed the HIGNUM field?

        p = substituted_expression_minimum(\
            [\
            "TABLES + ABLEST + STABLE == 2 * max(TABLES, ABLEST, STABLE)"],\
            answer="TABLES + ABLEST + STABLE, ABLEST, STABLE, TABLES",\
            verbose=0)   
        

        Using this setup I got no results (even with distinct=””).

        Like

        • Jim Randell's avatar

          Jim Randell 3:33 pm on 27 July 2020 Permalink | Reply

          @Frits: I think you get no answers because you are checking to see when one of the summands is equal to the sum total (which is never).

          Try using:

          answer="(max(TABLES, ABLEST, STABLE), ABLEST, STABLE, TABLES)"
          

          BTW: You don’t need to modify the enigma.py library, you can just define the [[ substituted_expression_minimum ]] function in your own program.

          Like

    • Frits's avatar

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

      @Jim: Thanks for the internal accumulate function

      from enigma import SubstitutedExpression, printf
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          # if one element in (X,Y,Z) is sum of 2 others then sum(X,Y,Z) = 2*max(X,Y,Z)
          # in this way we don't have to specify it is Z=X+Y, Y=X+Z or X=Y+Z 
          "TABLES + ABLEST + STABLE == 2 * max(TABLES, ABLEST, STABLE)",
          "not(is_duplicate(TABLES))",
          "not(is_duplicate(ABLEST))",
          "not(is_duplicate(STABLE))"
        ],
        answer="(TABLES + ABLEST + STABLE)/2, ABLEST, STABLE, TABLES",
        accumulate=min,
        verbose=0)      
          
      # solve the puzzle
      r = p.run()
      
      # Print answer
      lst = ["ABLEST", "STABLE", "TABLES"]
      cnt = 0
      for ans in r.accumulate:
          if cnt == 0: hghnum = ans
          else:  
            if ans == hghnum: printf("{lst[cnt-1]} is the sum with value {ans}")
          cnt += 1 
      

      Like

  • Unknown's avatar

    Jim Randell 8:42 am on 5 August 2019 Permalink | Reply
    Tags:   

    Teaser 2893: Win some, lose some 

    From The Sunday Times, 4th March 2018 [link] [link]

    The six teams in our local league play each other once in the season. Last season no two teams tied on points.

    Above is part of the table from the end of that season, with the teams in alphabetical order.

    However, digits have been consistently replaced by letters.

    Find the value of the number of LEMONS.

    [teaser2893]

     
    • Jim Randell's avatar

      Jim Randell 8:43 am on 5 August 2019 Permalink | Reply

      When I first looked at this puzzle I assumed the scoring regime was “2 points for a win, 1 point to each side for a draw” (which is a common scenario in Enigma puzzles [ link ]), but that gave me two possible solutions (the values for L and N are interchangeable). But using “3 points for a win, 1 point to each side for a draw” give a unique solution, and is probably what the setter had in mind. (This was later confirmed).

      I derived a set of constraints from the table and then used the general alphametic solver [[ SubstitutedExpression() ]] from the enigma.py library to solve the puzzle.

      The following run file executes in 73ms. (Internal runtime of the generated program is 94µs).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --answer="LEMONS"
      
      # each team has played 5 matches
      "W = 5"
      
      # won + lost + drawn = 5
      "O + N <= 5"
      "S + O + M = 5"
      "L + O <= 5"
      "S <= 5"
      
      # goals for >= won, goals against >= lost
      "E >= S"
      "S >= 5 - L - O"
      "T >= L"
      "O >= (E - S) // 3" "(E - S) % 3 = 0"
      
      # points (= 3 * won + drawn) are all different
      --code="pts = lambda w, d: 3 * w + d"
      "all_different(pts(O, 5 - O - N), pts(S, M), pts(5 - L - O, O), E)"
      

      Solution: LEMONS = 370124.

      We could provide additional constraints in the run file (e.g. assign W=5; and L, M, N, O, S are 0, 1, 2, 3, 4 (in some order)) but it doesn’t make much difference to the run time if we do:

      # [optional] w, l, d can only contain digits 0-5 (and W is 5)
      --assign="W,5"
      --invalid="6|7|8|9,LMNOS"
      

      If we change the definition of [[ pts() ]] to use the “2 points for a win …” scenario, and we find there is an additional solution of:

      LEMONS = 270134

      Like

  • Unknown's avatar

    Jim Randell 9:10 am on 4 August 2019 Permalink | Reply
    Tags: by: Angela Humphreys,   

    Brain-Teaser 491: [Family names] 

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

    Each of the five families in Admirals Walk comprised three boys; one of the boys in each family had the same christian name as the surname of one of his neighbours, and no boy had the same christian and surname.

    The three christian names of Mr. Arnold’s three sons all had the same initial as the surname of Lawrence, who lived opposite to Benjamin Thomas.

    Mr. Gordon’s oldest son had a christian name with the same initial as Clement’s surname. Clement’s father played chess with Jasper Lawrence’s father.

    Mr. Oliver was Arnold’s father’s business partner. Godfrey had measles. Mr. Oliver’s oldest son had the same christian name as Crispin’s surname, and also the same initial to his christian name as Arnold’s surname. Arnold lived next door to the man whose son Oswald played truant from school with his cousin Hector Lawrence, until Oswald’s brother Walter, a school prefect, caught them.

    Gilbert and Oscar had red hair.

    Oliver was in Borstal. What was his surname?

    When originally published the condition relating to Mr. Oliver’s sons was given as:

    Mr. Oliver’s oldest son had the same christian name as Crispin’s surname, and Mr. Oliver’s youngest son had the same initial to his christian name as Arnold’s surname.

    However a correction was published with Brain-Teaser 493 stating:

    It is regretted that in para. 3, line 7, “youngest” should have read “eldest”. However, as the correct initials of these boys can be proved independently and most solvers gave the correct answer, this answer will stand.”

    I have made the correction in the puzzle text above, although instead of just changing “youngest” to “eldest”, as we are already talking about Mr. Oliver’s oldest son, I used “and also”.

    This puzzle was originally published with no title.

    [teaser491]

     
    • Jim Randell's avatar

      Jim Randell 9:11 am on 4 August 2019 Permalink | Reply

      There are 15 first names mentioned, so each boy must have a different first name.

      This Python 3 program starts by filling out the full names we are given. Then it looks for three first names with the same initial for the Arnold family (which also gives us Lawrence’s surname). It then allocates the remaining names, and checks the remaining conditions.

      It runs in 271ms.

      Run: [ @repl.it ]

      from collections import defaultdict
      from enigma import subsets, update, diff, multiset, Accumulator, join, printf
      
      # names we know (map: firstname -> lastname)
      names = { "Benjamin": "Thomas", "Hector": "Lawrence", "Jasper": "Lawrence" }
      
      # the surnames are all distinct by initial
      lastnames = dict((x[0], x) for x in ("Arnold", "Gordon", "Lawrence", "Oliver", "Thomas"))
      
      # the remaining first names
      firstnames = ["Clement", "Crispin", "Gilbert", "Godfrey", "Oscar", "Oswald", "Walter" ]
      firstnames.extend(lastnames.values())
      
      # group firstnames by initial
      initial = defaultdict(list)
      for f in firstnames:
        initial[f[0]].append(f)
      
      # complete the families, ensuring no-one has the same first/last name
      def complete(ns, fs):
        # are we done?
        if not fs:
          yield ns
        else:
          # find an incomplete family to fill out
          s = multiset(ns.values())
          r = Accumulator(fn=min)
          for v in lastnames.values():
            n = 3 - s.get(v, 0)
            if n > 0: r.accumulate_data(n, v)
          (n, v) = (r.value, r.data)
          # choose names
          for ts in subsets(diff(fs, [v]), size=n):
            yield from complete(update(ns, ts, [v] * n), diff(fs, ts))
      
      # collect results
      r = multiset()
      
      # the Arnold children share the same first initial
      for (k, vs) in initial.items():
        if k == 'L': continue
        for fs in subsets(vs, size=3):
          if "Arnold" in fs: continue
          ns1 = update(names, fs, ["Arnold"] * 3)
      
          # Lawrence has one of these names as a surname
          ns1["Lawrence"] = lastnames[k]
      
          # complete the allocations
          for ns in complete(ns1, diff(firstnames, ns1.keys())):
      
            # "Clement's father played chess with Jasper Lawrence's father"
            # so Clement's surname is not Lawrence
            if ns["Clement"] == "Lawrence": continue
      
            # "Mr. Oliver was Arnold's father's business partner"
            # so Arnold's surname is not Oliver
            if ns["Arnold"] == "Oliver": continue
      
            # "Oswald played truant from school with his cousin Hector Lawrence"
            # so Oswald's surname is not Lawrence
            if ns["Oswald"] == "Lawrence": continue
      
            # "Arnold lived next door to ... Oswald"
            # so Arnold and Oswald have different surnames
            if ns["Arnold"] == ns["Oswald"]: continue
      
            # "... Oswald's brother Walter ..."
            # so Oswald and Walter have the same surname
            if ns["Oswald"] != ns["Walter"]: continue
      
            # "Mr. Oliver's oldest son had the same christian name as Crispin's surname ..."
            oliver_maj = ns["Crispin"]
            if ns[oliver_maj] != "Oliver": continue
      
            # "...and the same initial to his christian name as Arnold's surname"
            if oliver_maj[0] != ns["Arnold"][0]: continue
      
            # "Mr. Gordon's oldest son had a christian name with the same initial as Clement's surname"
            gordon_maj = list(k for (k, v) in ns.items() if v == "Gordon" and k[0] == ns["Clement"][0])
            if not gordon_maj: continue
      
            # each family has a child whose name is the surname of one of the other families
            ss = sorted(lastnames.values())
            fss = list()
            for v in ss:
              fss.append(set(x for (x, s) in ns.items() if s == v))
            if not all(fs.intersection(ss) for fs in fss): continue
      
            # output families
            fmt = lambda s: join(sorted(s), sep=", ")
            for (s, fs) in zip(ss, fss):
              printf("{s}: {fs}", fs=fmt(fs))
            printf("Oliver maj = {oliver_maj}; Gordon maj = {gordon_maj}", gordon_maj=fmt(gordon_maj))
            printf()
      
            # record the answer (Oliver's surname)
            r.add(ns["Oliver"])
      
      
      # output the answer
      for (s, n) in r.most_common():
        printf("Oliver {s} [{n} solutions]")
      

      Solution: Oliver’s surname is Lawrence.

      The families are:

      Arnold: Gilbert, Godfrey, Gordon
      Gordon: Lawrence, Oswald, Walter
      Lawrence: Hector, Jasper, Oliver
      Oliver: Clement, Oscar, Thomas
      Thomas: Arnold, Benjamin, Crispin

      Oswald Gordon and Oliver Thomas are the eldest sons in their respective families.

      The originally published puzzle said that “Mr. Oliver’s youngest son had the same initial to his christian name as Arnold’s surname”, which cannot be satisfied with the rest of the conditions. The youngest of the Oliver children must be Clement or Oscar, and Arnold’s surname is Lawrence or Thomas.

      With the change of the puzzle text to refer to the oldest son (who is Thomas Oliver), he shares the same initial (and indeed the same name) as Arnold, as Arnold and Crispin are brothers (both Thomases).

      Like

  • Unknown's avatar

    Jim Randell 5:47 pm on 2 August 2019 Permalink | Reply
    Tags:   

    Teaser 2967: Odds and evens 

    From The Sunday Times, 4th August 2019 [link] [link]

    I have done a “long multiplication”, which is reproduced above. [If the multiplication was ABC × DE, then the third line shows the result of ABC × E and the fourth line shows the result of ABC × D]. However instead of writing the actual digits involved, I have written “O” where there is an odd digit and “E” where there is an even digit.

    What is the result of the multiplication?

    [teaser2967]

     
    • Jim Randell's avatar

      Jim Randell 5:59 pm on 2 August 2019 Permalink | Reply

      (See also: Enigma 1242, Enigma 1721, Enigma 109, Enigma 1503).

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

      The following run file executes in 140ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      #     A B C
      #  *    D E
      #   -------
      #   J K L M (= ABC * E)
      #   N P Q   (= ABC * D)
      #   -------
      #   F G H I
      #   =======
      
      SubstitutedExpression
      
      --distinct=""
      --invalid="1|3|5|7|9,BCDEHIJLMNQ" # even digits
      --invalid="2|4|6|8,AFGKP" # odd digits
      --invalid="0,ADFGJKNP" # odd digits + leading even digits
      
      "ABC * DE = FGHI"
      
      "ABC * E = JKLM"
      "ABC * D = NPQ"
      

      Solution: The result of the multiplication sum is 9744.

      The full sum is:

      348 × 28 = 2784 + 696×10 = 9744

      Like

    • GeoffR's avatar

      GeoffR 8:45 am on 5 August 2019 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 0..9:A; var 0..9:B; var 1..9:C; 
      var 0..9:D; var 0..9:E; 
      var 0..9:F; var 0..9:G; var 0..9:H; var 0..9:I; 
      var 0..9:J; var 0..9:K; var 0..9:L; var 0..9:M; 
      var 0..9:N; var 0..9:P; var 0..9:Q;
      
      constraint A > 0 /\ D > 0 /\ J > 0 /\ N > 0 
      /\ K > 0/\ F > 0 /\ G > 0 /\ P > 0;
      
      % Allocate parity to the digits
      constraint A mod 2 == 1 /\ B mod 2 == 0 /\ C mod 2 == 0;
      constraint D mod 2 == 0 /\ E mod 2 == 0;
      constraint F mod 2 == 1 /\ G mod 2 == 1 /\ H mod 2 == 0
       /\ I mod 2 == 0;
      
      constraint J mod 2 == 0 /\ K mod 2 == 1 /\ L mod 2 == 0
       /\ M mod 2 == 0;
      constraint N mod 2 == 0 /\ P mod 2 == 1 /\ Q mod 2 == 0;
      
      % Components of multiplication sum
      var 100..999: ABC = 100*A + 10*B + C;
      var 10..99: DE = 10*D + E;
      var 1000..9999: JKLM = 1000*J + 100*K +10*L + M;
      var 1000..9999: NPQ = 1000*N + 100*P + 10*Q;
      var 1000..9999: FGHI = 1000*F + 100*G + 10*H + I;
      
      constraint ABC * DE == FGHI;
      constraint ABC * D * 10 == NPQ;
      constraint ABC * E == JKLM;
      
      solve satisfy;
      
      output ["Multiplication sum is " ++ show(ABC) ++ " * " 
      ++ show(DE) ++ " = " ++ show(JKLM) ++ " + " ++ show(NPQ)
       ++ " = " ++ show(FGHI) ];
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:28 am on 2 August 2019 Permalink | Reply
    Tags:   

    Teaser 2892: Political pairings 

    From The Sunday Times, 25th February 2018 [link] [link]

    A debating society arranged for a series of eight political debates between different Labour and Conservative politicians, each time with a different presenter.

    The presenters were:

    MARR, NEIL, NEWMAN, PARKINSON, PAXMAN, POPPLEWELL, ROBINSON and SNOW.

    The Labour politicians were:

    ABBOTT, ABRAHAMS, CHAKRABARTI, CORBYN, EAGLE, LEWIS, STARMER and WATSON.

    The Conservative politicians were:

    BRADLEY, GRAYLING, GREEN, HAMMOND, LEADSOM, LIDINGTON, MCLOUGHLIN and MUNDELL.

    For each debate there were 2 letters in common for any pairing from presenter, Labour politician and Conservative politician. The Labour politicians are listed alphabetically.

    What is the corresponding order for the Conservative politicians?

    [teaser2892]

     
    • Jim Randell's avatar

      Jim Randell 10:29 am on 2 August 2019 Permalink | Reply

      This puzzle can be solved using the [[ grouping ]] functionality in the enigma.py library (originally written to solve Teaser 2816, and a number of previous Teasers).

      This Python program runs in 87ms.

      Run: [ @repl.it ]

      from enigma import grouping
      
      # categories for this puzzle
      presenters = ( 'MARR', 'NEIL', 'NEWMAN', 'PARKINSON', 'PAXMAN', 'POPPLEWELL', 'ROBINSON', 'SNOW' )
      labs = ( 'ABBOTT', 'ABRAHAMS', 'CHAKRABARTI', 'CORBYN', 'EAGLE', 'LEWIS', 'STARMER', 'WATSON' )
      cons = ( 'BRADLEY', 'GRAYLING', 'GREEN', 'HAMMOND', 'LEADSOM', 'LIDINGTON', 'MCLOUGHLIN', 'MUNDELL' )
      
      # match the categories into groups such that each pair in each group shares exactly 2 letters
      grouping.solve([presenters, labs, cons], grouping.share_letters(2))
      

      Solution: LEADSOM, GRAYLING, HAMMOND, LIDINGTON, GREEN, BRADLEY, MUNDELL, MCLOUGHLIN.

      The groupings are:

      MARR, CHAKRABARTI, HAMMOND
      NEIL, EAGLE, GREEN
      NEWMAN, ABRAHAMS, GRAYLING
      PARKINSON, LEWIS, BRADLEY
      PAXMAN, STARMER, MUNDELL
      POPPLEWELL, WATSON, MCLOUGHLIN
      ROBINSON, ABBOTT, LEADSOM
      SNOW, CORBYN, LIDINGTON

      Like

  • Unknown's avatar

    Jim Randell 8:36 am on 31 July 2019 Permalink | Reply
    Tags:   

    Teaser 2895: Spanish and Latin 

    From The Sunday Times, 18th March 2018 [link] [link]

    I have ten cards. For each card there is a number in Spanish on one side and a number in Latin on the other.

    The Spanish numbers are CINCO, CUATRO, DIEZ, DOS, NUEVE, OCHO, SEIS, SIETE, TRES and UNO. The Latin numbers are DECEM, DUO, NOVEM, OCTO, QUATTUOR, QUINQUE, SEPTEM, SEX, TRES and UNUS. For each of the ten pairs of numbers, there are two letters in common. If I told you on how many cards the two numbers were consecutive, you should be able to work out all ten pairings. The Spanish numbers are written alphabetically.

    What is the corresponding order for the Latin numbers?

    [teaser2895]

     
    • Jim Randell's avatar

      Jim Randell 8:37 am on 31 July 2019 Permalink | Reply

      This Python program make use of a couple of the routines from the enigma.py library to solve this puzzle in a few lines of code. It runs in 80ms.

      Run: [ @repl.it ]

      from enigma import (grouping, filter_unique, unpack)
      
      # the words and corresponding numbers
      spanish = dict(CINCO=5, CUATRO=4, DIEZ=10, DOS=2, NUEVE=9, OCHO=8, SEIS=6, SIETE=7, TRES=3, UNO=1)
      latin = dict(DECEM=10, DUO=2, NOVEM=9, OCTO=8, QUATTUOR=4, QUINQUE=5, SEPTEM=7, SEX=6, TRES=3, UNUS=1)
      
      # find solutions unique by the number of pairs with consecutive values
      (ss, _) = filter_unique(
        # find all solution groups
        grouping.groups([list(spanish.keys()), list(latin.keys())], grouping.share_letters(2)),
        # count pairs with numerically consecutive values
        lambda s: sum(abs(spanish[es] - latin[la]) == 1 for (es, la) in s),
        tuple
      )
      
      # output solutions
      for s in ss:
        # sort the solution by the spanish numbers alphabetically
        grouping.output_groups(sorted(s, key=unpack(lambda es, la: es)))
      

      Solution: The corresponding Latin numbers are: NOVEM, TRES, DECEM, DUO, UNUS, OCTO, SEPTEM, QUINQUE, SEX, QUATTUOR.

      So the pairs are:

      CINCO, NOVEM (NO)
      CUATRO, TRES (RT) [consecutive]
      DIEZ, DECEM (DE)
      DOS, DUO (DO)
      NUEVE, UNUS (NU)
      OCHO, OCTO (CO)
      SEIS, SEPTEM (ES) [consecutive]
      SIETE, QUINQUE (EI)
      TRES, SEX (ES)
      UNO, QUATTUOR (OU)

      There are also two sets of cards that have 4 cards with consecutive numbers:

      CINCO, NOVEM (NO)
      CUATRO, TRES (RT) [consecutive]
      DIEZ, DECEM (DE)
      DOS, DUO (DO)
      NUEVE, UNUS (NU)
      OCHO, OCTO (CO)
      SEIS, SEPTEM (ES) [consecutive]
      SIETE, SEX (ES) [consecutive]
      TRES, QUATTUOR (RT) [consecutive]
      UNO, QUINQUE (NU)

      CINCO, QUINQUE (IN)
      CUATRO, TRES (RT) [consecutive]
      DIEZ, DECEM (DE)
      DOS, DUO (DO)
      NUEVE, UNUS (NU)
      OCHO, OCTO (CO)
      SEIS, SEPTEM (ES) [consecutive]
      SIETE, SEX (ES) [consecutive]
      TRES, QUATTUOR (RT) [consecutive]
      UNO, NOVEM (NO)

      And these are the only sets of cards that can be made.

      Like

  • Unknown's avatar

    Jim Randell 11:58 am on 30 July 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 490: Equal marks 

    From The Sunday Times, 18th October 1970 [link]

    Tom, Dick and Harry took a test in each of five subjects. Each test was marked out of a total of 40. In grand total each boy obtained half-marks, and no boy scored the same mark in two or more tests.

    Each boy’s best and third-best marking totalled one-fifth more than the total of his best and fourth-best, and each boy’s worst mark exceeded 10.

    The aggregate of the three boys’ marks in each of four subjects was the same, the highest single mark going to Tom. The second-highest was scored by Dick in the remaining subject.

    How many marks did Harry get in that subject?

    This puzzle is included in the book Sunday Times Brain Teasers (1974).

    [teaser490]

     
    • Jim Randell's avatar

      Jim Randell 11:58 am on 30 July 2019 Permalink | Reply

      This Python program runs in 410ms.

      Run: [ @repl.it ]

      from enigma import (irange, subsets, multiset, cproduct, printf)
      
      # find sets of 5 scores that sum to 100
      ss = list()
      for s in subsets(irange(11, 40), size=5, select='C'):
        # total score is 100
        if not (sum(s) == 100): continue
        # (best + 3rd best) = (6/5)(best + 4th best)
        if not (5 * (s[-1] + s[-3]) == 6 * (s[-1] + s[-4])): continue
        ss.append(s)
      
      # choose scores for the 3 students
      for (s1, x2, x3) in subsets(ss, size=3, select='C'):
        # choose orders for s2 and s3
        permute = lambda x: subsets(x, size=len, select='P')
        for (s2, s3) in cproduct([permute(x2), permute(x3)]):
          scores = (s1, s2, s3)
          # accumulate the total scores in each test
          ts = list(sum(s) for s in zip(*scores))
          m = multiset(ts)
          if not (sorted(m.values()) == [1, 4]): continue
          # find the highest and second highest marks
          ms = sorted(s1 + s2 + s3)
          (m1, m2, m3) = (ms[-1], ms[-2], ms[-3])
          if not (m1 > m2 > m3): continue
      
          # determine which is Tom, Dick
          T = D = H = -1
          for (i, s) in enumerate(scores):
            if m1 in s: T = i
            if m2 in s: D = i
          # Harry is the other one
          H = 3 - (T + D)
          if not (sorted([T, D, H]) == [0, 1, 2]): continue
          # check the 2nd highest score is in the test with the unique total
          d = scores[D].index(m2)
          if not (m[ts[d]] == 1): continue
      
          # output solution
          printf("T = {T}", T=scores[T])
          printf("D = {D}", D=scores[D])
          printf("H = {H}", H=scores[H])
          printf("+ = {ts}")
          printf("answer = {x}", x=scores[H][d])
          printf()
      

      Solution: 15 marks.

      The scores in each test are shown below:

      The highest single mark is highlighted red. The second highest mark is orange. And the answer (Harry’s mark in the same subject as the second highest mark) is yellow.

      There is a “near miss” scenario, where the scores are as follows:

      T = (11, 12, 21, 23, 33)
      D = (25, 26, 22, 14, 13)
      H = (25, 23, 13, 24, 15)

      The sum of the scores for each test are 61, apart from the third one where they are 56.

      Tom has the highest score (33) and Dick has the second highest (26), but the score of 26 is not in the subject with a sum of 56.

      The scores are a rearrangement of the scores in the actual solution because there are only three possible sequences of 5 scores between 11 and 40 that sum to 100 and have best and third best scores summing to one fifth more than the sum of the best and fourth best scores.

      Like

  • Unknown's avatar

    Jim Randell 12:40 pm on 28 July 2019 Permalink | Reply
    Tags:   

    Brainteaser 1633: New Year’s resolution 

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

    My New Year’s resolution is that this year I will remember my father’s birthday. To remind me I devised this formula: if I take the factorial of the number on the calendar representing the month in which he was born (1 for January to 12 for December) and multiply this by the square of the day of the month of his birth (1 to 31), the four digit result is the year when he was born.

    [Note, for example, that “6 factorial” is 6×5×4×3×2×1 = 720.]

    I then realised that this is no use for determining his birthday as even my own date of birth fits the formula.

    Can you tell me what is my father’s date of birth?

    This puzzle is included in the book Brainteasers (2002) under the title of “Birthday resolution”. The text was changed slightly, but the puzzle remains the same.

    [teaser1633]

     
    • Jim Randell's avatar

      Jim Randell 12:41 pm on 28 July 2019 Permalink | Reply

      This Python program finds day/month/year combinations that satisfy the formula, where the year is in the range [1850, 1993].

      It runs in 84ms.

      Run: [ @repl.it ]

      from enigma import irange, factorial, printf
      
      # number of possible days in each month
      days = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
      
      # consider day, month, formula values
      for (m, dm) in enumerate(days, start=1):
        f = factorial(m)
        for d in irange(1, dm):
          y = f * d * d
          if y < 1850: continue
          if y > 1993: break
          printf("d={d} m={m} -> y={y}")
      

      Solution: The father’s birthdate is 4th May 1920.

      There are 3 day/month combinations that give a reasonable value for the year.

      These are:

      day=18, month=3 → year=1944
      day=9, month=4 → year=1944
      day=4, month=5 → year=1920

      So the setters birthdate must be one of the 1944 dates (18th March 1944, 9th April 1944) and his fathers birthdate must be 4th May 1920.

      But it seems to me that the setter would probably be able to remember that his father was not born in 1944, so he can use the formula to determine his fathers birthday.

      The setter is obviously not a fan of Star Wars, otherwise he would find it easy to remember his father’s birthday.

      Like

  • Unknown's avatar

    Jim Randell 6:52 pm on 26 July 2019 Permalink | Reply
    Tags:   

    Teaser 2966: On track 

    From The Sunday Times, 28th July 2019 [link] [link]

    Sarah and Jenny are runners who train together on a circular track, and Sarah can run up to 20 per cent faster than Jenny. They both run at a constant speed, with Sarah running an exact percentage faster than Jenny. To reduce competition they start at the same point, but run round the track in different directions, with Sarah running clockwise.

    On one day they passed each other for the seventh time at a point which is an exact number of degrees clockwise from the start. Sarah immediately changed her pace, again an exact percentage faster then Jenny. After a few [additional] passes both runners reached the exit, at a point on the track an exact number of degrees clockwise from the start, at the same time.

    How fast, relative to Jenny, did Sarah run the final section?

    [teaser2966]

     
    • Jim Randell's avatar

      Jim Randell 8:20 pm on 26 July 2019 Permalink | Reply

      If S is going p percent faster than J, she is going at rate of (1 + p/100) times as fast as J.

      If they meet after J has travelled through d degrees of the circle, then S has travelled through d(1 + p/100) degrees of the circle, and together these must make up the full circle. So:

      d + d(1 + p/100) = 360
      d = 36000 / (200 + p)

      And if they meet at an exact number of degrees after k passings:

      kd = 36000k / (200 + p)

      must be an integer.

      This Python program finds possible percentage increases for S where she meets J at an exact number of degrees for up to 7 passings. It runs in 81ms.

      Run: [ @repl.it ]

      from enigma import (irange, cproduct, printf)
      
      # suppose they meet after k passings and S runs p percent faster than J
      for (k, p) in cproduct([irange(1, 7), irange(1, 20)]):
        # do they meet at an exact number of degrees?
        if (36000 * k) % (200 + p) == 0:
          printf("k={k} p=+{p}% [{s}]", s=('initial' if k == 7 else 'final'))
      

      Solution: Sarah ran the final section 16% faster than Jenny.

      For the first section Sarah was running 10% faster than Jenny.

      And the final section lasted for some multiple of 3 crossings. Probably 3 or 6, as there are only “a few” additional passes, but we don’t need to know that to get the answer to the puzzle.

      Further solutions appear if we allow higher values of k. For example, at k = 11 we get a solution where S is 20% faster than J, but from the wording of the puzzle it seems likely that the number of passings in the final section is fewer than 7, so we get a unique answer.

      Like

    • John Crabtree's avatar

      John Crabtree 2:19 am on 27 July 2019 Permalink | Reply

      You could calculate the minimum k for any p. In Excel format:
      k = +(200 + p) / GCD(36000, 200 + p)
      Then if k < 8, print k and p

      I am not a Python programmer, but understand that Python does have a gcd function.

      Like

      • Jim Randell's avatar

        Jim Randell 8:07 am on 27 July 2019 Permalink | Reply

        @John: Python does indeed have [[ gcd() ]].

        We can generate the list of first k for each p, and then output them in k order:

        from enigma import (irange, gcd, div, printf)
        
        # find first k for each p
        kps = ((div(200 + p, gcd(36000, 200 + p)), p) for p in irange(1, 20))
        
        # output (k, p) pairs, ordered by k
        for (k, p) in sorted(kps):
          printf("k={k} p=+{p}%")
        

        Like

      • Jim Randell's avatar

        Jim Randell 2:19 pm on 1 August 2019 Permalink | Reply

        Here’s a program that uses a similar approach to generate the solutions in a given range (in this case p = 1..20, k = 1..7, like my first program):

        from enigma import (irange, fraction, printf)
        
        # consider values for p
        for p in irange(1, 20):
          (a, b) = fraction(36000, 200 + p)
          # consider values for k
          for k in irange(b, 7, step=b):
            printf("k={k} p=+{p}% [{s}]", s=('initial' if k == 7 else 'final'))
        

        Like

    • Robert Brown's avatar

      Robert Brown 11:35 am on 1 August 2019 Permalink | Reply

      For each p value, you found equivalent k value. I reversed this, leading to an easy manual solution.
      At k passings, J has travelled 180k+x and S has travelled 180k–x giving J/S = (180k+x)/(180k–x).
      (x = integer, k is prime to avoid duplication). This gives the following rapid 3 line solution:

      k = 7 J/S = 1260+x / 1260-x By inspection, x = 60 giving J/S = 1.10
      k = 2 J/S = 360+x / 360-x By inspection, x = 60 giving J/S = 1.40 (too large)
      k = 3 J/S = 540+x / 540-x By inspection, x = 40 giving J/S = 1.16 (the answer)

      Like

  • Unknown's avatar

    Jim Randell 11:30 am on 26 July 2019 Permalink | Reply
    Tags:   

    Teaser 2816: Chat shows 

    From The Sunday Times, 11th September 2016 [link] [link]

    Each day next week there will be a chat show on television and in each show the host will appear with an actress and an actor.

    The hosts will be (in order) Dimbleby, Evans, Mack, Marr, NortonPeschardt and Ross. In no particular order, the actresses will be Arterton, Blunt, Carter, Jones, Knightley, Margolyes and Watson. Again in no particular order, the actors will be Bean, Caine, Cleese, Craig, De Niro, Neeson and Oldman.

    For any two people in the same show there are just two different letters of the alphabet that occur (perhaps more than once) in both their surnames.

    List the actresses in the order in which they appear.

    [teaser2816]

     
    • Jim Randell's avatar

      Jim Randell 11:30 am on 26 July 2019 Permalink | Reply

      This kind of grouping problem is a favourite of Graham Smithers. By the time this puzzle was published in September 2016 I had already solved several of this type of problem, writing a custom Python program for each one.

      So for this puzzle I wrote some generic code that could be used to solve this puzzle and the other similar puzzles I had encountered.

      The code is available in the enigma.py library by importing the [[ grouping ]] namespace.

      This Python program runs in 42ms.

      from enigma import grouping
      
      # categories for this puzzle
      hosts = ["Dimbleby", "Evans", "Mack", "Marr", "Norton", "Peschardt", "Ross"]
      actresses = ["Arterton", "Blunt", "Carter", "Jones", "Knightley", "Margolyes", "Watson"]
      actors = ["Bean", "Caine", "Cleese", "Craig", "De Niro", "Neeson", "Oldman"]
      
      # match the categories into groups such that each pair in each group shares exactly two letters
      grouping.solve([hosts, actresses, actors], grouping.share_letters(2))
      

      Solution: The order of the actresses is: Blunt, Carter, Margolyes, Arterton, Knightley, Jones, Watson.

      The full solution is:

      Dimbleby, Blunt, Bean
      Evans, Carter, Cleese
      Mack, Margolyes, Caine
      Marr, Arterton, Craig
      Norton, Knightley, Neeson
      Peschardt, Jones, Oldman
      Ross, Watson, De Niro

      Like

  • Unknown's avatar

    Jim Randell 12:15 pm on 25 July 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 489: [Railway journeys] 

    From The Sunday Times, 11th October 1970 [link]

    The All-Go railway runs from Alby to Goby over 20 miles away. Let us say it goes from A to G, and on the way you stop at B, C, D, E and F in that order.

    All the distances between stops are different from one another and all are less than 8 miles, yet it is possible to make journeys of 1 mile, 2 miles, 3 miles and so on up to 18 miles by picking the right stations.

    The distance from A to B is less than the distance from F to G.

    How many miles from A are B, C, D, E, F and G?

    This puzzle was originally published with no title.

    [teaser489]

     
    • Jim Randell's avatar

      Jim Randell 12:15 pm on 25 July 2019 Permalink | Reply

      (See also: Teaser 1986).

      This Python program runs in 220ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, tuples, csum, join, sprintf as f, printf)
      
      # required distances
      rs = set(irange(1, 18))
      
      # choose the lengths of the 6 segments, AB BC CD DE EF FG
      for ss in subsets(irange(1, 7), size=6, select='P'):
        (AB, BC, CD, DE, EF, FG) = ss
        if not (AB < FG): continue
      
        # collect together adjacent segments from 1 to 6
        ds = set(sum(t) for k in irange(1, 6) for t in tuples(ss, k))
        if not rs.issubset(ds): continue
      
        printf("{cs} [ss={ss}]", cs=join((f("A{x}={d}") for (x, d) in zip("BCDEFG", csum(ss))), sep=" "))
      

      Solution: The distances (in miles) are: A→B=2, A→C=7, A→D=14, A→E=15, A→F=18, A→G=24.

      The distances between stations are:

      [A] 2 [B] 5 [C] 7 [D] 1 [E] 3 [F] 6 [G]

      So as well as being able to make all the distances from 1 to 18, we can also make distances of 22 (B to G) and 24 (A to G).

      Like

  • Unknown's avatar

    Jim Randell 5:20 pm on 24 July 2019 Permalink | Reply
    Tags:   

    Teaser 2896: Light angles 

    From The Sunday Times, 25th March 2018 [link] [link]

    The hotel’s night clerk was always glancing at the novelty 12-hour clock facing her. Its hands’ rotation axis was exactly midway across and between three-quarters and four-fifths of the way up the four-metre high rectangular wall. Light rays directed out along the continuous-motion minute and hour hands made pencil beams across the wall. Twice during her 12-hour shift she saw the beams hit the top corners of the wall simultaneously, and twice she saw the beams hit the bottom corners simultaneously.

    How wide was the wall to the nearest metre?

    [teaser2896]

     
    • Jim Randell's avatar

      Jim Randell 5:21 pm on 24 July 2019 Permalink | Reply

      I thought this was a neat puzzle, but there are only a few values to consider.

      For any given hour, there will be a (fractional) minute when the hour “hand” and the minute “hand” are the at the same angle from vertical, but on opposite sides, and so mirror each other about the vertical axis.

      If we consider the times between 12 o’clock and 6 o’clock, at some time before 3 o’clock the two “hands” will illuminate the top corners of the wall, and as sometime after 3 o’clock the two “hands” will illuminate the bottom corners of the wall. By choosing values for the “top” hours and “bottom” hours, we can determine the corresponding minutes, angles of the “hands” and hence the height of the origin of the hands up the wall. If it’s between 3.0m and 3.2m we have a solution.

      This Python program runs in 68ms.

      Run: [ @repl.it ]

      from math import tan
      from itertools import product
      from enigma import (irange, fdiv, two_pi, printf)
      
      # given an hour, compute the corresponding minute and tangent
      def hmt(h):
        m = fdiv((12 - h) * 60, 13) # minute
        a = fdiv(h, 12) + fdiv(m, 720) # angle (fraction of a circle)
        t = tan(two_pi * a) # tangent of a
        return (h, m, t)
      
      # consider the times when the hour and minute beams mirror each other
      # about the vertical axis
      
      # calculate (h, m, t) for hours from 0 to 5
      hmts = list(hmt(h) for h in irange(0, 5))
      
      # the hour for the top right corner is before 3
      # the hour for the bottom right corner is after 3
      for ((h1, m1, t1), (h2, m2, t2)) in product(hmts[:3], hmts[3:]):
        # calculate the height of the hands above the floor
        y = fdiv(4 * t1, t1 - t2)
        # check it is in range
        if 3.0 <= y <= 3.2:
          # calculate the width of the wall
          x = y * -t2
          w = 2 * x
          # output solution
          printf("width={w:.03f}, x={x:.03f} y={y:.03f} [TR @ {h1}:{m1:05.2f}, BR @ {h2}:{m2:05.2f}]")
      

      Solution: The wall is approximately 16 metres wide.

      The actual width of the wall (to the nearest millimetre) is 15.979m.

      The height of the origin of the hands is 3.030m.

      Here is a diagram that shows the wall, ruled with lines at heights of 1m, 2m, 3m and 3.2m.

      The upper lines would be illuminated at 2:46 and 9:14.

      The lower lines would be illuminated at 3:42 and 8:18.

      So it is possible to observe the situation between 2:46 and 9:14, which is only 6 hours 28 minutes.

      Like

      • Jim Randell's avatar

        Jim Randell 9:29 am on 25 July 2019 Permalink | Reply

        Here’s a chart with the choices for the times (upward times across the top, downward times along the side), and the calculated width of the wall and height of the hands.

        We find the solution (highlighted) when h is between 3.0m and 3.2m.

        Like

  • Unknown's avatar

    Jim Randell 9:18 am on 23 July 2019 Permalink | Reply
    Tags:   

    Brainteaser 1631: Ali Baba’s boxes 

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

    The royal jewellers Fabulé make standard-sized golden cubic boxes which are diamond-encrusted on the outside. They make these from large sheets of gold which have been encrusted with diamonds on one side and they then cut out the required shapes to make the boxes.

    For example, the 3-by-4 cruciform shown on the left below provides a shape which folds up into a cubic box, but the strip on the right does not.

    Recently thieves broke into the Fabulé workshops and stole various cut-out pieces of the diamond-encrusted sheeting. They did not realise that in fact they had taken the mis-cuts: all the pieces that consisted of six squares but none would actually fold into a box. And their haul consisted of one piece of each possible faulty shape.

    How many pieces did they steal?

    This puzzle is included in the book Brainteasers (2002). The text was changed slightly, but the substance of the puzzle remains the same.

    [teaser1631]

     
    • Jim Randell's avatar

      Jim Randell 9:20 am on 23 July 2019 Permalink | Reply

      It seems like a bit of a wasteful manufacturing process, but it does mean that the shapes cannot be turned over. (Although card with different coloured sides would have sufficed).

      I enjoyed programming a constructive solution for this puzzle.

      This Python program generates possible shapes that consist of 6 connected squares, and determines how many different shapes there are when translation and rotation are allowed (but not reflection). It then takes those shapes and attempts to wrap each one around a cube to find out which of them represent the net of a cube.

      It runs in 982ms

      Run: [ @repl.it ]

      from enigma import Accumulator, ordered, uniq, update, first, join, printf
      
      # slide shape against x=0, y=0 axes
      def slide(shape):
        min_x = min(x for (x, y) in shape)
        min_y = min(y for (x, y) in shape)
        return tuple((x - min_x, y - min_y) for (x, y) in shape)
      
      # rotate shape through 90 degrees
      def rotate(shape):
        return tuple((y, -(x + 1)) for (x, y) in shape)
      
      # find a canonical representation of a shape
      def canonical(shape):
        r = Accumulator(fn=min)
        for i in (0, 1, 2, 3):
          # slide shape against the axes
          shape = slide(shape)
          r.accumulate(ordered(*shape))
          # rotate shape
          if i < 3: shape = rotate(shape)
        # return the minimim value
        return r.value
      
      # find squares adjacent to p
      def adj(p):
        (x, y) = p
        return ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1))
      
      # generate shapes with k additional squares
      def generate(k, shape):
        if k == 0:
          yield shape
        else:
          # choose a square to extend
          for p in shape:
            for q in adj(p):
              if q not in shape:
                yield from generate(k - 1, shape + [q])
      
      # roll a cube to the ajacent squares
      def roll(p, fs):
        ((x, y), (U, D, F, B, L, R)) = (p, fs)
        return (
           ((x - 1, y), (R, L, F, B, U, D)), # L
           ((x + 1, y), (L, R, F, B, D, U)), # R
           ((x, y - 1), (B, F, U, D, L, R)), # F
           ((x, y + 1), (F, B, D, U, L, R)), # B
        )
      
      # wrap a cube with shape
      # shape = the shape we are trying to wrap
      # ss = remaining unwrapped squares
      # m = map from square -> face
      # cube = current cube orientation (U, D, F, B, L, R)
      def wrap(shape, m):
        # are we done?
        n = len(shape)
        if len(m.keys()) == n:
          d = dict((k, v[1]) for (k, v) in m.items())
          if len(set(d.values())) == n:
            yield d
        else:
          # roll the cube
          for (p, fs) in m.items():
            for (q, gs) in roll(p, fs):
              if q in shape and q not in m:
                yield from wrap(shape, update(m, [(q, gs)]))
      
      # find the number of shapes made from 6 squares
      (t, n) = (0, 0)
      for s in uniq(canonical(s) for s in generate(5, [(0, 0)])):
        t += 1
        # can we form the shape into a cube?
        r = first(wrap(s, { s[0]: "UDFBLR" }))
        if r:
          n += 1
          printf("[{t}: {s} -> {r}]", r=join(r[0][p] for p in s))
        else:
          printf("[{t}: {s}]")
      
      printf("{n} of {t} shapes form cubes")
      

      Solution: There are 40 shapes that cannot be folded into a cube.

      We can make 60 different shapes out of 6 joined squares (where reflection is not allowed). (See OEIS A000988 [ @oeis ]).

      And there are 11 shapes that can be folded to make a cube (if reflection is allowed):

      The two of these on the left are achiral (i.e. their mirror image can be formed by rotating the piece), leaving 9 that are chiral (i.e. the mirror image cannot be formed by rotation of the piece). So we need to make mirror images of the 9 chiral shapes to give 20 shapes altogether that can be folded into a cube (where reflection is not allowed).

      The remaining 40 shapes cannot be folded into a cube.

      See: [ @wikipedia ], which tells us there are 60 one-sided hexonimoes and gives the 11 nets of the cube. Which is enough information to determine the answer.


      The program is constructive so can easily be augmented to plot the 60 shapes, and colour in those which can be folded into cubes. I used my simple plotting library [ @github ].

      Run: [ @repl.it ]

      The output looks like this:

      The colours show how the shape can be folded to give the colouring of a standard Rubik’s Cube.

      Like

  • Unknown's avatar

    Jim Randell 8:51 am on 22 July 2019 Permalink | Reply
    Tags:   

    Teaser 2897: Easter egg 

    From The Sunday Times, 1st April 2018 [link] [link]

    “Granddad, I’ve got a great big Easter egg in a square box!” My grandson Liam inspired this Teaser in which I have used non-zero digits then consistently replaced them with letters, using different letters for different digits.

    In this way, I have:

    EASTER
    (EGG)²
    BOX

    Two of the numbers add to make the third, and if I told you which number was largest you should be able to solve this Teaser.

    Find the value of BOX.

    [teaser2897]

     
    • Jim Randell's avatar

      Jim Randell 8:52 am on 22 July 2019 Permalink | Reply

      EASTER has 6 digits, (EGG)² has 5 or 6 digits, but whichever is larger BOX is the difference between them.

      The alphametic expression:

      abs(EASTER − (EGG)²) = BOX

      only has one solution (when we don’t use the digit 0):

      % python -m enigma SubstitutedExpression --digits="1-9" "abs(EASTER - sq(EGG)) = BOX"
      (abs(EASTER - sq(EGG)) = BOX)
      (abs(954891 - sq(977)) = 362) / A=5 B=3 E=9 G=7 O=6 R=1 S=4 T=8 X=2
      [1 solution]
      

      So this gives us our solution (BOX=362), even without needing to know which of EASTER or (EGG)² is larger. (In fact EASTER > (EGG)²).

      But here is a full solution, that solves the two possible sums as alphametic problems, and looks for situations where there is a single solution for BOX. It runs in 122ms.

      from enigma import (SubstitutedExpression, irange, printf)
      
      # look for solutions to the alphametic sum that give a unique value for ans
      def check(expr, answer):
        p = SubstitutedExpression(expr, digits=irange(1, 9), answer=answer, verbose=0)
        r = set(x for (_, x) in p.solve())
        if len(r) == 1:
          x = r.pop()
          printf("{answer}={x} [{expr}]")
      
      # there are two possible largest values
      check("sq(EGG) + BOX = EASTER", "BOX") # if EASTER is largest
      check("sq(EGG) - BOX = EASTER", "BOX") # if EGG^2 is largest
      

      Solution: BOX = 362.

      In fact this program runs faster than the single alphametic expression above, as each alphametic expression only requires considering the possibilities for 5 digits, rather than 6 with the single sum.

      If (non-leading) 0 digits are allowed, then there are three further solutions to the (EGG)² + BOX = EASTER sum (but still none for (EGG)² − BOX = EASTER).

      Like

    • GeoffR's avatar

      GeoffR 8:52 am on 30 July 2019 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:E;   var 1..9:A;   var 1..9:S;   var 1..9:T;
      var 1..9:R;   var 1..9:G;   var 1..9:B;   var 1..9:O;
      var 1..9:X;
      
      constraint all_different ([E, A, S, T, R, G, B, O, X]);
      
      var 100..999:EGG = 100*E + 11*G;
      var 100..999:BOX = 100*B + 10*O + X;
      var 100000..999999: EASTER = 100000*E + 10000*A + 1000*S + 100*T + 10*E + R;
      
      % Two of the numbers add to make the third
      constraint (EASTER - EGG*EGG == BOX) \/ (EGG*EGG - EASTER == BOX);
      
      solve satisfy;
      
      output [ "EASTER = " ++ show(EASTER) 
      ++ ", EGG = " ++ show(EGG)  ++ ", BOX = " ++ show(BOX) ];
      
      % EASTER = 954891, EGG = 977, BOX = 362
      % ----------
      % Finished in 232msec
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:38 am on 21 July 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 488: [Truth and lies] 

    From The Sunday Times, 4th October 1970 [link]

    The villagers of Titterton always tell the truth; those from Lilling always lie; those from Trulham alternate between truth and lie, starting with a truth; those from Litford also alternate, but start with a lie.

    I recently met four men, one from each village:

    Alf: “I’m from Trulham.”
    Bert: “Alf’s from Lilling.”
    Charlie: “Bert’s from Lilling.”
    Dan: “Charlie’s from Lilling.”
    Alf: “Dan’s from Titterton.”

    As I was sorting this out, in came three more men, each from one of other of the villages:

    Edgar: “I’m from Dan’s village.”
    Frank: “There are more chaps here from my village than from any other.”
    George: “Frank’s lying. I’m from Bert’s village.”

    Name the men from Titterton, Trulham and Litford. (I should perhaps add that I live in Totworth and tell the truth invariably).

    This puzzle was originally published with no title.

    [teaser488]

     
    • Jim Randell's avatar

      Jim Randell 8:40 am on 21 July 2019 Permalink | Reply

      I assumed the puzzle text itself was a completely true account, and that the “alternators” were at the beginning of their cycle with their first quoted statements.

      This Python program runs in 85ms.

      Run: [ @repl.it ]

      from enigma import (subsets, printf)
      
      # bT = always true (= Titterton)
      def bT(ss):
        return all(ss)
      
      # bF = always false (= Lilling)
      def bF(ss):
        return not any(ss)
      
      # bTF = alternate, T then F (= Trulham)
      def bTF(ss):
        return all(ss[0::2]) and not any(ss[1::2])
      
      # bFT = alternate, F then T (= Litford)
      def bFT(ss):
        return all(ss[1::2]) and not any(ss[0::2])
      
      # possible behaviours
      fns = (bT, bF, bTF, bFT)
      
      # solve the puzzle
      def solve():
        # choose behaviours for A, B, C, D
        for (A, B, C, D) in subsets(fns, size=len, select='P'):
      
          # A: "I am from Trulham", "D is from Titterton"
          if not A([A is bTF, D is bT]): continue
      
          # B: "A is from Lilling"
          if not B([A is bF]): continue
      
          # C: "B is from Lilling"
          if not C([B is bF]): continue
      
          # D: "C is from Lilling"
          if not D([C is bF]): continue
      
          # choose behaviours for E, F, G
          for (E, F, G) in subsets(fns, size=3, select='M'):
      
            # E: "I am from D's village"
            if not E([E == D]): continue
      
            # F: "There are more from my village than any other"
            s = (A, B, C, D, E, F, G)
            n = dict((k, s.count(k)) for k in s)
            f = all(n[F] > n[x] for x in s if x != F)
            if not F([f]): continue
      
            # G: "F is lying" "I am from B's village"
            if not G([(not f), G == B]): continue
      
            yield s
      
      for s in solve():
        (A, B, C, D, E, F, G) = (b.__name__ for b in s)
        printf("A={A} B={B} C={C} D={D} E={E} F={F} G={G}")
      

      Solution: The affiliations are:

      Titterton: Charlie
      Lilling: Bert, Edgar
      Trulham: Alf, George
      Litford: Dan, Frank

      Like

  • Unknown's avatar

    Jim Randell 6:21 pm on 19 July 2019 Permalink | Reply
    Tags:   

    Teaser 2965: Knock, knock! 

    From The Sunday Times, 21st July 2019 [link] [link]

    Last year a two-figure number of teams entered our football competition. With that number, it was impossible to have a straightforward knockout competition (where half the teams are knocked out in each round), so we divided the teams into equal-sized groups, each pair of teams within a group played each other once, and the overall winner from each group went forward to the second stage consisting of a knockout competition. Unfortunately our local team was knocked out in the quarter-finals of that stage.

    This year a higher number of teams entered (but not twice as many). Again a straightforward knockout competition was impossible so we copied last year’s model but with smaller groups and more of them. By coincidence the total number of games played this year equalled the number played last year.

    How many teams entered last year and how many this year?

    [teaser2965]

     
    • Jim Randell's avatar

      Jim Randell 7:01 pm on 19 July 2019 Permalink | Reply

      In order to have a knockout tournament (without using byes) the number of teams needs to be a power of 2 (half the teams are knocked out at each stage).

      So the numbers of teams we are dealing with cannot be powers of two. But we can divide the numbers into groups where the winners of the groups can have a knockout tournament, so the number of groups must be a power of 2. Hence the total number of teams has to have a divisor that is a power of 2.

      Within a group of size m, the number of matches where each team plays each other team is T(m − 1).

      This Python program looks for 2-digit values for the number of teams last year, works out the total number of matches played in the tournament, and then finds a scenario for this year which has the same total number of matches. It runs in 91ms.

      Run: [ @replit ]

      from enigma import (irange, div, tri, bit_permutations as powers2, printf)
      
      # check a positive integer is a power of 2
      is_power2 = lambda n: not (n & (n - 1))
      
      # number of teams last year (a 2-digit number)
      for n in irange(24, 99, step=8):
        # skip numbers that are powers of 2
        if is_power2(n): continue
      
        # divide n into k groups of m (k is a power of 2)
        # a team was knocked out in the quarter finals, so k > 7
        for k in powers2(8):
          m = div(n, k)
          if m is None: break
          # calculate the total number of games played
          t = k * tri(m - 1) + (k - 1)
      
          # now look for possible values with the same total, but with smaller groups
          for m2 in irange(3, m - 1):
            # and m2 must not be a power of 2
            if is_power2(m2): continue
            # calculate the number of groups
            k2 = div(t + 1, tri(m2 - 1) + 1)
            if k2 is None: continue
            # there must be more groups
            if not (k2 > k): continue
            # k2 must be a power of 2
            if not is_power2(k2): continue
            # the total number of teams this year
            n2 = k2 * m2
            # and n2 greater than n, but not twice n
            if not (n2 > n and n2 != n * 2): continue
      
            printf("t={t}: last year = {n} teams ({k} groups of {m}), this year = {n2} teams ({k2} groups of {m2})")
      

      Solution: 56 teams entered last year. 80 teams entered this year.

      Last year the 56 teams were formed into 8 groups of 7. Each group would play 21 matches, and then the 8 winners of the groups would play 4 quarter-finals, 2 semi-finals and 1 final to give 175 matches in total.

      This year the 80 teams were formed into 16 groups of 5. Each group would play 10 matches, and then the 16 winners of the groups would play 8 eighth-finals, 4 quarter-finals, 2 semi-finals and 1 final to also give 175 matches in total.


      As a team was knocked out in the quarter-finals last year, the number of groups must have been at least 8, and we can consider multiples of 8 (that are not exact powers of 2). So there are only 8 candidate numbers for the number of teams last year: 24, 40, 48, 56, 72, 80, 88, 96.

      There are two “near miss” scenarios:

      48 teams last year (8 groups of 6), and 96 teams this year (32 groups of 3). Both have 127 matches.
      96 teams last year (16 groups of 6), and 192 teams this year (64 groups of 3). Both have 255 matches.

      But both are eliminated by the requirement that the number of teams this year is not twice the number of teams last year.

      When I initially read the puzzle I took “not twice as many” to mean “fewer than twice as many”, but in my program I relaxed this condition to “not exactly twice as many”, and the single solution remains.

      There is also the question of whether, in the knockout tournament, there is a match to decide 3rd and 4th places. It doesn’t matter if there is or not, as long as both tournaments use the same format. If there is a match for the semi-final losers then the total numbers of matches will be increased by 1.

      Like

    • Jim Randell's avatar

      Jim Randell 12:52 pm on 22 July 2019 Permalink | Reply

      Here is a MiniZinc model for the puzzle. It executes in 66ms.

      %#! minizinc -a
      
      % powers of 2
      set of int: pow2 = { pow(2, i) | i in 1..9 };
      
      
      % last year: n teams, k groups of m
      var 10..99: n; % n is a 2 digit number
      var 3..33: k;
      var 5..99: m;
      
      % n can be divided into k groups of m;
      % n is not a power of 2
      % k is a power of 2
      % m is not a power of 2
      constraint k * m = n;
      constraint not(n in pow2);
      constraint k in pow2;
      constraint not(m in pow2);
      
      % k is at least 8
      constraint not(k < 8);
      
      
      % this year: n1 teams, k1 groups of m1
      var 11..1000: n1;
      var 4..334: k1;
      var 3..98: m1;
      
      constraint n1 = k1 * m1;
      constraint not(n1 in pow2);
      constraint k1 in pow2;
      constraint not(m1 in pow2);
      
      % more teams, but not twice as many
      constraint n1 > n;
      constraint n1 != 2 * n;
      
      % smaller groups, but more of them
      constraint m1 < m;
      constraint k1 > k;
      
      % total number of matches is the same
      constraint k * (m * (m - 1) + 2) = k1 * (m1 * (m1 - 1) + 2);
      
      solve satisfy;
      

      I wasn’t sure how use the neat trick to test for powers of 2 in MiniZinc, so I assumed an upper bound of 1,000 teams, and just made a set of powers of 2 less than this.

      Like

    • Lise Andreasen's avatar

      Lise Andreasen 2:50 pm on 6 June 2024 Permalink | Reply

      I think it’s possible to figure this out on paper, but there are a lot of restrictions to deal with.
      9 < x < 100
      x < y < 2x
      x = m * 2^k
      y = n * 2^l
      (that’s an L)
      l > k
      2 < n < m
      2^k or 2^l groups
      m or n members of each group
      Neither m nor n can be a power of 2
      There are this number of matches the first year:
      2^k * m(m-1)/2 + 2^k – 1
      And the second year:
      2^l * n(n-1)/2 + 2^l – 1
      These 2 numbers are the same
      A little calculation gives:
      m(m-1) + 2 = 2^(l-k) * [n(n-1) + 2]
      (I hope I got that one right!)
      Now it's possible to systematically go through all the possibilities.
      Like, k = 3, l = 4, n = 3, is m integer? And so on.
      Neat puzzle!

      Like

    • Frits's avatar

      Frits 7:38 pm on 6 June 2024 Permalink | Reply

      Comparing triangular numbers.

      tri = lambda n: (n * (n + 1)) // 2
      
      # powers of 2 (upper bound is not important as breaks are used)
      ps2 = [1 << i for i in range(1, 10)]
      
      # maximum number of teams last year is 96 (a multiple of 8)
      # t = number of teams per group
      # ngroups * t < 192 with ngroups >= 8 (quarter finals) so nteams < 24
      
      # nmatches = ngroups * (tri(t - 1) + 1) - 1  
      d = {tri(t - 1) + 1: t for t in range(2, 24) if t & (t - 1)} 
      mx = max(d)
      
      # last: matches = g * d[k2] - 1
      # this: matches = q * g * d[k1] - 1  where q = k2 / k1 and q is a power of 2
      for k1 in d.keys():
        for q in ps2:
          # k2 must be a multiple of k1
          if (k2 := q * k1) > mx: break
          if k2 not in d: continue
        
          # try different number of groups
          for g in ps2:
            if g < 8: continue
            if (nlast := g * d[k2]) > 99: break
            if (nthis := q * g * d[k1]) >= 2 * nlast: break
           
            print("answer:", nlast, "and", nthis)     
      

      Like

  • Unknown's avatar

    Jim Randell 10:05 am on 19 July 2019 Permalink | Reply
    Tags:   

    Teaser 2898: My valentine 

    From The Sunday Times, 8th April 2018 [link] [link]

    Using only positive digits, MILLY wrote down a two-digit number and a nine-digit number, both numbers being divisible by 14.

    Splitting her nine-digit number into three three-digit numbers, she noticed that all three numbers were also divisible by 14.

    Systematically replacing different digits by different letters, her two-digit number and nine-digit number ended up as MY VALENTINE.

    What number is represented by MILLY?

    [teaser2898]

     
    • Jim Randell's avatar

      Jim Randell 10:06 am on 19 July 2019 Permalink | Reply

      This puzzle can be solved directly using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file executes in 131ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      --digits="1-9"
      --answer="MILLY"
      
      # the 2 digit number is MY
      "MY % 14 = 0"
      
      # the 9 digit number is VALENTINE
      "VALENTINE % 14 = 0"
      "VAL % 14 = 0"
      "ENT % 14 = 0"
      "INE % 14 = 0"
      

      Solution: MILLY = 17224.

      MY = 14
      VALENTINE = 392658756 or 532896798

      Like

    • GeoffR's avatar

      GeoffR 10:46 am on 20 July 2019 Permalink | Reply

      Gave up using Geocode Solver after three minutes but Chuffed Solver produced a solution in a reasonable time.
      @Jim: Any idea why solvers run-times can vary so much?

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9: M; var 1..9: Y; var 1..9: V;
      var 1..9: A; var 1..9: L; var 1..9: E;
      var 1..9: N; var 1..9: T; var 1..9: I;
      
      constraint all_different ([M,Y,V,A,L,E,N,T,I]);
      
      var 100..999: VAL = 100*V + 10*A + L;
      var 100..999: ENT = 100*E + 10*N + T;
      var 100..999: INE = 100*I + 10*N + E;
      
      var 10..99: MY = 10*M + Y;
      var 10000..99999: MILLY = 10000*M + 1000*I + 110*L + Y;
      
      var 100000000..999999999: VALENTINE = V*pow(10,8) + A*pow(10,7)
      + L*pow(10,6) + E*pow(10,5) + N*pow(10,4) + T*pow(10,3)
      + I*100 + N*10 + E;
      
      constraint MY mod 14 == 0 /\ VALENTINE mod 14 == 0;
      constraint VAL mod 14 == 0 /\ ENT mod 14 == 0 /\ INE mod 14 == 0;
      
      solve satisfy;
      
      output [ "MILLY = " ++ show(MILLY) ]
      ++ ["\nMY = " ++ show(MY)]
      ++ ["\nVALENTINE = " ++ show(VALENTINE)  ];
      
      % MILLY = 17224
      % MY = 14
      % VALENTINE = 392658756
      % % time elapsed: 0.02 s
      % ----------
      % MILLY = 17224
      % MY = 14
      % VALENTINE = 532896798
      % % time elapsed: 0.02 s
      % ----------
      % ==========
      % Finished in 387msec
      

      Like

      • Jim Randell's avatar

        Jim Randell 11:10 am on 23 July 2019 Permalink | Reply

        @GeoffR: Using integer literals for the powers of 10 instead of [[ pow() ]] seems to make the model acceptable to the [[ gecode ]] solver.

        Like

    • GeoffR's avatar

      GeoffR 12:20 pm on 23 July 2019 Permalink | Reply

      @Jim: Thanks for the reply, yes that suggestion now works OK for the Geoode solver. Perhaps the Chuffed solver works better for powers as it looks as though it is based on C++

      Like

      • Jim Randell's avatar

        Jim Randell 1:16 pm on 23 July 2019 Permalink | Reply

        I suspect that one of the solvers notices that they are constant terms and calculates them once, but the other one doesn’t and keeps re-evaluating them.

        Like

    • Frits's avatar

      Frits 11:15 am on 18 September 2020 Permalink | Reply

       
      # concatenate list of integers
      to_num = lambda *args: int("".join(map(str, args)))
      
      # make sure loop variable value is not equal to previous ones
      def domain(v):
        # find already used loop values  ...
        vals = set()
        # ... by accessing previously set loop variable names
        for s in lvd[v]:
          vals.add(globals()[s])
        
        return [x for x in range(1,10) if x not in vals]
      
      # set up dictionary of for-loop variables
      lv = ['M', 'Y', 'E', 'N', 'T', 'I', 'V', 'A', 'L']
      lvd = {v: lv[:i] for i, v in enumerate(lv)}
      
      sol = set()
      
      # Solve puzzle by testing all posssible values
      for M in range(1,10):
        for Y in domain('Y'):
          if (to_num(M, Y) % 14 != 0): continue
          for E in domain('E'):
            for N in domain('N'):
              for T in domain('T'):
                if (to_num(E, N, T) % 14 != 0): continue
                for I in domain('I'):
                  if (to_num(I, N, E) % 14 != 0): continue
                  for V in domain('V'):
                    for A in domain('A'):
                      for L in domain('L'):
                        if (to_num(V, A, L) % 14 != 0): continue
                        sol.add(to_num(M, I, L, L, Y))
                          
      for s in sol:
        print(s)  
        
      # Output:
      #
      # 17224
      

      Like

      • Frits's avatar

        Frits 11:35 am on 18 September 2020 Permalink | Reply

        As VAL000000 is divisible by 14, ENT000 is divisible by 14 and INE is divisible by 14 we don’t need to verify that VALENTINE is divisible by 14.

        Like

    • Frits's avatar

      Frits 2:31 pm on 30 November 2021 Permalink | Reply

      Using choose().

        
      from enigma import choose
      
      # concatenate list of integers
      to_num = lambda *args: int("".join(map(str, args)))
      
      S = list(choose(range(1, 10), 
          [None, 
          (lambda M, Y: Y not in {M} and to_num(M, Y) % 14 == 0), 
          (lambda M, Y, E: E not in {M, Y}),
          (lambda M, Y, E, N: N not in {M, Y, E}),
          (lambda M, Y, E, N, T: T not in {M, Y, E, N} and 
                  to_num(E, N, T) % 14 == 0),
          (lambda M, Y, E, N, T, I: I not in {M, Y, E, N, T} and 
                  to_num(I, N, E) % 14 == 0),
          (lambda M, Y, E, N, T, I, V: V not in {M, Y, E, N, T, I}),
          (lambda M, Y, E, N, T, I, V, A: A not in {M, Y, E, N, T, I, V}),
          (lambda M, Y, E, N, T, I, V, A, L: L not in {M, Y, E, N, T, I, V, A} and 
                  to_num(V, A, L) % 14 == 0 and 
                  to_num(V, A, L, E, N, T, I, N, E) % 14 == 0),
          ]))
      
      print("MILLY:", *set("".join(str(x) for x in [s[0], s[5], s[8], s[8], s[1]]) 
                 for s in S))
      

      Like

    • Frits's avatar

      Frits 4:28 pm on 30 November 2021 Permalink | Reply

      With list comprehensions.

        
      # even digits are reserved for Y, T, E and L
      digits = set("123456789")
      odds = set("13579")
      evens = digits.difference(odds)
      
      # even digits are reserved for Y, T, E and L
      # so M must be odd
      MY = set(s for x in range(10, 100) if x % 14 == 0 and 
           len(set((s := str(x)))) == 2 and 
           s[0] in odds and '0' not in s)
      
      # list of 3 digit numbers divisible by 14    
      # middle digit (A or N) must be odd
      dgts3 = [s for x in range(100, 1000) if x % 14 == 0 and 
               len(set((s := str(x)))) == 3 and 
               s[1] in odds and '0' not in s]
      
      ooe = [x for x in dgts3 if x[0] in odds] # odd, odd, even
      
      # ENT and INE are both divisible by 14
      # N and I must be odd
      ENT_INE = [(x, y) for x in dgts3 for y in ooe 
                 if y[2] + y[1] == x[:2] and y[0] != x[2]]
      
      ENT_INE_VAL = [(x, y, z) for x, y in ENT_INE for z in ooe 
                     if "".join(sorted(digits.difference(x + y + z))) in MY]
                    
      sol = set()             
      for ENT, INE, VAL in ENT_INE_VAL:
        t = ENT + INE + VAL
        M = odds.difference([x for x in t if x in odds])
        Y = evens.difference([x for x in t if x in evens])
        sol.add(M.pop() + t[3] + t[8] + t[8] + Y.pop())
      
      for s in sol:  
        print("MILLY:", s)
      

      Like

    • GeoffR's avatar

      GeoffR 11:39 am on 1 December 2021 Permalink | Reply

      from itertools import permutations
      
      for p1 in permutations('123456789', 2):
        M, Y = p1
        MY = int(M + Y)
        if MY % 14 != 0: continue
        q1 = set('123456789').difference([M, Y])
      
        # 1st part(VAL) of VALENTINE
        for p2 in permutations(q1, 3):
          V, A, L = p2
          VAL = int(V + A + L)
          if VAL % 14 != 0:continue
          q2 = q1.difference([V, A, L])
      
          # 2nd part(ENT) of VALENTINE
          for p3 in permutations(q2, 4):
            E, N, T, I = p3
            ENT = int(E + N + T)
            if ENT % 14 != 0:continue
      
            # 3rd part(INE) of VALENTINE
            INE = int(I + N + E)
            if INE % 14 != 0: continue
            VALENTINE = int(V + A + L + E + N + T + I + N + E)
            # Stated condition, but not necessary
            #if VALENTINE % 14 != 0:continue
            MILLY = int(M + I + L + L + Y)
            print(f"MY={MY}, VALENTINE={VALENTINE}, MILLY={MILLY}") 
      
      # MY=14, VALENTINE=392658756, MILLY=17224
      # MY=14, VALENTINE=532896798, MILLY=17224
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 3:13 pm on 18 July 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 487: [Seating arrangements] 

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

    Five friends, Archer, Brown, Carter, Dale and Eade, and their wives, Anne, Betty, Clara, Diana and Edna, meet on the first Wednesday of every month to dine together (at a round table) in a local hotel. They have a rule that no wife may sit next to, or immediately opposite, her husband. Archer, as M.C., sits always with his back to the fireplace.

    We are concerned with the seating arrangements in January, April, July and October. On these four occasions no man, with the exception of the M.C., sat at any place more than once. In January the men sat in clockwise alphabetical order (men and women always sit alternately). Also, on these four evenings, the ladies sitting opposite Archer were successively: Betty, Clara, Diana and Edna.

    In April, Diana sat at the same place she occupied in January, and in July, Anne sat at the same place that she occupied in April.

    What was the seating arrangement in October? (Clockwise, A to A, using capital initials for the men and small initials for the women).

    This puzzle was originally published with no title.

    [teaser487]

     
    • Jim Randell's avatar

      Jim Randell 3:14 pm on 18 July 2019 Permalink | Reply

      This Python program runs in 88ms.

      Run: [ @repl.it ]

      from enigma import subsets, update as _update, join, printf
      
      # possible names
      names = "ABCDE"
      
      # update a string with new items
      def update(s, js, vs):
        return join(_update(list(s), js, vs))
      
      # generate possible seating plans
      def generate(ss):
        # find unseated people, and empty places
        (xs, js) = (set(names), list())
        for (j, x) in enumerate(ss):
          if x == '?':
            js.append(j)
          else:
            xs.remove(x)
      
        # complete the seating plan
        for s in subsets(xs, size=len, select='P'):
          yield update(ss, js, s)
      
      # groups of adjacent and opposite seats: w -> m
      group = {
        0: [0, 1, 3],
        1: [1, 2, 4],
        2: [2, 3, 0],
        3: [3, 4, 1],
        4: [4, 0, 2],
      }
      
      # check no men reuse seats, apart from seat 0
      def m_check(ms, ss):
        for (i, x) in enumerate(ms):
          if i == 0: continue
          if any(s[i] == x for (s, _) in ss):
            return False
        return True
      
      # check no woman is seated opposite or next to her husband
      def w_check(ms, ws):
        for (i, w) in enumerate(ws):
          if any(ms[j] == w for j in group[i]):
            return False
        return True
      
      # find seating plans, given previous plans
      def solve(ms, ws, ss=[]):
      
        # complete the seating plan for the men
        for ms1 in generate(ms):
          if not m_check(ms1, ss): continue
      
          # complete the seating plan for the women
          for ws1 in generate(ws):
            if not w_check(ms1, ws1): continue
            yield (ms1, ws1)
      
      # find the January seating plan
      for Jan in solve('ABCDE', '??B??'):
      
        # find the April seating plan
        # Mrs D sits in the same place as in Jan
        ws = update('??C??', [Jan[1].index('D')], ['D'])
        for Apr in solve('A????', ws, [Jan]):
      
          # find the July seating plan
          # Mrs A sits in the same place as in April
          ws = update('??D??', [Apr[1].index('A')], ['A'])
          for Jul in solve('A????', ws, [Jan, Apr]):
      
            # find the October seating plan
            for Oct in solve('A????', '??E??', [Jan, Apr, Jul]):
      
              # output the seating plans
              printf("Jan={Jan[0]}+{Jan[1]} Apr={Apr[0]}+{Apr[1]} Jul={Jul[0]}+{Jul[1]} Oct={Oct[0]}+{Oct[1]}")
      

      Solution: The seating arrangement in October was:

      Mr A; Mrs B; Mr E; Mrs A; Mr D; Mrs E; Mr C; Mrs D; Mr B; Mrs C

      The other seating plans are:

      Jan = Mr A; Mrs E; Mr B; Mrs A; Mr C; Mrs B; Mr D; Mrs C; Mr E; Mrs D
      Apr = Mr A; Mrs B/E; Mr D; Mrs E/B; Mr B/E; Mrs C; Mr E/B; Mrs A; Mr C; Mrs D
      Jul = Mr A; Mrs E/B; Mr C; Mrs B/E; Mr E/B; Mrs D; Mr B/E; Mrs A; Mr D; Mrs C
      Oct = Mr A; Mrs B; Mr E; Mrs A; Mr D; Mrs E; Mr C; Mrs D; Mr B; Mrs C

      In places where there are options either the first or second option is used consistently throughout.

      Like

  • Unknown's avatar

    Jim Randell 8:35 am on 17 July 2019 Permalink | Reply
    Tags:   

    Teaser 2899: Base jumping 

    From The Sunday Times, 15th April 2018 [link] [link]

    In written multiplication tests Jay always calculated correct answers, but, quirkily, wrote the digits of any two-figure answers in reverse order. A completed test sheet (pre-printed with ordered triplets of single-figure positive integers to multiply, e.g., 1×2×2=? and 3×5×9=?) included a question and his two-figure written answer that would be right when thought of in a number base other than “base 10” (any numerals keeping their usual values). Jay told Kay about this, but didn’t give the question or answer. Kay asked whether one of that question’s numbers to multiply was a particular value and from the reply knew both the question and the answer Jay had written.

    What was Jay’s written answer to that question?

    [teaser2899]

     
    • Jim Randell's avatar

      Jim Randell 8:35 am on 17 July 2019 Permalink | Reply

      This Python program collects the possible multiplications where a reversed answer would be correct in a non-decimal base. It then uses the [[ filter_unique() ]] function (from the enigma.py library) to find situations where the answer for a specific digit indicate a unique multiplication question.

      This Python program runs in 60ms. (Internal runtime is 673µs).

      Run: [ @replit ]

      from enigma import (subsets, irange, multiply, div, unpack, filter_unique, printf)
      
      # collect possible questions
      ss = list()
      
      # consider triples composed of single digits (i, j, k)
      for t in subsets(irange(1, 9), size=3, select='R'):
        n = multiply(t)
        if not (9 < n < 100): continue
      
        # n = 10x + y = by + x
        (x, y) = divmod(n, 10)
        if x == y or y == 0: continue
        b = div(n - x, y)
        if b is None: continue
        if not (x < b and y < b and b != 10): continue
      
        # record tuples (<triple>, <product>, <tens digit>, <units digit>, <base>)
        printf("[{n} (base 10) = {y}{x} (base {b}), {n} -> {t}]")
        ss.append((t, n, x, y, b))
      
      # K asks if one of the numbers in the triple is a particular value k
      for k in irange(1, 9):
        # K asks if the digit k is (at least) one of the digits in the question
        # and from the answer can determine both the question and J's answer
        f = unpack(lambda t, n, x, y, b: k in t)
        g = unpack(lambda t, n, x, y, b: (t, n))
        rs = filter_unique(ss, f, g).unique
        # output unique solutions
        for r in rs:
          (t, n, x, y, b) = r
          printf("[k={k} -> {z}] question: {t[0]}x{t[1]}x{t[2]} (= {n}), jay's answer: {y}{x} (in base {b} = {n})", z=f(r))
      

      Solution: Jay’s answer was 48.

      It is odd that the setter asks for Jay’s written answer, which can refer to multiple possible written questions, rather than the three digits of the written question, which are unique.

      There are 9 possible multiplications where a reversed answer would be correct in a non-decimal base:

      1×3×7 = 21 [base 10] = 12 [base 19]
      1×6×7 = 2×3×7 = 42 [base 10] = 24 [base 19]
      1×7×9 = 3×3×7 = 63 [base 10] = 36 [base 19]
      2×6×7 = 3×4×7 = 84 [base 10] = 48 [base 19]
      1×9×9 = 3×3×9 = 81 [base 10] = 18 [base 73]

      Kay asks if the question includes the digit 4, and receives the answer “Yes”, this narrows the question down to a single possibility:

      3×4×7 = 84 [base 10] = 48 [base 19]

      This is the only digit/answer combination which gives a unique answer for the question.

      However, if Kay had asked about the digit 7, and received a “No” answer, there would have been two possible questions, but he would still be able to work out Jay’s answer:

      1×9×9 = 3×3×9 = 81 [base 10] = 18 [base 73]

      Like

    • GeoffR's avatar

      GeoffR 8:18 pm on 23 July 2019 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % triplets of single value positive integers
      var 1..9: i; var 1..9: j; var 1..9: k;
      
      % the correct two digit result in base 10
      var 9..99: nbr;    
      
      % tens and units digits of the result                    
      var 1..9: t; var 1..9: u;  
      
      % the number base for reversed digits
      var 11..99: b;              
      
      % order triplet digits
      constraint i <= j /\ j <= k;
      
      constraint nbr = i * j * k;
      
      % find tens and units digits of 2-digit number
      constraint t = nbr div 10 /\ u == nbr mod 10;
      
      % number is correct in base b if digits are reversed
      constraint nbr == 10*t + u /\ nbr == b*u + t;
      
      solve satisfy;
      
      output [show(i) ++ " * " ++ show(j) ++ " * " ++ show(k) ++ " == " 
      ++show(nbr) ++ " (base 10) == " ++ show(u) ++ show(t) ++ 
      " (base " ++ show(b) ++ ")"]
                  
      % 1 * 3 * 7 == 21 (base 10) == 12 (base 19)
      % 3 * 3 * 9 == 81 (base 10) == 18 (base 73)
      % 1 * 9 * 9 == 81 (base 10) == 18 (base 73)
      % 1 * 6 * 7 == 42 (base 10) == 24 (base 19)
      % 2 * 3 * 7 == 42 (base 10) == 24 (base 19)
      % 3 * 3 * 7 == 63 (base 10) == 36 (base 19)
      % 1 * 7 * 9 == 63 (base 10) == 36 (base 19)
      
      % Jay's answer - digit 4 gives a unique result  
      % 3 * 4 * 7 == 84 (base 10) == 48 (base 19)  <<<
      
      % 2 * 6 * 7 == 84 (base 10) == 48 (base 19)
      % ----------
      % ==========         
      

      Like

  • Unknown's avatar

    Jim Randell 8:50 am on 16 July 2019 Permalink | Reply
    Tags: by: Alan Gebbie   

    Brainteaser 1629: Tangle o’ the aisles 

    From The Sunday Times, 28th November 1993 [link]

    It was a big day in Obness. Five girls, each from a different Scottish island, each marries the only sibling of one of the others. It was a big day for one of the licensed retailers too: ten bottles of good malt sold with all the brothers-in-law receiving one each from the respective grooms.

    Ann’s husband’s sister’s husband is married to the wife of the Harris girl’s brother. Bella’s husband’s sister’s husband’s sister’s husband’s sister’s husband’s sister is Emma. Celia and Delia are sisters-in-law. The Lewis girl’s brother’s wife is the Mull girl’s husband’s sister. Celia’s husband’s brothers-in-law are the Harris girl’s brother and the brother of the girl from Skye. Delia has a sister-in-law from Iona.

    For each girl find out:

    (a) who married her brother?
    (b) which island did she come from?

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

    Although the puzzle states that the girls are from different islands, Lewis and Harris are parts of the same island (although they are often referred to as if they were separate islands).

    [teaser1629]

     
    • Jim Randell's avatar

      Jim Randell 8:51 am on 16 July 2019 Permalink | Reply

      The brothers names aren’t mentioned, so I’m going to assume they share their initial with their sister and call them: Alan, Bruce, Callum, Dougal and Euan.

      We can then refer to the girls and the boys by their initial, and the mapping between siblings is the identity map.

      This Python program runs in 87ms.

      Run: [ @replit ]

      from enigma import (subsets, ordered, printf)
      
      # names (of girls and their brothers)
      names = "ABCDE"
      
      # islands
      islands = "HILMS"
      
      # marries: girl -> boy
      for s in subsets(names, size=len(names), select="P"):
        m = dict(zip(names, s))
        # no-one marries their own brother
        if any(g == b for (g, b) in m.items()): continue
      
        # "B's husband's sister's husband's sister's husband's sister's husband's sister is E"
        if not (m[m[m[m['B']]]] == 'E'): continue
      
        # "C and D are sisters-in-law"
        if not (m['C'] == 'D' or m['D'] == 'C'): continue
      
        # home: island -> girl (or boy)
        for s in subsets(names, size=len(names), select="P"):
          h = dict(zip(islands, s))
      
          # "A's husband's sister's husband is married to the wife of the H girl's brother"
          # X is married to the wife of X, so:
          # A's husband's sister's husband is the H girl's brother
          # A's husband's sister's husband is from H    
          if not (h['H'] == m[m['A']]): continue
      
          # "The L girl's brother's wife is the M girl's husband's sister"
          if not (h['L'] == m[m[h['M']]]): continue
      
          # "C's husband's brothers-in-law are the H girl's brother and the brother of the girl from S"
          if not (ordered('C', m[m['C']]) == ordered(h['H'], h['S'])): continue
      
          # "D has a sister-in-law from Iona"
          if not (h['I'] == m['D'] or m[h['I']] == 'D'): continue
          
          # output solution
          rev = lambda d: dict((v, k) for (k, v) in d.items())
          (rh, rm)  = (rev(h), rev(m))
          for g in names:
            printf("girl {g}: from {i}; her brother married {x}", i=rh[g], x=rm[g])
          printf()
      

      Solution: The answers are:

      Ann is from Iona. Her brother (Alan) married Bella.
      Bella is from Skye. Her brother (Bruce) married Emma.
      Celia is from Harris. Her brother (Callum) married Delia.
      Delia is from Mull. Her brother (Dougal) married Ann.
      Emma is from Lewis. Her brother (Euan) married Celia.

      So the weddings (using the names I made up) are:

      Ann (Iona) m. Dougal (Mull)
      Bella (Skye) m. Alan (Iona)
      Celia (Harris) m. Euan (Lewis)
      Delia (Mull) m. Callum (Harris)
      Emma (Lewis) m. Bruce (Skye)

      So Emma’s brother (Euan) and his wife, Celia, are actually from different parts of the same island – the island of Lewis and Harris [ link ].

      Like

    • Lise Andreasen's avatar

      Lise Andreasen 5:17 am on 3 June 2024 Permalink | Reply

      The pair Lewis and Skye are not neighbors. (A girl from one didn’t get married to the boy from the other.)
      The pair Harris and Skye are not neighbors.
      This can be done in 4 ways:

      L H M S I
      L S M H I
      M H L S I
      M S L H I

      In the 2nd and 4th row, A, C and D occupy the 1st, 2nd and 4th position. But then B and E can’t be sisters in law. Rejected.

      In the 1st row, it’s L girl, L boy, H girl, H boy etc. A is from I, D is from L, C is from H. But then A won’t get the right relationship to H. Rejected.

      Only the 3rd row is left. And it works.

      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