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