Seven Segment Search - Advent of Code 2021 Day 8


Today's problem involves a display system whose wiring has been mixed up, and we need to figure out the correct numbers that are being sent to the display.

A full description of the problem can be found here -

Understanding the problem

Each wire is lettered from a through g, and goes to a single bar in the 7 digit display.

The input to the problem has 2 parts. The first "calibration" part contains a set of unique signals that will be displayed. Each of these signals correspond to the numbers 0 through 9. The second part contains the actual numbers that are sent to the display.

Since the numbers 1,4,7 and 8 are the only numbers that are displayed with a unique number of bars, we can infer the set of wires corresponding to these numbers. We know the set but we cannot infer which wire goes to which yet. This information is enough to devise a solution to the first part of the problem.
If we were to compare the wires of 1 and 7, we can definitely figure out which wire goes to the top bar - a. We can also determine the two wires that should go to c and f. Comparing 1 to 4 also helps us determine the set of wires that should go to b and d. Finally comparing 8, 4 and 7 , lets us determine the set of wires that got to b and e.
Based on the data so far, we can compare the wires from 1 and 8 to gives us the "non-three" wires. 
Using the newly found information above, in conjunction with what we know from 8 and 4, we can accurately determine which wire goes to b,d,e and g.
For the last set of undiscovered wires we need to figure out the set of wires for 2,3 and 5. Using the information we have just learnt, we can figure out which set of wires corresponds to the number 2, and then use this to determine which wires got to c and f.


The only caveat to the code below is that I use a different lettering scheme for the dictionary key storing the wire. It doesn't affect the actual logic that determines which wire goes to that position

from collections import Counter

with open('input.txt') as f:
    lines = f.readlines()

def isUniqueCharSignal(u):
    return len(Counter(u)) == len(u)

def getOneFourSevenEight(signals):

    # 1 = 2 uniq chars, 4 = 4 unique chars, 7 = 3 unique chars, 8 = 7 unique chars
    k = { 2 : 1, 3 : 7 , 4 : 4 , 7 : 8 }

    return { k[len(i)]:i for i in getOneFourSevenEightNoUnique(signals) } 

def getOneFourSevenEightNoUnique(signals):
    return [ i for i in signals if len(i) in [2,3,4,7] ]

def getTwoThreeFive(signals):
    return [ set(i) for i in signals if len(i) == 5 ]

def getWiring(unique):

    u = getOneFourSevenEight(unique)

    # get chars exclusive to one , seven , four and eight
    oneChars = set(u[1])
    sevenChars = set(u[7]).difference(oneChars) 
    fourChars = set(u[4]).difference(oneChars) 
    eightChars = set(u[8]).difference(fourChars).difference(sevenChars).difference(oneChars)

    # build our intial set of wiring assumptions
    wiring = {  'a':0 ,'b':0 ,'c':0 ,'d':0 , 'e':0, 'f':0, 'g':0 } 
    wiring['b'] = wiring['c'] = oneChars
    wiring['a'] = list(sevenChars)[0]
    wiring['f'] = wiring['g'] = fourChars
    wiring['d'] = wiring['e'] = eightChars

    # get chars to two, three and give
    twoThreeFive = getTwoThreeFive(unique)

    # we can determine the chars in three
    threeChars = [ i for i in twoThreeFive if oneChars.issubset(i) ][0]
    # we can determine the chars NOT in three by removing the chars in three that are also in eight
    notThreeChars = set(u[8]).difference(threeChars)

    # knowing what is not in 3 we can solidify what is in positions 3,4,5 and 6
    wiring['g'] = list(wiring['g'].difference(notThreeChars))[0]
    wiring['f'] = list(wiring['f'].difference(wiring['g']))[0]
    wiring['d'] = list(wiring['d'].difference(notThreeChars))[0]
    wiring['e'] = list(wiring['e'].difference(wiring['d']))[0]

    # we can now definitively pick the chars in two and therefore the positions at 1 and 2
    twoChars = [ i for i in twoThreeFive if not set(wiring['f']).issubset(i) and set(wiring['e']).issubset(i)   ][0]

    wiring['b'] = list(twoChars.intersection(wiring['b']))[0]
    wiring['c'] = list(wiring['c'].difference(set(wiring['b'])))[0]

    # and generate the wiring for each number
    numbers = {}  
    numbers[0] = {wiring['a'],wiring['b'],wiring['c'],wiring['d'],wiring['e'],wiring['f']}
    numbers[1] = {wiring['b'],wiring['c']}
    numbers[2] = {wiring['a'],wiring['b'],wiring['g'],wiring['e'],wiring['d']}
    numbers[3] = {wiring['a'],wiring['b'],wiring['c'],wiring['d'],wiring['g']}
    numbers[4] = {wiring['f'],wiring['g'],wiring['b'],wiring['c']}
    numbers[5] = {wiring['a'],wiring['f'],wiring['g'],wiring['c'],wiring['d']}
    numbers[6] = {wiring['a'],wiring['f'],wiring['e'],wiring['d'],wiring['c'],wiring['g']}
    numbers[7] = {wiring['a'],wiring['b'],wiring['c']}
    numbers[8] = {wiring['a'],wiring['b'],wiring['c'],wiring['d'],wiring['e'],wiring['f'],wiring['g']}
    numbers[9] = {wiring['a'],wiring['b'],wiring['c'],wiring['d'],wiring['f'],wiring['g']}

    return wiring, numbers

def getNumber(displaySignal,numbers):
    return [ i for i in numbers if numbers[i] == displaySignal][0]

countOfOneFourSevenEight = 0
countOfAllNumbers = 0

for line in lines:

    (unique, display) = line.rstrip().split("|")
    (unique, display) = ([i for i in unique.split(" ") if i != ''],[j for j in display.split(" ") if j != ''])

    displayCharSignals = getOneFourSevenEightNoUnique(display)

    countOfOneFourSevenEight += len(displayCharSignals) 

    wiring,numbers = getWiring(unique)

    allnumbers = int("".join([  str(getNumber(set(i),numbers)) for i in display ]))
    countOfAllNumbers += allnumbers

print("Count of One Four Seven Eight = ",countOfOneFourSevenEight)
print("Count of All Numbers = ",countOfAllNumbers)

# Count of One Four Seven Eight =  349
# Count of All Numbers =  1070957