# 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:

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]

### 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]]

# 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
```

This site uses Akismet to reduce spam. Learn how your comment data is processed.