Enigmatic Code

Programming Enigma Puzzles

Enigma 388: See the light!

From New Scientist #1537, 4th December 1986 [link]

My niece was playing with my calculator recently. She showed me a three-figure number displayed (and I could see three different digits) and then she pushed the “square” button. This resulted in another number being displayed. I could see a number, but I soon realised that it was not the square of the original number.

On investigation we soon find out what was wrong. My calculator usually lights up the digits in this way:

Enigma 1701

that is, it lights up some of the seven little elements in each case. But we found out that the calculator had developed a fault. Although it did all its calculations correctly, in each place where a digit could be displayed the same one of the seven elements never lit up.

Some digits from 0 to 9 could still be lit up correctly, but over half of them couldn’t. Just that fact, together with knowing how many of the 10 digits could light up correctly, would enable you to work out which of the seven elements consistently failed.

If my calculator had been working correctly, what would I have seen displayed after the “square” button had been pushed?

[enigma388]

Advertisements

2 responses to “Enigma 388: See the light!

  1. Jim Randell 17 March 2017 at 7:21 am

    Like Enigma 1232 (a similar problem), this problem was also a bit more convoluted to code up than I originally expected.

    This Python program runs in 42ms.

    from itertools import permutations
    from collections import defaultdict
    from enigma import irange, join, filter_unique, unpack, nconcat, nsplit, printf
    
    # map digits to illuminated segments, arranged as:
    #   0
    # 1   2
    #   3
    # 4   5
    #   6
    segments = dict((i, set(ss)) for (i, ss) in enumerate((
      (0, 1, 2, 4, 5, 6), # 0
      (2, 5), # 1
      (0, 2, 3, 4, 6), # 2
      (0, 2, 3, 5, 6), # 3
      (1, 2, 3, 5), # 4
      (0, 1, 3, 5, 6), # 5
      (0, 1, 3, 4, 5, 6), # 6
      (0, 2, 5), # 7
      (0, 1, 2, 3, 4, 5, 6), # 8
      (0, 1, 2, 3, 5, 6), # 9
    )))
    
    # consider which segment fails, more than 5 of the digits are affected
    fs = list()
    for f in irange(0, 6):
      # count the number of affected digits
      ds = tuple(d for (d, ss) in segments.items() if f in ss)
      n = len(ds)
      printf("[failure of segment {f} affects {n} digits = {ds}]", ds=join(ds, sep=','))
      if n > 5:
        fs.append((f, ds))
    
    # the number of affected digits is unique
    (fs, _) = filter_unique(fs, unpack(lambda f, ds: len(ds)))
    
    # find digits that display as a different digit when a segment fails
    m = defaultdict(list)
    for (i, j) in permutations(segments.keys(), 2):
      (a, b) = (segments[i], segments[j])
      if not a.issubset(b): continue
      s = b.difference(a)
      if len(s) != 1: continue
      # failure of segment <s> makes digit <j> display as digit <i>
      m[s.pop()].append((j, i))
    
    digits = set(segments.keys())
    
    # consider possible failed digits
    for (f, ds) in fs:
      # make a map of actual digit to displayed digit
      d = dict(m[f])
      # but some digits must display as other digits
      if not d: continue
      # how do the (permissible) digits in t display?
      display = lambda t: tuple(d.get(x, x) for x in t)
      # permissible digits in the number (and it's square)
      ps = digits.difference(ds).union(d.keys())
    
      # find possible 3 digit start number
      for (a, b, c) in permutations(ps, 3):
        # how does it display?
        d_n = display((a, b, c))
        # it must display as 3 different digits
        if len(set(d_n)) != 3: continue
        # square the number
        n = nconcat(a, b, c)
        sq = nsplit(n ** 2)
        # all digits must be permissible
        if not(ps.issuperset(sq)): continue
        # and the displayed square is not the square of the originally displayed number
        d_sq = display(sq)
        if  nconcat(d_sq) == nconcat(d_n) ** 2: continue
    
        printf("original = {n} displays as {d_n}, squared = {sq} displays as {d_sq}", d_n=join(d_n), sq=join(sq), d_sq=join(d_sq))
    

    Solution: If the calculator had been working correctly the square displayed would be 29929.

    On the broken calculator the upper-left vertical segment on each digit does not light up.

    So the 3-digit number 173 (which displays as 173) when squared gives 29929, which is displayed as 23323 (which is not a square number).

    The restriction that more than 5 of the digits are affected by the failure only rules out the failure of the lower-left hand vertical segment (which affects the four digits 0, 2, 6, 8), the remaining segments affect 6 or more digits, although, of these, only failure of the upper-left vertical segment (which affects 6 digits) and lower-right vertical segment (which affects 9 digits) are uniquely identified by the number of affected digits.

    I use the unpack() function in line 35 to allow the program to work in both Python 2 and Python 3.

  2. Brian Gladman 17 March 2017 at 3:49 pm
    from collections import defaultdict
    from itertools import product
    from functools import reduce
    
    # Number the segments of a seven segment display as follows:
    #
    #    1
    #  2   3
    #    4
    #  5   6
    #    7
    
    # the segments for the digits 0 .. 9
    segments = ({1, 2, 3, 5, 6, 7},    {3, 6},                # 0, 1
                {1, 3, 4, 5, 7},       {1, 3, 4, 6, 7},       # 2, 3
                {2, 3, 4, 6},          {1, 2, 4, 6, 7},       # 4, 5
                {1, 2, 4, 5, 6, 7},    {1, 3, 6},             # 6, 7 
                {1, 2, 3, 4, 5, 6, 7}, {1, 2, 3, 4, 6, 7})    # 8, 9
    
    # convert a digit sequence into a number
    d2n = lambda seq: reduce(lambda x,y : 10 * x + y, seq)
    
    # map counts of correctly displayed digits to failed segments
    c2f = defaultdict(set)
    for fail in range(1, 8):
      correct = [d for d in range(10) if fail not in segments[d]]
      c2f[len(correct)].add(fail)
    
    # the segment that has failed can be determined if the
    # number of digits that display correctly is known
    for cnt, fail_segs in c2f.items():
      if cnt < 5 and len(fail_segs) == 1:
        failed, = fail_segs
    
        # for each digit that diplays as a valid digit, map
        # the digit to that displayed
        d2d = dict()
        for d in range(10):
          segs = segments[d].difference([failed])
          if segs in segments:
            d2d[d] = segments.index(segs)
          
        # pick three digits that display as valid digits
        for d3 in product(d2d.keys(), repeat=3):
          # find the digits that they display as
          t = [d2d[d] for d in d3]
          # which must all be different
          if len(set(t)) == 3:
            # the number and the number displayed
            nbr, display = d2n(d3), d2n(t)
            # look for a square that displays only valid digits
            try:
              sqr = (d2d[int(d)] for d in str(nbr ** 2))
              sqrd = reduce(lambda x,y : 10 * x + y, sqr)
              print('Segment {} failed: {} and {} display as {} and {}.'
                    .format(failed, nbr, nbr ** 2, display, sqrd))
            except KeyError:
              continue
    

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: