Enigmatic Code

Programming Enigma Puzzles

Puzzle 81: Uncle Bungle and the vertical tear

From New Scientist #1132, 7th December 1978 [link]

It was, I’m afraid, typical of Uncle Bungle that he should have torn up the sheet of paper which gave particulars of the numbers of matches played, won, lost, drawn and so on of four local football teams who were eventually going to play each other once. Not only had he torn it up, but he had also thrown away more than half of it onto, I suspect, the fire, which seems to burn eternally in Uncle Bungle’s grate. The tear was a vertical one and the only things that were left were the “goals against” and the “points” — or rather most of the points, for those of the fourth team had also been torn off.

What was left was as follows:


(2 points are given for a win and 1 for a draw).

It will not surprise those who know my uncle to hear that one of the figures was wrong, but fortunately it was only one out (i.e. one more or one less than the correct figure).

Each side played at least one game, and not more than seven goals were scored in any match.

Calling the teams ABC and D in that order, find the score in each match.



One response to “Puzzle 81: Uncle Bungle and the vertical tear

  1. Jim Randell 15 February 2017 at 9:45 am

    There’s not enough of the table (and one of the entries is wrong anyway) to make it worthwhile to use the Football.substituted_table() solver from the enigma.py library. But we can use other routines in the Football() class.

    This Python program runs in 250ms.

    from itertools import product
    from enigma import Football, irange, update, join, printf
    # scoring system
    football = Football(points={ 'w': 2, 'd': 1 })
    # possible scores in the matches (no match has more than 7 goals scores)
    scores = {
      'w': [
        (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0),
        (2, 1), (3, 1), (4, 1), (5, 1), (6, 1),
        (3, 2), (4, 2), (5, 2),
        (4, 3),
      'd': [(0, 0), (1, 1), (2, 2), (3, 3)],
      'x': [None],
    scores['l'] = list((b, a) for (a, b) in scores['w'])
    # check discrepancy
    # r = current discrepancy (d, label)
    # x = computed value
    # y = value given in the table
    # s = label if this value differs
    # discrepancy (abs(x - y)) should be d or less
    # the new discrepancy value (or None) is returned
    def check(r, x, y, s):
      d = abs(x - y)
      if d > r[0]: return None
      if d == 0: return r
      return (0, s)
    # initial discrepancy
    r0 = (1, None)
    # choose outcomes for B's matches
    for (AB, BC, BD) in football.games(repeat=3):
      if AB == BC == BD == 'x': continue
      B = football.table([AB, BC, BD], [1, 0, 0])
      r1 = check(r0, B.points, 5, "points B")
      if r1 is None: continue
      # choose outcomes for C's remaining matchs
      for (AC, CD) in football.games(repeat=2):
        if AC == BC == CD == 'x': continue
        C = football.table([AC, BC, CD], [1, 1, 0])
        r2 = check(r1, C.points, 0, "points C")
        if r2 is None: continue
        # and the remaining match
        for AD in football.games():
          if AB == AC == AD == 'x': continue
          if AD == BD == CD == 'x': continue
          A = football.table([AB, AC, AD], [0, 0, 0])
          r3 = check(r2, A.points, 3, "points A")
          if r3 is None: continue
          # choose the scores for C
          for (sAC, sBC, sCD) in product(scores[AC], scores[BC], scores[CD]):
            # goals for/against C
            (fC, aC) = football.goals([sAC, sBC, sCD], [1, 1, 0])
            r4 = check(r3, aC, 0, "against C")
            if r4 is None: continue
            # choose remaining scores for B
            for (sAB, sBD) in product(scores[AB], scores[BD]):
              # goals for/against B
              (fB, aB) = football.goals([sAB, sBC, sBD], [1, 0, 0])
              r5 = check(r4, aB, 6, "against B")
              if r5 is None: continue
              # choose remaining score
              for sAD in scores[AD]:
                # goals for/against A
                (fA, aA) = football.goals([sAB, sAC, sAD], [0, 0, 0])
                r6 = check(r5, aA, 5, "against A")
                if r6 is None: continue
                # goals for/against D
                (fD, aD) = football.goals([sAD, sBD, sCD], [1, 1, 1])
                r7 = check(r6, aD, 7, "against D")
                if r7 is None: continue
                # but one of the digits must be wrong
                if r7[0] > 0: continue
                # output a solution
                printf("\"{r7[1]}\" value is wrong")
                fmt = lambda s: ('---' if s is None else join(s, sep='-'))
                printf("A vs B = ({AB}) {s}", s=fmt(sAB))
                printf("A vs C = ({AC}) {s}", s=fmt(sAC))
                printf("A vs D = ({AD}) {s}", s=fmt(sAD))
                printf("B vs C = ({BC}) {s}", s=fmt(sBC))
                printf("B vs D = ({BD}) {s}", s=fmt(sBD))
                printf("C vs D = ({CD}) {s}", s=fmt(sCD))

    Solution: The scores in the matches that have been played are: A v B = 3-3; A v D = 3-2; B v C = 1-0; B v D = 4-3.

    The remaining matches are not yet played.

    The incorrect figure is the “goals against” column for C. It should be 1 (not 0).

    This is what the full table looks like:


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: