From 2d203e541e2992408fb0778c8e8cc30d30cec0f8 Mon Sep 17 00:00:00 2001 From: Jeremy Kaplan Date: Tue, 7 Dec 2021 23:42:54 -0800 Subject: [PATCH] 2021 Day 8 --- 2021/aoc/stringset/stringset.go | 108 +++++++++++++++ 2021/aoc/stringset/stringset_test.go | 40 ++++++ 2021/day8/main.go | 188 +++++++++++++++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 2021/aoc/stringset/stringset.go create mode 100644 2021/aoc/stringset/stringset_test.go create mode 100644 2021/day8/main.go diff --git a/2021/aoc/stringset/stringset.go b/2021/aoc/stringset/stringset.go new file mode 100644 index 0000000..45ff894 --- /dev/null +++ b/2021/aoc/stringset/stringset.go @@ -0,0 +1,108 @@ +package stringset + +import ( + "fmt" + "sort" + "strings" +) + +type StringSet map[string]struct{} + +func Slice(vals []string) StringSet { + set := make(StringSet) + for _, v := range vals { + set[v] = struct{}{} + } + return set +} + +func Runes(v string) StringSet { + return Slice(strings.Split(v, "")) +} + +func (s StringSet) String() string { + var elts []string + for e := range s { + elts = append(elts, e) + } + sort.Strings(elts) + return fmt.Sprintf("{%s}", strings.Join(elts, ", ")) +} + +func (s StringSet) Only() string { + if len(s) > 1 { + panic(fmt.Sprintf("too many elements: %s", s)) + } + for v := range s { + return v + } + panic("empty set") +} + +func (s StringSet) Has(elt string) bool { + _, ok := s[elt] + return ok +} + +func (s StringSet) add(elt string) { + s[elt] = struct{}{} +} + +func (s StringSet) Union(t StringSet) StringSet { + set := make(StringSet) + for a := range s { + set.add(a) + } + for b := range t { + set.add(b) + } + return set +} + +func (s StringSet) Intersect(t StringSet) StringSet { + set := make(StringSet) + for a := range s { + if t.Has(a) { + set.add(a) + } + } + return set +} + +func (s StringSet) Minus(t StringSet) StringSet { + set := make(StringSet) + for a := range s { + if !t.Has(a) { + set.add(a) + } + } + return set +} + +func (s StringSet) Equal(t StringSet) bool { + if len(s) != len(t) { + return false + } + for a := range s { + if !t.Has(a) { + return false + } + } + return true +} + +func Union(sets ...StringSet) StringSet { + t := make(StringSet) + for _, s := range sets { + t = t.Union(s) + } + return t +} + +func Intersect(sets ...StringSet) StringSet { + t := Union(sets...) + for _, s := range sets { + t = t.Intersect(s) + } + return t +} diff --git a/2021/aoc/stringset/stringset_test.go b/2021/aoc/stringset/stringset_test.go new file mode 100644 index 0000000..3661c64 --- /dev/null +++ b/2021/aoc/stringset/stringset_test.go @@ -0,0 +1,40 @@ +package stringset_test + +import ( + "testing" + + "github.com/jdkaplan/advent-of-code/aoc/stringset" +) + +func TestMinus(t *testing.T) { + a := stringset.Runes("dab") + b := stringset.Runes("ab") + u := a.Minus(b) + if n := len(u); n != 1 { + t.Fatalf("Expected one value, got %d", n) + } + if v := u.Only(); v != "d" { + t.Errorf(`Expected value to be "d", got %s`, v) + } +} + +func TestIntersect(t *testing.T) { + t.Run("pairwise", func(t *testing.T) { + u := stringset.Runes("dab").Intersect(stringset.Runes("ab")) + expected := stringset.Runes("ab") + if !u.Equal(expected) { + t.Errorf(`Expected value to be %s, got %s`, expected, u) + } + }) + + t.Run("multiple", func(t *testing.T) { + u := stringset.Intersect( + stringset.Runes("dab"), + stringset.Runes("ab"), + ) + expected := stringset.Runes("ab") + if !u.Equal(expected) { + t.Errorf(`Expected value to be %s, got %s`, expected, u) + } + }) +} diff --git a/2021/day8/main.go b/2021/day8/main.go new file mode 100644 index 0000000..7783db2 --- /dev/null +++ b/2021/day8/main.go @@ -0,0 +1,188 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/jdkaplan/advent-of-code/aoc" + "github.com/jdkaplan/advent-of-code/aoc/stringset" +) + +func main() { + lines := aoc.Input().ReadLines("day8.txt") + fmt.Println(part1(lines)) + fmt.Println(part2(lines)) +} + +func part1(lines []string) int { + var count int + for _, l := range lines { + _, out := aoc.Cut(l, " | ") + for _, p := range strings.Fields(out) { + if _, ok := easy(p); ok { + count++ + } + } + } + return count +} + +func part2(lines []string) int { + var sum int + for _, l := range lines { + pats, out := aoc.Cut(l, " | ") + d := infer(strings.Fields(pats)) + n, err := d.decode(strings.Fields(out)) + if err != nil { + panic(err) + } + sum += n + } + return sum +} + +func easy(segments string) (int, bool) { + switch len(segments) { + // 0 segments => {} + // 1 segments => {} + case 2: + return 1, true + case 3: + return 7, true + case 4: + return 4, true + case 7: + return 8, true + default: + // 5 segments => {2, 3, 5} + // 6 segments => {0, 6, 9} + return -1, false + } +} + +// AA +// B C +// DD +// E F +// GG +type Display struct { + A, B, C, D, E, F, G string +} + +func (d *Display) String() string { + return strings.Join([]string{d.A, d.B, d.C, d.D, d.E, d.F, d.G}, " ") +} + +func infer(pats []string) *Display { + var one, four, seven, eight stringset.StringSet + var fives, sixes []stringset.StringSet + for _, pat := range pats { + s := stringset.Runes(pat) + switch len(s) { + case 2: + one = s + case 3: + seven = s + case 4: + four = s + case 5: + fives = append(fives, s) + case 6: + sixes = append(sixes, s) + case 7: + eight = s + default: + panic("WTF") + } + } + a := seven.Minus(one) + ifives := stringset.Intersect(fives...) + g := ifives.Minus(seven.Union(four)) + d := ifives.Minus(a.Union(g)) + e := eight.Minus(four.Union(a).Union(g)) + five := stringset.Intersect(sixes...).Union(d) + six := five.Union(e) + c := eight.Minus(six) + // good + two := stringset.Union(a, c, d, e, g) + var three stringset.StringSet + for _, s := range fives { + if s.Equal(five) || s.Equal(two) { + continue + } + three = s + } + if len(three) == 0 { + panic("WAT") + } + f := three.Minus(two) + b := eight.Minus(stringset.Union(a, c, d, e, f, g)) + + return &Display{ + A: a.Only(), + B: b.Only(), + C: c.Only(), + D: d.Only(), + E: e.Only(), + F: f.Only(), + G: g.Only(), + } +} + +func (d *Display) decode(pats []string) (int, error) { + var n int + for _, p := range pats { + n *= 10 + x, err := d.decodeSingle(p) + if err != nil { + return -1, err + } + n += x + } + return n, nil +} + +func (d *Display) decodeSingle(pat string) (int, error) { + s := stringset.Runes(pat) + t0 := stringset.Slice([]string{d.A, d.B, d.C, d.E, d.F, d.G}) + t1 := stringset.Slice([]string{d.C, d.F}) + t2 := stringset.Slice([]string{d.A, d.C, d.D, d.E, d.G}) + t3 := stringset.Slice([]string{d.A, d.C, d.D, d.F, d.G}) + t4 := stringset.Slice([]string{d.B, d.C, d.D, d.F}) + t5 := stringset.Slice([]string{d.A, d.B, d.D, d.F, d.G}) + t6 := stringset.Slice([]string{d.A, d.B, d.D, d.E, d.F, d.G}) + t7 := stringset.Slice([]string{d.A, d.C, d.F}) + t8 := stringset.Slice([]string{d.A, d.B, d.C, d.D, d.E, d.F, d.G}) + t9 := stringset.Slice([]string{d.A, d.B, d.C, d.D, d.F, d.G}) + if s.Equal(t0) { + return 0, nil + } + if s.Equal(t1) { + return 1, nil + } + if s.Equal(t2) { + return 2, nil + } + if s.Equal(t3) { + return 3, nil + } + if s.Equal(t4) { + return 4, nil + } + if s.Equal(t5) { + return 5, nil + } + if s.Equal(t6) { + return 6, nil + } + if s.Equal(t7) { + return 7, nil + } + if s.Equal(t8) { + return 8, nil + } + if s.Equal(t9) { + return 9, nil + } + return -1, fmt.Errorf("decode segments: %s", s) +}