diff --git a/day20p1/solution.go b/day20p1/solution.go new file mode 100644 index 0000000..7ba46f9 --- /dev/null +++ b/day20p1/solution.go @@ -0,0 +1,262 @@ +package day20p1 + +import ( + "fmt" + "io" + "strconv" + "strings" + + "aoc/utils" +) + +func Solve(r io.Reader) any { + lines := utils.ReadLines(r) + + modules := make(map[string]*module) + for _, ln := range lines { + name, mod := parseLine(ln) + modules[name] = mod + } + + // get all sources for modules + for name, mod := range modules { + for _, d := range mod.destinations { + if md, ok := modules[d]; ok { + md.sources = append(md.sources, name) + } + } + } + + // Set memory for all modules + bitpos := 0 + for name, mod := range modules { + switch mod.kind { + case flipflop: + mod.bitnumber = bitpos + bitpos++ + case conjunction: + mod.bitnumber = bitpos + bitpos += len(mod.sources) + } + + if utils.Verbose { + fmt.Printf("%s %#v\n", name, *mod) + } + } + + bits := NewBitfield(bitpos) + var lowCount, highCount uint64 + + sb := bits.Serialize() + for i := 0; i < 1000; i++ { + res := pressButton(sb, modules) + sb = res.serializedBits + lowCount += res.lowPulses + highCount += res.highPulses + } + + return lowCount * highCount +} + +type pressResult struct { + serializedBits string + lowPulses uint64 + highPulses uint64 +} + +type pulse struct { + source string + destination string + isHigh bool +} + +func pressButton(serializedState string, modules map[string]*module) pressResult { + bits, err := Deserialize(serializedState) + utils.Check(err, "deserialization error for %s", serializedState) + + var lowCount uint64 + var highCount uint64 + + queue := []pulse{{"button", "broadcaster", false}} + + for len(queue) > 0 { + p := queue[0] + queue = queue[1:] + if utils.Verbose { + fmt.Printf("Pulse: %s -> %s high: %t\n", p.source, p.destination, p.isHigh) + } + + switch p.isHigh { + case true: + highCount++ + case false: + lowCount++ + } + + md, ok := modules[p.destination] + + if !ok { + continue + } + + switch md.kind { + case broadcast: + for _, dName := range md.destinations { + queue = append(queue, pulse{p.destination, dName, p.isHigh}) + } + + case flipflop: + if p.isHigh == false { + currentState := bits.Get(md.bitnumber) + newState := !currentState + switch newState { + case true: + bits.Set(md.bitnumber) + case false: + bits.Unset(md.bitnumber) + } + + for _, dName := range md.destinations { + queue = append(queue, pulse{p.destination, dName, newState}) + } + } + case conjunction: + idx := -1 + for i, v := range md.sources { + if v == p.source { + idx = i + break + } + } + if idx < 0 { + panic(fmt.Errorf("did not find %s in sources for module %s", p.source, p.destination)) + } + + switch p.isHigh { + case true: + bits.Set(md.bitnumber + idx) + case false: + bits.Unset(md.bitnumber + idx) + } + + sendHigh := false + for i := md.bitnumber; i < md.bitnumber+len(md.sources); i++ { + if !bits.Get(i) { + sendHigh = true + break + } + } + for _, dName := range md.destinations { + queue = append(queue, pulse{p.destination, dName, sendHigh}) + } + } + } + + res := pressResult{bits.Serialize(), lowCount, highCount} + return res +} + +type modtype int + +const ( + flipflop modtype = iota + conjunction + broadcast +) + +type module struct { + kind modtype + destinations []string + sources []string + bitnumber int +} + +func parseLine(ln string) (string, *module) { + var ret module + var name string + + components := strings.Split(ln, " -> ") + ret.destinations = strings.Split(components[1], ", ") + + switch components[0][0] { + case '%': + // flip flop + ret.kind = flipflop + name = components[0][1:] + case '&': + ret.kind = conjunction + name = components[0][1:] + default: + ret.kind = broadcast + name = components[0] + } + + return name, &ret +} + +// -- the code below was generated by bing chat -- // +// Bitfield represents an arbitrary-length bitfield. +type Bitfield struct { + bits []uint64 +} + +// NewBitfield creates a new Bitfield with the specified number of bits. +func NewBitfield(numBits int) *Bitfield { + numUint64s := (numBits + 63) / 64 + return &Bitfield{ + bits: make([]uint64, numUint64s), + } +} + +// Set sets the specified bit to 1. +func (bf *Bitfield) Set(bitIndex int) { + wordIndex, offset := bitIndex/64, uint(bitIndex%64) + bf.bits[wordIndex] |= (1 << offset) +} + +// Get checks if the specified bit is set (1). +func (bf *Bitfield) Get(bitIndex int) bool { + wordIndex, offset := bitIndex/64, uint(bitIndex%64) + return (bf.bits[wordIndex] & (1 << offset)) != 0 +} + +// Len returns the total number of bits in the bitfield. +func (bf *Bitfield) Len() int { + return len(bf.bits) * 64 +} + +// -- end of bing generated code -- // + +// Unset sets the specified bit to 0 (why was this not included?) +func (bf *Bitfield) Unset(bitIndex int) { + wordIndex, offset := bitIndex/64, uint(bitIndex%64) + bf.bits[wordIndex] &^= (1 << offset) +} + +// Serialize to string +func (bf *Bitfield) Serialize() string { + ret := make([]string, len(bf.bits)) + + for i, b := range bf.bits { + ret[i] = strconv.FormatUint(b, 16) + } + + return strings.Join(ret, ",") +} + +// Deserialize string to bitfield +func Deserialize(s string) (*Bitfield, error) { + components := strings.Split(s, ",") + + bits := make([]uint64, len(components)) + + for i, c := range components { + v, err := strconv.ParseUint(c, 16, 64) + if err != nil { + return nil, err + } + + bits[i] = v + } + return &Bitfield{bits}, nil +} diff --git a/day20p1/solution_test.go b/day20p1/solution_test.go new file mode 100644 index 0000000..16e9c9f --- /dev/null +++ b/day20p1/solution_test.go @@ -0,0 +1,44 @@ +package day20p1 + +import ( + "strings" + "testing" + + "aoc/utils" +) + +var testInput = `broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a` + +var testInput2 = `broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output` + +func TestSolve(t *testing.T) { + tests := []struct { + input string + answer uint64 + }{ + {testInput, 32000000}, + {testInput2, 11687500}, + } + + if testing.Verbose() { + utils.Verbose = true + } + + for _, test := range tests { + r := strings.NewReader(test.input) + + result := Solve(r).(uint64) + + if result != test.answer { + t.Errorf("Expected %d, got %d", test.answer, result) + } + } +} diff --git a/inputs b/inputs index deca866..55ae375 160000 --- a/inputs +++ b/inputs @@ -1 +1 @@ -Subproject commit deca866dd57805556cae1f1b26b9ba65b6e66b3f +Subproject commit 55ae37526917eb805de6b8342dd9481a2848d638