Compare commits

...

9 Commits

Author SHA1 Message Date
efim 98206fe6d4 day20, part2 done with online LCM calculator 2023-12-20 13:16:13 +00:00
efim 57fdfb01cb day20: whelp. 2023-12-20 11:14:45 +00:00
efim 1e32ec0988 day20, part1 2023-12-20 10:19:24 +00:00
efim 6c061375ce day20, examples work in test 2023-12-20 10:16:43 +00:00
efim f538945dff day20, modules state comparations 2023-12-20 09:58:40 +00:00
efim 00e60657fa day20, example 2 first four steps pass 2023-12-20 09:46:22 +00:00
efim 1d7a0ef7b8 day20, receive functions 2023-12-20 08:54:12 +00:00
efim 4974127cef day20: more reading 2023-12-20 08:10:10 +00:00
efim 9dbc2ca205 day20, starting to read in data
with using tests as entry points for checking things
2023-12-20 07:35:51 +00:00
13 changed files with 1197 additions and 17 deletions

3
.gitignore vendored
View File

@ -1,4 +1,3 @@
/.direnv/
/.go
example
/day19/input
input

17
day19/example Normal file
View File

@ -0,0 +1,17 @@
px{a<2006:qkq,m>2090:A,rfg}
pv{a>1716:R,A}
lnx{m>1548:A,A}
rfg{s<537:gd,x>2440:R,A}
qs{s>3448:A,lnx}
qkq{x<1416:A,crn}
crn{x>2662:A,R}
in{s<1351:px,qqz}
qqz{s>2770:qs,m<1801:hdj,R}
gd{a>3333:R,R}
hdj{m>838:A,pv}
{x=787,m=2655,a=1222,s=2876}
{x=1679,m=44,a=2067,s=496}
{x=2036,m=264,a=79,s=2244}
{x=2461,m=1339,a=466,s=291}
{x=2127,m=1623,a=2188,s=1013}

5
day20/example1 Normal file
View File

@ -0,0 +1,5 @@
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a

5
day20/example2 Normal file
View File

@ -0,0 +1,5 @@
broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output

142
day20/looping.go Normal file
View File

@ -0,0 +1,142 @@
package day20
import (
"cmp"
"log"
"slices"
)
func TransitiveOutputs(from string, allModules map[string]Module, visited map[string]any) map[string]any {
// log.Printf("looking for transitive children of %s\n", from)
_, alreadyProcessed := visited[from]
if alreadyProcessed {
return visited
}
module, found := allModules[from]
if !found {
return visited
}
visited[from] = struct{}{}
children := module.Outputs()
for _, output := range children {
TransitiveOutputs(output, allModules, visited)
}
delete(visited, "th")
// for key, _ := range visited {
// result = append(result, key)
// }
return visited
}
func FindSubGraphLoopLength(subgraph map[string]any, allModules map[string]Module, monitorOutputsOf string) (fromStep, toStep int, monitoredPulses map[int][]PulseType) {
step := 1
seenSubgraphStates := make(map[string]int)
monitoredPulses = make(map[int][]PulseType)
for {
subgraphModules := make(map[string]Module)
for key, _ := range subgraph {
subgraphModules[key] = allModules[key]
}
subgraphState := ModulesState(subgraphModules)
// log.Printf("looping %d. state is %s", step, subgraphState)
prevSteps, known := seenSubgraphStates[subgraphState]
if known {
// log.Printf(">>> searching for loop of %+v", subgraph)
log.Printf(">>> found loop from %d to %d. of size %d\n", prevSteps, step - 1, step - prevSteps)
return prevSteps, step, monitoredPulses
}
seenSubgraphStates[subgraphState] = step
monitoredPulsesOfTheStep := PropagateButtonPressWithMonitor(allModules, step, monitorOutputsOf)
if len(monitoredPulsesOfTheStep) > 0 {
monitoredPulses[step] = monitoredPulsesOfTheStep
}
step++
}
panic("")
}
// i see lot's of 'LowPulse'
// while i want to find steps where all inputs are remembered as High.
// so i'm interested in steps with "high" pulses and next steps that make it 'low' after
func FilterMonitoredPulses(requestedPulses map[int][]PulseType) {
afterHigh := false
for step, pulses := range requestedPulses {
processedPulses := make([]PulseType, 0)
for _, pulse := range pulses {
if pulse == HighPulse {
processedPulses = append(processedPulses, pulse)
afterHigh = true
continue
}
if afterHigh {
processedPulses = append(processedPulses, pulse)
afterHigh = false
}
}
if len(processedPulses) > 0 {
requestedPulses[step] = processedPulses
} else {
delete(requestedPulses, step)
}
}
}
// loop math
// 2023/12/20 12:35:08 >>> searching for loop of sr
// 2023/12/20 12:35:08 >>> found loop from 1 to 4028. of size 4028
// 2023/12/20 12:35:08 the pulses: +map[4026:[high low]]
// 2023/12/20 12:35:08 >>> searching for loop of ch
// 2023/12/20 12:35:08 >>> found loop from 0 to 3923. of size 3924
// 2023/12/20 12:35:08 the pulses: +map[3817:[high low]]
// 2023/12/20 12:35:08 >>> searching for loop of hd
// 2023/12/20 12:35:09 >>> found loop from 0 to 3793. of size 3794
// 2023/12/20 12:35:09 the pulses: +map[3427:[high low]]
// 2023/12/20 12:35:09 >>> searching for loop of bx
// 2023/12/20 12:35:09 >>> found loop from 0 to 3739. of size 3740
// 2023/12/20 12:35:09 the pulses: +map[3211:[high low]]
func CalcCommonStep() int {
type LoopInfo struct {
loopLength, initialDesiredStep int
curStep int
}
loopA := &LoopInfo{4027, 4026, 4026}
loopB := &LoopInfo{3923, 3922, 3922}
loopC := &LoopInfo{3793, 3792, 3792}
loopD := &LoopInfo{3739, 3211, 3738}
// nope they can have different amount of own loops.
// so it's 4 unknowns, 5 unknowns.
// i could store 4 'steps' and on each iteration increase the smallest one
// until they are all equal
loops := []*LoopInfo{loopA, loopB, loopC, loopD}
allSameStep := loopA.curStep == loopB.curStep &&
loopB.curStep == loopC.curStep &&
loopC.curStep == loopD.curStep
i := 0
for !allSameStep {
minLoop := slices.MinFunc(loops, func(a *LoopInfo, b *LoopInfo) int {
return cmp.Compare(a.curStep, b.curStep)
})
minLoop.curStep += minLoop.loopLength
if i % 10000000 == 0 {
log.Printf(">> iterations made: %d, min step is %d", i, minLoop.curStep)
}
i++
}
return loopA.curStep
}

276
day20/modules.go Normal file
View File

@ -0,0 +1,276 @@
package day20
import (
"fmt"
"regexp"
"slices"
"strings"
)
type PulseType int
func (pt PulseType)String() string {
types := []string{"high", "low"}
return types[pt]
}
const (
HighPulse PulseType = iota
LowPulse
)
type Signal struct {
To, From string
PulseType PulseType
}
type Module interface {
Receive(s Signal) []Signal
Outputs() []string
StateSnapshot() string
MermaidFlow() string
}
// Modules
type FlipFlop struct {
Name string
OutputNames []string
IsOn bool
}
// ignores HighPulse
// on LowPulse - toggle state and send signal
func (ff *FlipFlop)Receive(s Signal) []Signal {
if s.PulseType == HighPulse {
return []Signal{}
}
ff.IsOn = !ff.IsOn
outTemplate := Signal{
From: ff.Name,
}
if ff.IsOn {
outTemplate.PulseType = HighPulse
} else {
outTemplate.PulseType = LowPulse
}
result := make([]Signal, len(ff.OutputNames))
for i, outName := range ff.OutputNames {
out := outTemplate
out.To = outName
result[i] = out
}
return result
}
func (ff *FlipFlop)Outputs() []string {
return ff.OutputNames
}
func (ff *FlipFlop)String() string {
return fmt.Sprintf("[flip-flop '%s' (on: %t) -> %s]", ff.Name, ff.IsOn, ff.OutputNames)
}
func (ff *FlipFlop)StateSnapshot() string {
return ff.String()
}
func (ff *FlipFlop)MermaidFlow() string {
result := "\n"
for _, toName := range ff.OutputNames {
result += fmt.Sprintf("%s --> %s\n", ff.Name, toName)
}
return result
}
func IsLineFlipFlop(line string) bool {
return strings.HasPrefix(line, "%")
}
func ParseFlipFlop(line string) (result FlipFlop) {
re := regexp.MustCompile(`%(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`)
matches := re.FindStringSubmatch(line)
// log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2])
result.Name = matches[1]
result.OutputNames = strings.Split(matches[2], ", ")
return
}
type Broadcast struct {
OutputNames []string
}
// send same pulse to all outputs
func (b *Broadcast)Receive(s Signal) (result []Signal) {
signalTemplate := Signal{From: "broadcast", PulseType: s.PulseType}
for _, out := range b.OutputNames {
outSignal := signalTemplate
outSignal.To = out
result = append(result, outSignal)
}
return
}
func (b *Broadcast)Outputs() []string {
return b.OutputNames
}
func (b *Broadcast)String() string {
return fmt.Sprintf("[broadcast -> %+v]", b.OutputNames)
}
func (b *Broadcast)StateSnapshot() string {
return b.String()
}
func (b *Broadcast)MermaidFlow() string {
result := "\n"
for _, toName := range b.OutputNames {
result += fmt.Sprintf("%s --> %s\n", "broadcast", toName)
}
return result
}
func IsLineBroadcast(line string) bool {
return strings.HasPrefix(line, "broadcaster")
}
func ParseBroadcast(line string) (result Broadcast) {
re := regexp.MustCompile(`broadcaster -> (?P<OUTPUTS>.+)`)
matches := re.FindStringSubmatch(line)
result.OutputNames = strings.Split(matches[1], ", ")
return
}
type Conjunction struct {
Name string
OutputNames []string
MostRecentPulseFromInputIsHigh map[string]bool
}
// remembers last signal type from all inputs (initial default is Low)
// when receiving pulse, first update memory for that input
// then if for all inputs remembered is high - send LowPulse
// otherwise if some remembers are low - send HighPulse
func (c *Conjunction)Receive(s Signal) (result []Signal) {
c.MostRecentPulseFromInputIsHigh[s.From] = s.PulseType == HighPulse
allHigh := true
for _, latestImpulseHight := range c.MostRecentPulseFromInputIsHigh {
if !latestImpulseHight {
allHigh = false
break
}
}
outTemplate := Signal{From: c.Name}
if allHigh {
outTemplate.PulseType = LowPulse
} else {
outTemplate.PulseType = HighPulse
}
for _, outName := range c.OutputNames {
outSignal := outTemplate
outSignal.To = outName
result = append(result, outSignal)
}
return
}
func (c *Conjunction)Outputs() []string {
return c.OutputNames
}
func (c *Conjunction)RegisterInputs(allModules map[string]Module) {
for name, module := range allModules {
if slices.Contains( module.Outputs(), c.Name) {
c.MostRecentPulseFromInputIsHigh[name] = false
}
}
}
func (c *Conjunction)String() string {
return fmt.Sprintf("[conjunction '%s' -> %+v]", c.Name, c.OutputNames)
}
func (c *Conjunction)StateSnapshot() string {
return fmt.Sprintf("[conjunction '%s' -> %+v]", c.Name, c.MostRecentPulseFromInputIsHigh)
}
func (c *Conjunction)MermaidFlow() string {
result := "\n"
for _, toName := range c.OutputNames {
result += fmt.Sprintf("%s --> %s\n", c.Name, toName)
}
return result
}
func IsLineConjunction(line string) bool {
return strings.HasPrefix(line, "&")
}
func ParseConjunction(line string) (result Conjunction) {
re := regexp.MustCompile(`&(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`)
matches := re.FindStringSubmatch(line)
// log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2])
result.Name = matches[1]
result.OutputNames = strings.Split(matches[2], ", ")
result.MostRecentPulseFromInputIsHigh = map[string]bool{}
return
}
type Button struct {}
func (b *Button)Receive(s Signal) []Signal {
return []Signal{
{ To: "broadcast", From: "button", PulseType: LowPulse },
}
}
func (b *Button)Outputs() []string {
return []string{"broadcast"}
}
func (b *Button)String() string {
return "[button]"
}
func (b *Button)StateSnapshot() string {
return b.String()
}
func (b *Button)MermaidFlow() string {
return "button --> broadcast\n"
}
type Output struct {}
func (o *Output)Receive(s Signal) []Signal {
// log.Print("Outut received signal: ", s)
return []Signal{}
}
func (o *Output)Outputs() []string {
return []string{}
}
func (o *Output)String() string {
return "[output]"
}
func (o *Output)StateSnapshot() string {
return o.String()
}
func (o *Output)MermaidFlow() string {
return ""
}

61
day20/modules_test.go Normal file
View File

@ -0,0 +1,61 @@
package day20
import (
"slices"
"testing"
)
func TestParseFlipFlop(t *testing.T) {
flipFlopLine := "%a -> inv, con"
if !IsLineFlipFlop(flipFlopLine) {
t.Errorf("line '%s' should be flip flop\n", flipFlopLine)
}
module := ParseFlipFlop(flipFlopLine)
t.Logf("got module %+v\n", module)
}
func TestParseBroadcast(t *testing.T) {
broadcastLine := "broadcaster -> a, b, c"
if !IsLineBroadcast(broadcastLine) {
t.Error("expected line to pass broadcast check")
}
module := ParseBroadcast(broadcastLine)
t.Logf("got module %+v\n", module)
if !slices.Equal(module.OutputNames, []string{"a", "b", "c"}) {
t.Errorf("got unexpected outputs: %+v\n", module.OutputNames)
}
}
func TestParseConjunction(t *testing.T) {
conjunctionLine := "&inv -> b"
if !IsLineConjunction(conjunctionLine) {
t.Errorf("line '%s' should be flip flop\n", conjunctionLine)
}
module := ParseConjunction(conjunctionLine)
t.Logf("got module %+v\n", module)
moduleAsExpected := module.Name != "inv" || slices.Equal(module.OutputNames, []string{"b"})
if !moduleAsExpected {
t.Fail()
}
}
func TestReadManyModules(t *testing.T) {
filename := "example1"
modules := ReadModules(filename)
t.Logf("> read example1:\n%+v", modules)
filename2 := "example2"
modules2 := ReadModules(filename2)
t.Logf("> read example2:\n%+v", modules2)
}
func TestConjunctionRegisterInputs(t *testing.T) {
filename := "example2"
modules := ReadModules(filename)
conjunctionInv := modules["inv"].(*Conjunction)
conjunctionInv.RegisterInputs(modules)
t.Logf("after registering inputs on $inv : %+v", conjunctionInv.MostRecentPulseFromInputIsHigh)
}

170
day20/my-mermaid.mmd Normal file
View File

@ -0,0 +1,170 @@
flowchart LR
zd --> ln
zd --> gf
vz --> cr
vz --> vc
ch --> db
ch --> mc
qm --> gm
cc --> nn
qk --> vc
sr --> gf
sr --> vl
lr --> sb
hv --> lr
cl --> qx
cl --> bf
xm --> db
sf --> bp
tj --> lc
tj --> gf
rz --> qx
rz --> cv
vc --> lr
vc --> hd
vc --> ks
vc --> qn
vc --> gx
vc --> nh
vc --> hv
bf --> qx
bf --> pf
jd --> qx
jd --> vm
ds --> cc
vm --> cl
ff --> pl
th --> rx
gm --> tj
gm --> gf
fj --> zd
mc --> ds
mc --> db
ks --> vz
button --> broadcast
cv --> xz
kt --> qx
kt --> rz
qj --> xm
qj --> db
bx --> qx
bx --> qp
fn --> pr
fn --> gf
qp --> cb
qp --> qx
cd --> pm
cd --> vc
nh --> hv
pl --> sf
pl --> db
qq --> qm
qq --> gf
xf --> th
zl --> th
pf --> qx
qn --> th
jz --> qj
jz --> db
pr --> gf
vl --> gf
vl --> fj
sb --> ks
sb --> vc
cr --> gx
cr --> vc
lc --> gf
lc --> fn
xn --> th
nn --> ff
nn --> db
ln --> gf
ln --> qq
pm --> vc
pm --> qk
xz --> jd
gx --> cd
broadcast --> sr
broadcast --> ch
broadcast --> hd
broadcast --> bx
qx --> cb
qx --> cv
qx --> bx
qx --> xz
qx --> vm
qx --> zl
db --> ff
db --> ds
db --> sf
db --> ch
db --> cc
db --> xf
cb --> kt
bp --> db
bp --> jz
gf --> fj
gf --> qm
gf --> xn
gf --> sr
hd --> vc
hd --> nh

1
day20/my-mermaid.mmd.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 134 KiB

174
day20/notes.org Normal file
View File

@ -0,0 +1,174 @@
#+title: Notes
* ok. only thought i had was to simulate the thing
have single executor, that takes head of the queue,
signals would be (to, from, type)
take 'to' out of the map, call it's 'process(from, type)'
and different types of executors would implement this differently.
and return a slice of new signals in order, to be appended.
if queue is empty - the single button press is propagated and all is well.
we will take snapshot of state, String() repr of all executors should be enough,
and save amount of signals sent so far
* also, i suppose i'd want to have entry points for fiddling with single executors to be test cases.
* modules to implement
** DONE Broadcast
** DONE Flip-Flop
** DONE Conjunction
** DONE Button
* i guess each module could test if string is it's a representation of this type
and would be able to parse it? into it's own struct?
well, those are just functions, since only methods are associated, so ok
* how do i run single tests?
** running tests from the module
#+begin_src bash
go test sunshine.industries/aoc2023/day20 -v
#+end_src
have file with `_test.go` and `func Test...(t *testing.T) {}` name
** running single test
#+begin_src bash
go test sunshine.industries/aoc2023/day20 -v -run TestParseFlipFlop
#+end_src
* yikes. if i don't know the 'inputs' to the conjunction, don't know how to check for 'all high'
let's add registering after the map is read.
* well. for part 2 brute force doesn't work.
how could i examine inputs to the 'rx' to see when it will receive 'low'?
i suppose inputs could be on prime cycles, which would align to all required values only on a very big step?
let's do some kind of visualiztion?
how would i do graphql or mermaidjs?
flowchard in mermaid should be it
go run . > day20/my-mermaid.mmd
* so, looking at the thingy.
rx is produced by &th
which has inputs of
11:&xn -> th
14:&qn -> th
16:&xf -> th
32:&zl -> th
for rx to receive a low pulse.
&th should receive High Pulse, while all other inputs alse remembered as high.
this is not too easy.
but first let's check if loops over
- xn
- qn
- xh
- zl
are manageable.
well.
i'll need to what?
not only track the inputs of the th.
but state of the 'subloop'
and they are separate
is there an easy way to collect the names from each subloop?
i guess i could write a collect.
from each of outputs of 'broadcast'
then have a funciton that checks loop size of each subgraphs
but i will also need to figure out on which steps output of the loop is remembered as High \ Low
let's start with loop size? and modify things if need be
** starting points of loops:
children of the broadcast:
broadcaster -> sr, ch, hd, bx
sr, ch, hd, bx
** ok. some data here
2023/12/20 12:05:06 >>> searching for loop of sr
2023/12/20 12:05:06 >>> found loop from 1 to 4028. of size 4028
2023/12/20 12:05:06 >>> searching for loop of ch
2023/12/20 12:05:06 >>> found loop from 0 to 3923. of size 3924
2023/12/20 12:05:06 >>> searching for loop of hd
2023/12/20 12:05:06 >>> found loop from 0 to 3793. of size 3794
2023/12/20 12:05:06 >>> searching for loop of bx
2023/12/20 12:05:07 >>> found loop from 0 to 3739. of size 3740
one of these guys starts from 1, not from 0.
this is unusual, but OK
now, i want to figure out what are steps where output for the each cycle is 'considered as saved as 1'
i guess i could just directly probe the
`th`
on each step up to 4028
but also, if the signallings from those are rare - would be eaiser to collect steps of each signal.
** ok. i collected 'monitored pulses' and i see lots of 'Low'
what i want is all "high" and first low after those.
** oh wow, this crap
2023/12/20 12:30:05 >>> searching for loop of ch
2023/12/20 12:30:05 >>> found loop from 1 to 3924. of size 3924
2023/12/20 12:30:05 the pulses
+map[3922:[high low]]
2023/12/20 12:30:05 >>> searching for loop of hd
2023/12/20 12:30:05 >>> found loop from 0 to 3793. of size 3794
2023/12/20 12:30:05 the pulses
+map[3661:[high low]]
2023/12/20 12:30:05 >>> searching for loop of bx
2023/12/20 12:30:05 >>> found loop from 0 to 3739. of size 3740
2023/12/20 12:30:05 the pulses
+map[3499:[high low]]
2023/12/20 12:30:05 >>> searching for loop of sr
2023/12/20 12:30:05 >>> found loop from 0 to 4027. of size 4028
2023/12/20 12:30:05 the pulses
+map[624:[high low]]
*** but at least these 'high low' are all on same step.
now with info on loop start, place of pulse in the loop and length of loops,
what is the step so that those [high low] occur on same step num?
*** math should be:
3922 + LOOP_N * (LOOP_LEN)
** wait i now get different output?
2023/12/20 12:57:50 >>> searching for loop of bx
2023/12/20 12:57:50 >>> found loop from 1 to 3739. of size 3739
2023/12/20 12:57:50 the pulses: +map[3738:[high low]]
2023/12/20 12:57:50 >>> searching for loop of sr
2023/12/20 12:57:50 >>> found loop from 0 to 4026. of size 4027
2023/12/20 12:57:50 the pulses: +map[286:[high low]]
2023/12/20 12:57:50 >>> searching for loop of ch
2023/12/20 12:57:50 >>> found loop from 0 to 3922. of size 3923
2023/12/20 12:57:50 the pulses: +map[78:[high low]]
2023/12/20 12:57:50 >>> searching for loop of hd
2023/12/20 12:57:51 >>> found loop from 0 to 3792. of size 3793
2023/12/20 12:57:51 the pulses: +map[3481:[high low]]
** why is my filtering unstable?
** let's check for single loop?
** yikes. but maybe
2023/12/20 13:08:52 >>> searching for loop of sr
2023/12/20 13:08:52 >>> found loop from 2 to 4028. of size 4027
2023/12/20 13:08:52 the pulses: +map[4027:[high low]]
2023/12/20 13:09:23 >>> searching for loop of ch
2023/12/20 13:09:23 >>> found loop from 2 to 3924. of size 3923
2023/12/20 13:09:23 the pulses: +map[3923:[high low]]
2023/12/20 13:09:37 >>> searching for loop of hd
2023/12/20 13:09:37 >>> found loop from 2 to 3794. of size 3793
2023/12/20 13:09:37 the pulses: +map[3793:[high low]]
2023/12/20 13:09:49 >>> searching for loop of bx
2023/12/20 13:09:49 >>> found loop from 2 to 3740. of size 3739
2023/12/20 13:09:49 the pulses: +map[3739:[high low]]
all loops start from same plase.
i could just do 1 press. then the loop starts. and all of them have [high low] on last place.
so it's going to be 1 + least common ...
** aaand, i just did least common multiple of the cycle lenghts.
and i didn't even added 1. which is strange. i guess i did have 'off-by-one'
crap

105
day20/propagation_test.go Normal file
View File

@ -0,0 +1,105 @@
package day20
import (
"log"
"testing"
)
func TestPropagateButtonPressExample1(t *testing.T) {
filename := "example1"
modules := ReadModules(filename)
t.Log("got modules:\n", modules)
low, high := PropagateButtonPress(modules, 0)
t.Logf("got low %d and high %d\n", low, high)
t.Log("modules after single button press:\n", modules)
success := low == 8 && high == 4
if !success {
t.Errorf("expected low 8 got %d, high 4 got %d", low, high)
}
}
func TestPropagateButtonPressExample2(t *testing.T) {
filename := "example2"
modules := ReadModules(filename)
t.Log("got modules:\n", modules)
InitStuffs(modules)
low, high := PropagateButtonPress(modules, 0)
t.Logf("got low %d and high %d\n", low, high)
t.Log("modules after single button press:\n", modules)
success := low == 4 && high == 4
if !success {
t.Errorf("expected low 4 got %d, high 4 got %d", low, high)
}
}
func TestPropagateButtonPressExample2FourSteps(t *testing.T) {
filename := "example2"
modules := ReadModules(filename)
t.Log("got modules:\n", modules)
InitStuffs(modules)
initialModulesState := ModulesState(modules)
low, high := PropagateButtonPress(modules, 0)
t.Logf("got low %d and high %d\n", low, high)
t.Log("#1 button press:\n", modules)
success := low == 4 && high == 4
if !success {
t.Errorf("expected low 4 got %d, high 4 got %d", low, high)
}
low, high = PropagateButtonPress(modules, 0)
t.Logf("got low %d and high %d\n", low, high)
t.Log("#2 button press:\n", modules)
success = low == 4 && high == 2
if !success {
t.Errorf("expected low 4 got %d, high 2 got %d", low, high)
}
secondState := ModulesState(modules)
if initialModulesState == secondState {
t.Error("initial state should be different from second")
}
low, high = PropagateButtonPress(modules, 0)
t.Logf("got low %d and high %d\n", low, high)
t.Log("#3 button press:\n", modules)
success = low == 5 && high == 3
if !success {
t.Errorf("expected low 5 got %d, high 3 got %d", low, high)
}
thirdState := ModulesState(modules)
if initialModulesState == thirdState {
t.Error("initial state should be different from third")
}
low, high = PropagateButtonPress(modules, 0)
t.Logf("got low %d and high %d\n", low, high)
t.Log("#4 button press:\n", modules)
success = low == 4 && high == 2
if !success {
t.Errorf("expected low 4 got %d, high 2 got %d", low, high)
}
lastState := ModulesState(modules)
log.Print("initial modules state:\n", initialModulesState)
log.Print("after 4 steps modules state:\n", lastState)
if initialModulesState != lastState {
t.Error("expected state to be same after 4 steps for example 2")
}
}
func TestExample1TheQuestion(t *testing.T) {
filename := "example1"
modules := ReadModules(filename)
InitStuffs(modules)
low, high := Count10000ButtonPresses(modules)
t.Log("got low and high: ", low, high)
t.Log("response is: ", low * high)
}

232
day20/pulsePropagation.go Normal file
View File

@ -0,0 +1,232 @@
package day20
import (
"fmt"
"log"
"os"
"strings"
)
func Run() int {
// fmt.Println("hello from dya 20")
filename := "day20/input"
modules := ReadModules(filename)
InitStuffs(modules)
// log.Print("got modules:\n", modules)
// var low, high int
// low, high = Count10000ButtonPresses(modules)
// log.Printf("got low %d and high %d\n", low, high)
CheckSubgraphsStuff(modules)
var result int
// result = CalcCommonStep()
return result
}
func CheckSubgraphsStuff(allModules map[string]Module) {
// loopStarts := allModules["broadcast"].Outputs()
// loop start and loop sink
loopItems := map[string]string {
// "sr": "xn",
// "ch": "xf",
// "hd": "qn",
"bx": "zl",
}
for start, end := range loopItems {
log.Printf(">>> searching for loop of %s", start)
themap := make(map[string]any)
loopModules := TransitiveOutputs(start, allModules, themap)
_, _, requestedPulses := FindSubGraphLoopLength(loopModules, allModules, end)
FilterMonitoredPulses(requestedPulses)
log.Printf("the pulses: +%v", requestedPulses)
}
}
func Count10000ButtonPresses(modules map[string]Module) (lowSignalsCount, highSignalsCount int) {
count := 1000
type counts struct {
low, high int
step int
}
countsAfterState := make(map[string]counts)
// after each button press check if reached already known state - cycle is present.
// then calculate amount of signals before the loop - how much was on that previous state.
// then diff - how much added after the loop
// for now let's just print the info on loop
for i := 0; i < count; i++ {
if i % 10000 == 0 {
log.Println("done button presses: ", i)
}
stepLow, stepHigh := PropagateButtonPress(modules, i)
lowSignalsCount += stepLow
highSignalsCount += stepHigh
// log.Printf("after step %d low is %d and high is %d", i, lowSignalsCount, highSignalsCount)
state := ModulesState(modules)
prevCounts, found := countsAfterState[state]
if found {
loopLen := i - prevCounts.step
log.Printf(">>> found loop. from step %d to step %d. of len %d",
prevCounts.step, i, loopLen)
multiplication := count / loopLen
lowCountInCycle := lowSignalsCount - prevCounts.low
highCountInCycle := highSignalsCount - prevCounts.high
lowSignalsCount = lowCountInCycle * multiplication
highSignalsCount = highCountInCycle * multiplication
return
}
countsAfterState[state] = counts{stepLow, stepHigh, i}
}
return
}
func PropagateButtonPress(modules map[string]Module, i int) (lowSignalsCount, highSignalsCount int) {
signals := []Signal{{From: "button", To: "broadcast", PulseType: LowPulse}}
lowSignalsCount += 1
for len(signals) > 0 {
curSignal := signals[0]
signals = signals[1:]
// log.Printf("%s -%s-> %s", curSignal.From, curSignal.PulseType, curSignal.To)
receivingModule, found := modules[curSignal.To]
if !found {
// log.Print(fmt.Sprintf("signal %+v can't find it's recepient\n", curSignal))
if curSignal.To == "rx" && curSignal.PulseType == LowPulse {
panic(fmt.Sprintf("getting low signal to rx, on step %d", i))
}
continue
}
newSignals := receivingModule.Receive(curSignal)
// all newSignals will have same type
newSignalsAmount := len(newSignals)
if newSignalsAmount > 0 {
signals = append(signals, newSignals...)
someNewSignal := newSignals[0]
if someNewSignal.PulseType == HighPulse {
highSignalsCount += newSignalsAmount
} else {
lowSignalsCount += newSignalsAmount
}
}
}
return
}
func PropagateButtonPressWithMonitor(modules map[string]Module, i int, monitorAllOutputsOf string) []PulseType {
result := make([]PulseType, 0)
signals := []Signal{{From: "button", To: "broadcast", PulseType: LowPulse}}
for len(signals) > 0 {
curSignal := signals[0]
signals = signals[1:]
if curSignal.From == monitorAllOutputsOf {
result = append(result, curSignal.PulseType)
}
// log.Printf("%s -%s-> %s", curSignal.From, curSignal.PulseType, curSignal.To)
receivingModule, found := modules[curSignal.To]
if !found {
// log.Print(fmt.Sprintf("signal %+v can't find it's recepient\n", curSignal))
if curSignal.To == "rx" && curSignal.PulseType == LowPulse {
panic(fmt.Sprintf("getting low signal to rx, on step %d", i))
}
continue
}
newSignals := receivingModule.Receive(curSignal)
// all newSignals will have same type
newSignalsAmount := len(newSignals)
if newSignalsAmount > 0 {
signals = append(signals, newSignals...)
}
}
return result
}
// process sends single `low pulse` directly to "broadcast"
func ReadModules(filename string) map[string]Module {
result := make(map[string]Module)
bytes, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprint("error reading file: ", filename))
}
text := strings.TrimSpace(string(bytes))
for _, line := range strings.Split(text, "\n") {
switch {
case IsLineBroadcast(line):
parsed := ParseBroadcast(line)
result["broadcast"] = &parsed
case IsLineFlipFlop(line):
parsed := ParseFlipFlop(line)
result[parsed.Name] = &parsed
case IsLineConjunction(line):
parsed := ParseConjunction(line)
result[parsed.Name] = &parsed
}
// log.Println(line)
}
buttonModule := Button{}
result["button"] = &buttonModule
outputModule := Output{}
result["output"] = &outputModule
return result
}
func InitStuffs(allModules map[string]Module) {
for _, module := range allModules {
if conjunction, ok := module.(*Conjunction); ok {
conjunction.RegisterInputs(allModules)
}
}
}
func ModulesState(modules map[string]Module) string {
// relying on printing of map values to be ordered by key
// https://stackoverflow.com/a/54524991/2788805
states := make(map[string]string)
for name, module := range modules {
states[name] = module.StateSnapshot()
}
return fmt.Sprint(states)
}
func AllMermaidFlowChard(allModules map[string]Module) (result string) {
result = "flowchart TD\n"
for _, module := range allModules {
result += module.MermaidFlow()
}
return
}

23
main.go
View File

@ -2,25 +2,18 @@ package main
import (
"log"
"time"
"sunshine.industries/aoc2023/day19"
"sunshine.industries/aoc2023/day20"
)
func main() {
startTime := time.Now()
log.Print("> starting run:")
// lnx{m>1548:A,A}
// qqz{s>2770:qs,m<1801:hdj,R}
// kt{m>2215:R,x>3386:A,x<3107:R,R}
testSorter := day19.ReadSorterLine("kt{m>2215:R,x>3386:A,x<3107:R,R}")
log.Printf("my test sorter is %+v", testSorter)
simplified := day19.SimplifyOperation(testSorter)
log.Printf("> simplivied %+v", simplified)
detail := day19.ReadDetailLine("{x=787,m=2655,a=1222,s=2876}")
log.Printf("> detail %+v", detail)
result := day19.Run()
log.Printf("\n\nday19 result: %d\n****\n", result)
result := day20.Run()
log.Printf("\n\nday20 result: %d\n****\n", result)
endTime := time.Now()
diff := endTime.Sub(startTime)
log.Printf("execution took %s", diff.String())
}