mirror of
https://github.com/RussellChamp/advent-of-code-2021.git
synced 2026-10-27 21:52:32 +00:00
232 lines
7.0 KiB
Go
232 lines
7.0 KiB
Go
/* 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,
|
||
|
|
}
|
||
|
|
}
|