From 5de262fb6d2692e2ebf9a1272a271374ac87e537 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 8 Dec 2021 01:11:50 -0500 Subject: [PATCH] 2021 day8 2852/1511, i am too lazy to clean this up... oof that was painful --- 2021/day08/main.go | 229 ++++++++++++++++++++++++++++++++++++++++ 2021/day08/main_test.go | 64 +++++++++++ 2 files changed, 293 insertions(+) create mode 100644 2021/day08/main.go create mode 100644 2021/day08/main_test.go diff --git a/2021/day08/main.go b/2021/day08/main.go new file mode 100644 index 0000000..62fe7fc --- /dev/null +++ b/2021/day08/main.go @@ -0,0 +1,229 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "log" + "regexp" + "sort" + "strings" + + "github.com/alexchao26/advent-of-code-go/util" +) + +//go:embed input.txt +var input string + +func init() { + // do this in init (not main) so test file has same input + input = strings.TrimRight(input, "\n") + if len(input) == 0 { + panic("empty input.txt file") + } +} + +func main() { + var part int + flag.IntVar(&part, "part", 1, "part 1 or 2") + flag.Parse() + fmt.Println("Running part", part) + + ans := jumbledSevenSegment(input, part) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) +} + +func jumbledSevenSegment(input string, part int) int { + // four digit seven segment displays + // seven segments are letters + // need to unjumble the ten mappings (pre-bar) to outputs (last 4 post-bar) + var parsedInput [][]string + for i, line := range strings.Split(input, "\n") { + // regexp capture group to just get characters + parts := regexp.MustCompile(`([a-g]+)`).FindAllString(line, -1) + + if len(parts) != 14 { + log.Fatalf("should be 14 parts in each input line, got %d for line %d", len(parts), i) + } + + // return them as just all 14 mappings together, alphabetize them for easier comparison later + var fourteen []string + for _, v := range parts { + fourteen = append(fourteen, alphabetizeString(v)) + } + parsedInput = append(parsedInput, fourteen) + } + + // PART 1 just count the outputs that are "easy" to determine + // aka only one displayed number uses that number of segments + if part == 1 { + var ans int + for _, set := range parsedInput { + // check the back 4: output + for _, o := range set[10:] { + switch len(o) { + // 1, 4, 7, 8 have a unique number of segments lit + // 2, 4, 4, 7 segments respectively + case 2, 4, 3, 7: + ans++ + } + } + } + return ans + } + + // PART 2 + var ans int + indexToCharacters := make([]string, 10) + for _, set := range parsedInput { + + workingSet := set[:10] + var killIndices []int // store the indices that will need to be removed in batches + + // 1, 4, 7 and 8 are all easy to find because they have a unique number of segments + for i, mapping := range workingSet { + switch len(mapping) { + case 2: + // these two make up the 1 + indexToCharacters[1] = mapping + killIndices = append(killIndices, i) + case 4: // the 4 + indexToCharacters[4] = mapping + killIndices = append(killIndices, i) + case 3: // the 7 + indexToCharacters[7] = mapping + killIndices = append(killIndices, i) + case 7: // the 8 + indexToCharacters[8] = mapping + killIndices = append(killIndices, i) + } + } + + // remove them from the workingSet + workingSet = removeSliceIndices(workingSet, killIndices...) + + // only 0 3 and 9 will fully overlap the 1 characters + var zeroThreeOrNine []string + killIndices = []int{} // reset these... + for i, mapping := range workingSet { + if checkStringOverlap(mapping, indexToCharacters[1]) { + zeroThreeOrNine = append(zeroThreeOrNine, mapping) + killIndices = append(killIndices, i) + } + } + if len(zeroThreeOrNine) != 3 { + log.Fatalf("one three or nine does not have three matches: got %d", len(zeroThreeOrNine)) + } + + // find the 3 based on segment length + for i, maybe039 := range zeroThreeOrNine { + if len(maybe039) == 5 { + // must be the 3 + indexToCharacters[3] = maybe039 + zeroThreeOrNine = removeSliceIndices(zeroThreeOrNine, i) + break + } + } + + // the 9 will have a full overlap with 4 + for i, maybe09 := range zeroThreeOrNine { + if checkStringOverlap(maybe09, indexToCharacters[4]) { + indexToCharacters[9] = maybe09 + zeroThreeOrNine = removeSliceIndices(zeroThreeOrNine, i) + } + } + + // 0 is only one left of the triplet + indexToCharacters[0] = zeroThreeOrNine[0] + + // trim down working set again + workingSet = removeSliceIndices(workingSet, killIndices...) + if len(workingSet) != 3 { + log.Fatalf("expected length of 3 at this stage, got %d", len(workingSet)) + } + + // 6 is the last one with a length of 6 + for i, mapping := range workingSet { + if len(mapping) == 6 { + indexToCharacters[6] = mapping + workingSet = removeSliceIndices(workingSet, i) + } + } + + // 5 will be fully contained within the 9 + for i, mapping := range workingSet { + if checkStringOverlap(indexToCharacters[9], mapping) { + indexToCharacters[5] = mapping + workingSet = removeSliceIndices(workingSet, i) + } + } + + if len(workingSet) != 1 { + log.Fatalf("expected length of 1 at this stage, got %d", len(workingSet)) + } + + // 2 is the last remaining mapping + indexToCharacters[2] = workingSet[0] + + // finally, collect the four digits in the output & add it to the answer + var num int + for _, out := range set[10:] { + for i, mapping := range indexToCharacters { + // because they were all alphabetized, we can just do a direct comparison + if out == mapping { + // shift all digits to the left and add the new digit to the end + num *= 10 + num += i + } + } + } + ans += num + } + + return ans +} + +func removeSliceIndices(sli []string, indices ...int) []string { + m := map[int]bool{} + for _, v := range indices { + m[v] = true + } + + var ans []string + for i, v := range sli { + if !m[i] { + ans = append(ans, v) + } + } + return ans +} + +func checkStringOverlap(larger, smaller string) bool { + // safeguard, a smaller string can never contain a larger string + // i think every use of this function already passes args in the correct order, but this makes + // the checkStringOverlap algo more robust in general + if len(larger) < len(smaller) { + larger, smaller = smaller, larger + } + + largeMap := map[rune]bool{} + for _, r := range larger { + largeMap[r] = true + } + + // check all runes in the smaller string, return false if not in largeMap + for _, r := range smaller { + if !largeMap[r] { + return false + } + } + return true +} + +// alphabetize the strings to make them easier to compare later +func alphabetizeString(input string) string { + chars := strings.Split(input, "") + sort.Strings(chars) + return strings.Join(chars, "") +} diff --git a/2021/day08/main_test.go b/2021/day08/main_test.go new file mode 100644 index 0000000..e3b3529 --- /dev/null +++ b/2021/day08/main_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "testing" +) + +var shortExample = `acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf` +var example = `be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe +edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc +fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg +fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb +aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea +fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb +dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe +bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef +egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb +gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce` + +func Test_jumbledSevenSegment(t *testing.T) { + tests := []struct { + name string + input string + part int + want int + }{ + { + name: "example", + input: example, + part: 1, + want: 26, + }, + { + name: "actual", + input: input, + part: 1, + want: 367, + }, + { + name: "short_example", + input: shortExample, + part: 2, + want: 5353, + }, + { + name: "example", + input: example, + part: 2, + want: 61229, + }, + { + name: "actual", + input: input, + part: 2, + want: 974512, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := jumbledSevenSegment(tt.input, tt.part); got != tt.want { + t.Errorf("jumbledSevenSegment() = %v, want %v", got, tt.want) + } + }) + } +}