Completed both parts of Day 20 puzzle with help from reddit thread for the second part (detect that there are 4 subgraphs and use LCM)
parent
d8eddf80e3
commit
c431e8741b
@ -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
|
||||
}
|
||||
@ -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)
|
||||
*/
|
||||
}
|
||||
@ -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
|
||||
Loading…
Reference in New Issue