Files
advent-of-code-2021/day-8/main.go
T

232 lines
7.0 KiB
Go
Raw Normal View History

2021-12-08 16:31:05 -05:00
/* Day 8: Seven Segment Search */
package main
import (
"AoC2021/utils/arrays"
"AoC2021/utils/bits"
"AoC2021/utils/log"
"AoC2021/utils/timer"
"bufio"
"math"
"os"
"strings"
)
func check(e error) {
if e != nil {
log.Println(log.DEBUG, "Oh snap!")
panic(e)
}
}
func main() {
log.Println(log.NORMAL, "--- Day 8: Seven Segment Search ---")
timer.Start()
part1()
timer.Tick()
log.Println(log.NORMAL)
part2()
timer.Tick()
log.Println(log.NORMAL)
}
func part1() {
log.Println(log.NORMAL, "* Part 1 *")
log.Println(log.NORMAL, " Goal: For now, focus on the easy digits.")
log.Println(log.NORMAL, " Answer: In the output values, how many times do digits 1, 4, 7, or 8 appear?")
input, err := os.Open("./input.txt")
check(err)
defer input.Close()
scanner := bufio.NewScanner(input)
lines := 0
ones := 0
fours := 0
sevens := 0
eights := 0
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " | ")
if len(fields) != 2 {
panic("Read invalid input line")
}
_ = strings.Fields(fields[0])
// count all the ones, fours, sevens, and eights in the output strings based on how many segments are lit up
outputDigits := strings.Fields(fields[1])
for _, d := range outputDigits {
switch len(d) {
case 2:
ones += 1
case 3:
sevens += 1
case 4:
fours += 1
case 7:
eights += 1
}
}
lines++
}
total := ones + fours + sevens + eights
log.Printf(log.NORMAL, "Read %d lines from input\n", lines)
log.Printf(log.NORMAL, "We counted %d ones, %d fours, %d sevens, and %d eights for a total of %d\n", ones, fours, sevens, eights, total)
}
func part2() {
log.Println(log.NORMAL, "* Part 2 *")
log.Println(log.NORMAL, " Goal: For each entry, determine all of the wire/segment connections and decode the four-digit output values")
log.Println(log.NORMAL, " Answer: What do you get if you add up all of the output values?")
input, err := os.Open("./input.txt")
check(err)
defer input.Close()
scanner := bufio.NewScanner(input)
lines := 0
total := 0
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " | ")
if len(fields) != 2 {
panic("Read invalid input line")
}
inputFields := strings.Fields(fields[0])
segMap := createPatternMap(arrays.MapStrToInt(inputFields, ParseField))
log.Println(log.DEBUG, "Created pattern", segMap)
outputFields := strings.Fields(fields[1])
fieldValue := 0
fieldsCount := len(outputFields)
for idx, field := range outputFields {
fValue := segMap[ParseField(field)]
log.Printf(log.DIAGNOSTIC, "field %s => %d; ", field, fValue)
fieldValue += fValue * int(math.Pow10(fieldsCount-idx-1))
}
total += fieldValue
log.Println(log.DIAGNOSTIC)
log.Printf(log.DIAGNOSTIC, "Unscrambled %s => %d\n", outputFields, fieldValue)
lines++
}
log.Printf(log.NORMAL, "Read %d lines from input\n", lines)
log.Printf(log.NORMAL, "Calculated total is %d\n", total)
}
// Represent a segment as a 7-bit value
/* Segment values are aligned as below
* aaaa
* b c
* b c
* dddd
* e f
* e f
* gggg
* segment 'a' is found in [0, 2, 3, 5, 6, 7, 8, 9] but not [1, 4]
* segment 'b' is found in [0, 4, 5, 6, 8, 9] but not [1, 2, 3, 7]
* segment 'c' is found in [0, 1, 2, 3, 4, 7, 8, 9] but not [5, 6]
* segment 'd' is found in [2, 3, 4, 5, 6, 8, 9] but not [0, 1, 7]
* segment 'e' is found in [0, 2, 6, 8] but not [1, 3, 4, 5, 7, 9]
* segment 'f' is found in [0, 1, 3, 4, 5, 6, 7, 8, 9] but not [2]
* segment 'g' is found in [0, 2, 3, 5, 6, 8, 9] but not [1, 4, 7]
*/
// parse a field from a string value to a bit-map int value (eg 'cda' to b1101 => 13)
func ParseField(field string) int {
value := 0
for _, c := range field {
value += 1 << int(rune(c)-rune('a'))
}
return value
}
// func getDefaultMap() map[int]int {
// return map[int]int{
// ParseField("abcefg"): 0,
// ParseField("cf"): 1,
// ParseField("acdeg"): 2,
// ParseField("acdfg"): 3,
// ParseField("bcdf"): 4,
// ParseField("abdfg"): 5,
// ParseField("abdefg"): 6,
// ParseField("acf"): 7,
// ParseField("abcdefg"): 8,
// ParseField("abcdfg"): 9}
// }
// Given a list of a all 10 possible 7-segment displays create a map that can be used to unscramble values
func createPatternMap(input []int) map[int]int {
// initialize our translation map that will convert from character to character (eg wire 'a' actually goes to segment 'c')
segMap := make(map[int]int)
// first find and map all the values that can be known by counting segments
// find the only pattern that matches '1'
one := arrays.FindFirstInt(input, bits.ByBitCount(2))
// next find the one that matches '7'
seven := arrays.FindFirstInt(input, bits.ByBitCount(3))
// next find the one that matches '4'
four := arrays.FindFirstInt(input, bits.ByBitCount(4))
// the one that matches '8' will be the only one with 7 segments but tells us nothing about what those segments map to
eight := arrays.FindFirstInt(input, bits.ByBitCount(7))
// the two segments in '1' will match 'c' or 'f' but we don't know yet
// the additional segment in '7' will map to segment 'a'
segMap['a'] = seven - one
// the two additional segments in '4' will match 'b' or 'd' but we're not sure which ones yet
// all of '0', '6' and '9' have 6 segments
// the six-segment field that shares only 1 side with '1' is '6'
six := arrays.FindFirstInt(input, func(v int) bool { return bits.ByBitCount(6)(v) && bits.MatchingOnBits(one, v) == 1 })
// and we know that the segment not in six is 'c'
segMap['c'] = eight - six
// and the other value 'f' will be the other segment in '1'
segMap['f'] = one - segMap['c']
// but we can't yet place '0' or '9' with what we know
// all of '2', '3', and '5' have 5 segments
// three is the only 5 segment field that matches 3 bits in '7'
three := arrays.FindFirstInt(input, func(v int) bool { return bits.ByBitCount(5)(v) && bits.MatchingOnBits(seven, v) == 3 })
// the additional two segments are 'd' ang 'g' but we can't place them yet
// five is the other 5 segment field that matches 3 bits in '4'
five := arrays.FindFirstInt(input, func(v int) bool { return bits.ByBitCount(5)(v) && v != three && bits.MatchingOnBits(four, v) == 3 })
// segments 'b', 'd', and 'g' can't yet be placed
// but segment 'e' is what we get by subtracting five from six
segMap['e'] = six - five
// the last five segment number must be 2
two := arrays.FindFirstInt(input, func(v int) bool { return bits.ByBitCount(5)(v) && v != three && v != five })
// segment 'g' is the only one that doesn't appear in '1', '4', or '7' and is not 'e'
segMap['g'] = eight - bits.BitwiseAndA(one, four, seven, segMap['e'])
// segment 'd' is '3' minus 'a', 'c', 'f', and 'g'
segMap['d'] = three - segMap['a'] - segMap['c'] - segMap['f'] - segMap['g']
// then we know that zero is eight minus 'd'
zero := eight - segMap['d']
// and nine will be eight minus 'e'
nine := eight - segMap['e']
log.Println(log.DIAGNOSTIC, "segMap", segMap)
return map[int]int{
zero: 0,
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9,
}
}