day20, part2 done with online LCM calculator

This commit is contained in:
efim 2023-12-20 13:16:13 +00:00
parent 57fdfb01cb
commit 98206fe6d4
4 changed files with 318 additions and 13 deletions

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
}

View File

@ -2,7 +2,6 @@ package day20
import ( import (
"fmt" "fmt"
"log"
"regexp" "regexp"
"slices" "slices"
"strings" "strings"
@ -93,7 +92,7 @@ func ParseFlipFlop(line string) (result FlipFlop) {
re := regexp.MustCompile(`%(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`) re := regexp.MustCompile(`%(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`)
matches := re.FindStringSubmatch(line) matches := re.FindStringSubmatch(line)
log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2]) // log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2])
result.Name = matches[1] result.Name = matches[1]
result.OutputNames = strings.Split(matches[2], ", ") result.OutputNames = strings.Split(matches[2], ", ")
@ -220,7 +219,7 @@ func ParseConjunction(line string) (result Conjunction) {
re := regexp.MustCompile(`&(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`) re := regexp.MustCompile(`&(?P<NAME>\D+) -> (?P<OUTPUTS>.+)`)
matches := re.FindStringSubmatch(line) matches := re.FindStringSubmatch(line)
log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2]) // log.Printf("matching %s getting '%s' and '%s'\n", line, matches[1], matches[2])
result.Name = matches[1] result.Name = matches[1]
result.OutputNames = strings.Split(matches[2], ", ") result.OutputNames = strings.Split(matches[2], ", ")
@ -256,7 +255,7 @@ func (b *Button)MermaidFlow() string {
type Output struct {} type Output struct {}
func (o *Output)Receive(s Signal) []Signal { func (o *Output)Receive(s Signal) []Signal {
log.Print("Outut received signal: ", s) // log.Print("Outut received signal: ", s)
return []Signal{} return []Signal{}
} }

View File

@ -67,3 +67,108 @@ but first let's check if loops over
- zl - zl
are manageable. 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

View File

@ -13,15 +13,39 @@ func Run() int {
filename := "day20/input" filename := "day20/input"
modules := ReadModules(filename) modules := ReadModules(filename)
InitStuffs(modules) InitStuffs(modules)
log.Print("got modules:\n", modules) // log.Print("got modules:\n", modules)
var low, high int // var low, high int
// low, high = Count10000ButtonPresses(modules) // low, high = Count10000ButtonPresses(modules)
log.Printf("got low %d and high %d\n", low, high) // log.Printf("got low %d and high %d\n", low, high)
fmt.Println(AllMermaidFlowChard(modules)) CheckSubgraphsStuff(modules)
return low * high 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) { func Count10000ButtonPresses(modules map[string]Module) (lowSignalsCount, highSignalsCount int) {
@ -108,6 +132,41 @@ func PropagateButtonPress(modules map[string]Module, i int) (lowSignalsCount, hi
return 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" // process sends single `low pulse` directly to "broadcast"
func ReadModules(filename string) map[string]Module { func ReadModules(filename string) map[string]Module {
@ -132,7 +191,7 @@ func ReadModules(filename string) map[string]Module {
result[parsed.Name] = &parsed result[parsed.Name] = &parsed
} }
log.Println(line) // log.Println(line)
} }
buttonModule := Button{} buttonModule := Button{}
@ -152,11 +211,11 @@ func InitStuffs(allModules map[string]Module) {
} }
func ModulesState(allModules map[string]Module) string { func ModulesState(modules map[string]Module) string {
// relying on printing of map values to be ordered by key // relying on printing of map values to be ordered by key
// https://stackoverflow.com/a/54524991/2788805 // https://stackoverflow.com/a/54524991/2788805
states := make(map[string]string) states := make(map[string]string)
for name, module := range allModules { for name, module := range modules {
states[name] = module.StateSnapshot() states[name] = module.StateSnapshot()
} }
@ -164,7 +223,7 @@ func ModulesState(allModules map[string]Module) string {
} }
func AllMermaidFlowChard(allModules map[string]Module) (result string) { func AllMermaidFlowChard(allModules map[string]Module) (result string) {
result = "flowchart LR\n" result = "flowchart TD\n"
for _, module := range allModules { for _, module := range allModules {
result += module.MermaidFlow() result += module.MermaidFlow()
} }