Advent of Code 2021: Day 8

Search for a command to run...

No comments yet. Be the first to comment.
In this series we solve programming challenges from the Advent of Code 2021 using JavaScript.
This blog post is eleventh in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 11. This day had many similarities to Day 9, and I solved the challenge using similar methods. All solutions are in thi...
This blog post is fourteenth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 14. This challenge was about optimizing an algorithm that generates exponentially larger and larger strings. I found ...

This blog post is thirteenth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 13. All solutions are in this GitHub repository. Solving Part One Given a list of x, y coordinates representing dots ...

This blog post is twelfth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described on Day 12. All solutions are in this GitHub repository. Solving Part One Given a set of connections between underground caves, ...

This blog post is eleventh in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 11. This day had many similarities to Day 9, and I solved the challenge using similar methods. All solutions are in thi...

This blog post is the tenth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 10. All solutions are in this GitHub repository. Solving Part One Given a set of lines with opening and closing bracke...

On this page
This blog post is eighth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 8. The challenge was about decoding four-digit numbers represented using a seven-segment display. The difficulty in solving this problem increased compared to the previous days and I struggled a bit to find the solution in part two. However, this was my favourite challenge so far.
All solutions are in this GitHub repository.
The problem is about decoding digits represented using a seven-segment display. Each segment has a name from a to g. By default, if we name the segments in order, the digits from 0 to 9 are represented by the segments abcefg, cf, acdeg, acdfg, bcdf, abdfg, abdefg, acf, abcdefg and abcdfg .

The problem is we don't know which letter corresponds to which segment. We must deduct it from a set of ten unique segment patterns representing digits from 0 to 9 given in random order. For example, given the pattern
acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab
we must find a way to deduce the name of each segment. The segment mapping from the example is illustrated below.

The pattern from the example represents the following digits:
acedgfb: 8 cdfbe: 5 gcdfa: 2 fbcad: 3 dab: 7 cefabd: 9 cdfgeb: 6 eafb: 4 cagedb: 0 ab: 1
We are given multiple lines, each containing ten unique signal patterns, a delimiter and four output values. For example,
acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf
The goal is to count the number of four-digit output values representing either 1, 4, 7 and 8. The problem description hints that these digits have a unique number of segments. Indeed only 1 has two segments, only 4 has four segments, only 7 has three segments and only 8 has seven segments.
To solve part one we must:
Since we only need the output value for part one, we can extract them from each line using the delimiter |.
const lines = data.split('\n')
const outputValues = lines.map((line) => line.split(' | ')[1])
Then to extract each digit into an array we can use the space as separator.
const digits = outputValue.split(" ")
Since 1, 4, 7, 8 are represented using 2, 4, 3, 7 segments respectively, we can check whether the length of the digit is one of those numbers. The includes function is useful for performing this check.
[2, 4, 3, 7].includes(digit.length)
We can use the reduce function to count output values respecting the condition previously defined.
digits.reduce((digitCount, digit) => {
if ([2, 4, 3, 7].includes(digit.length)) {
return digitCount + 1
}
return digitCount
}, 0)
Then, we can use the reduce function again to sum digits from all lines.
return outputValues.reduce((digitCount, outputValue) => {
const digits = outputValue.split(" ")
return digitCount + digits.reduce((count, digit) => {
if([2, 4, 3, 7].includes(digit.length)) {
return count + 1
}
return count
},0)
}, 0)
The final solution for part one is in my GitHub repository.
The second part of the problem was the tricky one. The goal of the second part was to decode all output values, from a four-digit number and sum all four-digit numbers.
I used a top-down approach where I wrote higher-level functions first and, thus defined the overall structure of the program, then defined the lower-level functions and went into detail. Here is how I broke down the problem:
The trickiest part was finding the segment mapping, in other words, identifying the name of each segment. The strategy was:
We can outline the solution as follows. We define the getOutput function afterwards.
const solve = (data) => {
const lines = data.split('\n')
return lines.reduce((sum, line) => sum + getOutput(line), 0)
}
We can further outline the solution:
const getOutput = (line) => {
const [segmentPatternsString, outputValuesString] = line.split(' | ')
const segmentMap = findSegmentNamesMap(segmentPatternsString)
const correspondingSevenSegmentDisplay = getSevenSegmentDisplay(segmentMap)
return getOutputDigits(outputValuesString, correspondingSevenSegmentDisplay)
}
Let's look at an example line. Given the line
acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf
the segmentPatternsString is
acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab
and outputValuesString is
cdfeb fcadb cdfeb cdbaf
The findSegmentNamesMap function returns the following mapping:
{
a: 'd',
b: 'e',
c: 'a',
d: 'f',
e: 'g',
f: 'b',
g: 'c',
}
The getSevenSegmentDisplay function maps each digit based on the default seven segment display.
So, the default seven-segment display
[ 'abcefg', 'cf', 'acdeg', 'acdfg', 'bcdf', 'abdfg', 'abdefg', 'acf', 'abcdefg', 'abcdfg']
becomes the following based on the segment names map
['abcdeg', 'ab', 'acdfg', 'abcdf', 'abef', 'bcdef', 'bcdefg', 'abd', 'abcdefg', 'abcdef']
Notice the letters are sorted alphabetically. This makes identifying the output digits easier.
The getOutputDigits maps the outputValuesString to 5353.
We can start by initializing the segment name map:
const findSegmentNamesMap = (segmentPatternsString) => {
const segmentMap = { a: '', b: '', c: '', d: '', e: '', f: '', g: '' }
return segmentMap
}
Using the default segment names we have the digits from 0 to 9 represented as:
const sevenSegmentDisplay = [ 'abcefg', 'cf', 'acdeg', 'acdfg', 'bcdf', 'abdfg', 'abdefg', 'acf', 'abcdefg', 'abcdfg']
and each letter occurs as follows
const abcdefgOccurence = { a: 8, b: 6, c: 8, d: 7, e: 4, f: 9, g: 7 }
Thus, the b, e and f segments occur a unique number of times. We can use this information to determine the segment name mapping for these values.
const findSegmentNamesMap = (segmentPatternsString) => {
const segmentMap = { a: '', b: '', c: '', d: '', e: '', f: '', g: '' }
const letterOccurence = calculateLetterOccurence(segmentPatternsString)
segmentMap.b = findLetter(letterOccurence, abcdefgOccurence.b)
segmentMap.e = findLetter(letterOccurence, abcdefgOccurence.e)
segmentMap.f = findLetter(letterOccurence, abcdefgOccurence.f)
return segmentMap
}
Part one hinted at using the length of digits. We can deduce a, c and d.
To find the segment corresponding to a, we can identify numbers 1 and 7 in the segmentPatternsString. We are looking for the letter not present in 1. For example, given ab and abd we can determine that d is the name corresponding to segment a.
const findA = (segmentPatternsString) => {
const one = findDigitBySignalsLength(segmentPatternsString, 2)
const seven = findDigitBySignalsLength(segmentPatternsString, 3)
let a = seven
one.split('').forEach((letter) => {
a = a.replace(letter, '')
})
return a
}
To find the segment corresponding to c, we can identify the number 1 in the segmentPatternsString and eliminate the previously found segment corresponding to f. For example, given ab and the previously found b corresponding to f, we can deduce that a is the name corresponding to c.
const findC = (segmentPatternsString, letterF) => {
const one = segmentPatternsString
.split(' ')
.find((digit) => digit.length === 2)
return one.replace(letterF, '')
}
To find the segment corresponding to d, we can identify the number4 in the segmentPatternsString and eliminate the previously found segment corresponding to b, c and f. For example, given eafb, and the previously found e corresponding to b, a corresponding to c and b corresponding to f, we can deduce that f is the name corresponding to d.
const findD = (segmentPatternsString, signalMap) => {
const four = findDigitBySignalsLength(segmentPatternsString, 4)
return four
.replace(signalMap.b, '')
.replace(signalMap.c, '')
.replace(signalMap.f, '')
}
After mapping a, b, c, d, e and f, we can determine the segment corresponding to g by process of elimination.
const findG = (signalMap) => {
let allLetters = 'abcdefg'
Object.keys(signalMap).forEach(
(key) => (allLetters = allLetters.replace(signalMap[key], ''))
)
return allLetters
}
The final solution for part two is in my GitHub repository.
Day 8 was really challenging and entailed decoding numbers by identifying unique occurrences, segment lengths and by process of elimination.