Enigmatic Code

Programming Enigma Puzzles

Enigma 207: Read all about it!

From New Scientist #1353, 14th April 1983 [link]

Hook, Line and Sinker were the judges for the Cooker Book prize this year as usual. They assembled a short list of six and then, as usual, could not agree on the order of merit. In the end each did his own ranking, giving 6 points for first place, 5 for second and so on (no ties). Then they totalled the points, which produced a final order (also without ties). Hook gave 5 points to the book which in fact came out second and 1 point to the book which finished third. He ranked “Stuff” above “Nonsense” and gave “Umph” the number of points which Line gave to “Impenetrables”. Line ranked “Gawp” above “Elements” and placed “Impenetrables” below “Umph”. Sinker ranked “Nonsense” third and “Stuff” fifth. No book totalled 13 or 10 or received the same number of points from any two judges. One of the books totalled 8.

Can you spell out the final order?



3 responses to “Enigma 207: Read all about it!

  1. Jim Randell 17 July 2014 at 7:08 am

    This Python program isn’t particularly elegant, or particularly quick, but it does find the answer. It runs in 535ms.

    from itertools import permutations
    from enigma import irange, diff, printf
    # indices for the books
    books = tuple("IEGNSU")
    # possible scores
    scores = tuple(irange(1, 6))
    # Hook's scores
    for s in permutations(books):
      hook = dict(zip(s, scores))
      # he rated S > N
      if not(hook['S'] > hook['N']): continue
      # Line's scores
      # he gave I the number of points Hook gave to U
      hU = hook['U']
      (lb, ls) = (diff(books, 'I'), diff(scores, (hU,)))
      for s in permutations(lb):
        line = { 'I': hU }
        line.update(zip(s, ls))
        # he rated G > E and I < U
        if not(line['G'] > line['E'] and line['I'] < line['U']): continue
        # no two scores are the same
        if any(line[x] == hook[x] for x in books): continue
        # Sinker's scores
        # he ranked N 3rd (4 points) and S 5th (2 points)
        (sb, ss) = (diff(books, 'NS'), diff(scores, (4, 2)))
        for s in permutations(sb):
          sinker = { 'N': 4, 'S': 2 }
          sinker.update(zip(s, ss))
          # no two scores are the same
          if any(sinker[x] in (hook[x], line[x]) for x in books): continue
          # compute the totals
          total = dict((x, hook[x] + line[x] + sinker[x]) for x in books)
          # all totals should be different
          s = set(total.values())
          if len(s) != 6: continue
          # no book scored 10 or 13, but one did score 8
          if not(8 in s and 10 not in s and 13 not in s): continue
          # work out the order
          order = sorted(books, key=lambda x: total[x], reverse=True)
          # Hook gave 5 points to the book that finished 2nd
          if not(hook[order[1]] == 5): continue
          # and 1 point to the book that finished 3rd
          if not(hook[order[2]] == 1): continue
          printf("line={line} hook={hook} sinker={sinker}")
          printf("total={total} order={order}")

    Solution: The final order is: Gawp, Elements, Nonsense, Impenetrables, Umph, Stuff.

    The actual scores given are shown below:

    Enigma 207 - Solution

    • Hugh Casement 19 November 2014 at 1:45 pm

      Note that the initial letters of the book titles read GENIUS.
      I’m still not inspired to read any of them, though.

  2. Jim Randell 19 October 2016 at 10:05 pm

    Here is the problem expressed as a set of MiniZinc constraints, which can be executed directly to give the solution.

    include "globals.mzn";
    % identify the books
    set of int: Books = 1..6;
    int: Gawp = 1;
    int: Elements = 2;
    int: Nonsense = 3;
    int: Impenetrables = 4;
    int: Umph = 5;
    int: Stuff = 6;
    % identify the judges
    set of int: Judges = 1..3;
    int: Hook = 1;
    int: Line = 2;
    int: Sinker = 3;
    % scores
    array[Judges, Books] of var 1..6: scores;
    % each judge gives each book a different score
    constraint forall (j in Judges) (alldifferent([scores[j, b] | b in Books]));
    % totals
    array[Books] of var int: totals;
    constraint forall (b in Books) (totals[b] = (sum (j in Judges) (scores[j, b])));
    % totals are all different
    constraint alldifferent(totals);
    % positions, map position -> book
    array[Books] of var 1..6: positions;
    constraint alldifferent(positions);
    % position 1 has the highest score, position 6 the lowest
    constraint decreasing([totals[positions[i]] | i in 1..6]);
    % "Hook gave 5 points to the book that came second"
    constraint scores[Hook, positions[2]] = 5;
    % "and 1 point to the book that finished third"
    constraint scores[Hook, positions[3]] = 1;
    % "He ranked 'Stuff' above 'Nonsense'"
    constraint scores[Hook, Stuff] > scores[Hook, Nonsense];
    % "gave 'Umph' the number of points which Line gave to 'Impenetrables'"
    constraint scores[Hook, Umph] = scores[Line, Impenetrables];
    % "Line ranked 'Gawp' above 'Elements'"
    constraint scores[Line, Gawp] > scores[Line, Elements];
    % "and placed 'Impenetrables' below 'Umph'"
    constraint scores[Line, Impenetrables] < scores[Line, Umph];
    % "Sinker ranked 'Nonsense' third" (i.e. 4 points)
    constraint scores[Sinker, Nonsense] = 4;
    % "and 'Stuff' fifth" (i.e. 2 points)
    constraint scores[Sinker, Stuff] = 2;
    % "No book totalled 13 or 10"
    constraint forall (b in Books) (totals[b] != 13 /\ totals[b] != 10);
    % "or received the same number of points from any two judges"
    constraint forall (b in Books) (alldifferent([scores[j, b] | j in Judges]));
    % "One of the books totalled 8."
    constraint exists (b in Books) (totals[b] = 8);
    solve satisfy;

    I used the minizinc.py wrapper library that I wrote for Enigma 361 to let me bring the solution(s) back into Python for easier formatting.

    This program runs in 144ms.

    from enigma import join, concat, printf
    from minizinc import MiniZinc
    p = MiniZinc("enigma207.mzn")
    # labels (1-indexed)
    books = [ None, "Gawp", "Elements", "Nonsense", "Impenetrables", "Umph", "Stuff" ]
    judges = [ None, "Hook", "Line", "Sinker" ]
    for (scores, totals, positions) in p.solve(result="scores totals positions", solver="mzn-gecode -a"):
      # output the results by position
      for (i, b) in enumerate(positions, start=1):
          "{i}: {b}, {p} pts ({s})",
          s=join((concat(j, '=', scores[k][b]) for (k, j) in enumerate(judges[1:], start=1)), sep=', ')

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: