diff --git a/day20/day20.go b/day20/day20.go new file mode 100644 index 0000000..13f1e28 --- /dev/null +++ b/day20/day20.go @@ -0,0 +1,184 @@ +package day20 + +import ( + "fmt" + "strings" +) + +const ( + UNTYPED = "?" + FLIP_FLOP = "%" + BROADCASDTER = "b" + CONJUNCTION = "&" + LOW = 0 + HIGH = 1 +) + +type Module struct { + kind string + name string + status bool + ouputs []string + inputs []string + inputMem map[string]int +} + +type Pulse struct { + kind int + src string + dst string +} + +func parseModules(lines []string) map[string]*Module { + modules := map[string]*Module{} + modulesByOrderOfAppearance := []*Module{} + + for _, line := range lines { + kind := string(line[0]) + parts := strings.Split(line, " -> ") + name := parts[0][1:] + outputs := strings.Split(parts[1], ", ") + + if kind == "b" { + name = "b" + name + } + + module := &Module{kind, name, false, outputs, []string{}, map[string]int{}} + modules[name] = module + modulesByOrderOfAppearance = append(modulesByOrderOfAppearance, module) + } + + // setup inputs + for _, module := range modulesByOrderOfAppearance { + for _, outputName := range module.ouputs { + m, ok := modules[outputName] + + if !ok { + // create ad'hoc untyped module + m = &Module{UNTYPED, outputName, false, []string{}, []string{}, map[string]int{}} + modules[outputName] = m + modulesByOrderOfAppearance = append(modulesByOrderOfAppearance, m) + } + + m.inputs = append(m.inputs, module.name) + m.inputMem[module.name] = LOW + } + } + + return modules +} + +func runSim(modules map[string]*Module, pulseQueue []*Pulse, count int) (int, int) { + countLow := 0 + countHigh := 0 + + for len(pulseQueue) > 0 { + + pulse := pulseQueue[0] + pulseQueue[0] = nil + pulseQueue = pulseQueue[1:] + + module := modules[pulse.dst] + + switch module.kind { + case BROADCASDTER: + for _, dst := range module.ouputs { + newPulse := &Pulse{pulse.kind, module.name, dst} + pulseQueue = append(pulseQueue, newPulse) + if newPulse.kind == LOW { + countLow++ + } else { + countHigh++ + } + } + + case FLIP_FLOP: + if pulse.kind == LOW { + var pulseKind int + if module.status { + pulseKind = LOW + } else { + pulseKind = HIGH + } + module.status = !module.status // flip status + + for _, dst := range module.ouputs { + newPulse := &Pulse{pulseKind, module.name, dst} + pulseQueue = append(pulseQueue, newPulse) + if newPulse.kind == LOW { + countLow++ + } else { + countHigh++ + } + } + } + + case CONJUNCTION: + if pulse.kind == HIGH && module.name == "qn" { + fmt.Println("qn from ", pulse.src, " : ", count) + } + module.inputMem[pulse.src] = pulse.kind + allHigh := true + for _, v := range module.inputMem { + if v == LOW { + allHigh = false + break + } + } + + var pulseKind int + if allHigh { + pulseKind = LOW + } else { + pulseKind = HIGH + } + + for _, dst := range module.ouputs { + newPulse := &Pulse{pulseKind, module.name, dst} + pulseQueue = append(pulseQueue, newPulse) + if newPulse.kind == LOW { + countLow++ + } else { + countHigh++ + } + } + + case UNTYPED: + module.status = pulse.kind == HIGH + } + + } + + return countLow, countHigh +} + +func Part1(lines []string, pressCount int) (map[string]*Module, int) { + modules := parseModules(lines) + + sumLow := 0 + sumHigh := 0 + + for i := 0; i < pressCount; i++ { + queue := []*Pulse{} + queue = append(queue, &Pulse{LOW, "button", "broadcaster"}) + countLow, countHigh := runSim(modules, queue, i+1) + countLow++ + + sumLow += countLow + sumHigh += countHigh + } + + return modules, sumLow * sumHigh +} + +func Part2(lines []string, pressCount int) (map[string]*Module, int) { + modules := parseModules(lines) + + for i := 0; i < pressCount; i++ { + queue := []*Pulse{} + queue = append(queue, &Pulse{LOW, "button", "broadcaster"}) + runSim(modules, queue, i+1) + } + + return modules, 0 +} diff --git a/day20/day20_test.go b/day20/day20_test.go new file mode 100644 index 0000000..dea92a3 --- /dev/null +++ b/day20/day20_test.go @@ -0,0 +1,125 @@ +package day20 + +import ( + "testing" + + "gitea.paas.celticinfo.fr/oabrivard/aoc2023/utils" +) + +func TestParseModule(t *testing.T) { + lines := []string{ + `broadcaster -> a, b, c`, + `%a -> b`, + `%b -> c`, + `%c -> inv`, + `&inv -> a`, + } + + result := parseModules(lines) + + if len(result) != 5 { + t.Fatalf("expected 5, got %d", len(result)) + } +} + +func Test1Part1(t *testing.T) { + lines := []string{ + `broadcaster -> a, b, c`, + `%a -> b`, + `%b -> c`, + `%c -> inv`, + `&inv -> a`, + } + + result, count := Part1(lines, 1) + + if count != 32 { + t.Fatalf("expected 32, got %d", count) + } + + if result["a"].status || result["b"].status || result["c"].status { + t.Fatalf("expected flip-flop a, b and c to be off, got %v %v %v", result["a"].status, result["b"].status, result["c"].status) + } +} + +func Test2Part1(t *testing.T) { + lines := []string{ + `broadcaster -> a, b, c`, + `%a -> b`, + `%b -> c`, + `%c -> inv`, + `&inv -> a`, + } + + _, count := Part1(lines, 1000) + + if count != 32000000 { + t.Fatalf("expected 32000000, got %d", count) + } +} + +func Test3Part1(t *testing.T) { + lines := []string{ + "broadcaster -> a", + "%a -> inv, con", + "&inv -> b", + "%b -> con", + "&con -> output", + } + + result, _ := Part1(lines, 3) + + if !result["output"].status || !result["a"].status || result["b"].status { + t.Fatalf("expected flip-flop a to be on, b to be off and output to be on, got %v %v %v", result["a"].status, result["b"].status, result["output"].status) + } + + result, _ = Part1(lines, 4) + + if !result["output"].status || result["a"].status || result["b"].status { + t.Fatalf("expected flip-flop a and b to be off and output to be on, got %v %v %v", result["a"].status, result["b"].status, result["output"].status) + } +} + +func Test4Part1(t *testing.T) { + lines := []string{ + "broadcaster -> a", + "%a -> inv, con", + "&inv -> b", + "%b -> con", + "&con -> output", + } + + _, count := Part1(lines, 1000) + + if count != 11687500 { + t.Fatalf("expected 11687500, got %d", count) + } +} + +func TestPart1WithInput(t *testing.T) { + lines := utils.ReadLines("input.txt") + + _, count := Part1(lines, 1000) + + if count != 925955316 { + t.Fatalf("expected 925955316, got %d", count) + } +} + +func TestPart2WithInput(t *testing.T) { + lines := utils.ReadLines("input.txt") + + Part2(lines, 5000) + + // use firt four different values printed during simulation that starts with "qn from" + // then find the LCM of the values. The LCM is the solution to this problem. + // Found it thanks to explanation from jwezorek and JuniorBirdman1115 on + // https://www.reddit.com/r/adventofcode/comments/18mmfxb/2023_day_20_solutions/ + /* + qn from jx : 3907 + qn from qz : 3911 + qn from tt : 3931 + qn from cq : 4021 + LCM is 241528477694627 (computed it with https://www.calculatorsoup.com/calculators/math/lcm.php) + */ +} diff --git a/day20/input.txt b/day20/input.txt new file mode 100644 index 0000000..436fa87 --- /dev/null +++ b/day20/input.txt @@ -0,0 +1,58 @@ +%cg -> mt, hb +%sp -> xm +%nr -> hf, mt +broadcaster -> tl, gd, zb, gc +&qz -> qn +%df -> hd +%vg -> rm, kx +%gm -> mt, md +%ls -> hc +%lq -> zq, fx +&zd -> bz, kg, zb, lf, sq, zk, jx +%lz -> mt +%sq -> zk +%zn -> kx, tc +&zq -> mb, hc, qz, ql, tl, ls +&mt -> zm, tt, mh, gd, md +%lm -> mb, zq +%hf -> mt, sm +%hb -> mh, mt +%rm -> kx +%gc -> kx, sp +&cq -> qn +%mh -> jt +%zm -> nr +%xm -> kx, ld +&jx -> qn +&qn -> rx +%mp -> qt, kx +%zk -> vj +%hd -> mp, kx +%tl -> zq, hl +%zb -> zd, ph +%cl -> zd +&tt -> qn +%ld -> zn +%js -> lq, zq +%sm -> mt, lz +%qt -> vg, kx +%md -> cg +%vj -> bz, zd +%qs -> zd, fs +%mb -> ps +&kx -> cq, gc, sp, df, ld +%hc -> lm +%tc -> df, kx +%ps -> js, zq +%fs -> qc, zd +%hl -> jj, zq +%bz -> qs +%jj -> zq, ql +%ql -> ls +%ph -> kg, zd +%qc -> cl, zd +%lf -> sq +%kg -> lf +%fx -> zq +%jt -> zm, mt +%gd -> gm, mt