From 98206fe6d4d8b41c81b006ebba6b5f92d46df80a Mon Sep 17 00:00:00 2001 From: efim Date: Wed, 20 Dec 2023 13:16:13 +0000 Subject: [PATCH] day20, part2 done with online LCM calculator --- day20/looping.go | 142 ++++++++++++++++++++++++++++++++++++++ day20/modules.go | 7 +- day20/notes.org | 105 ++++++++++++++++++++++++++++ day20/pulsePropagation.go | 77 ++++++++++++++++++--- 4 files changed, 318 insertions(+), 13 deletions(-) create mode 100644 day20/looping.go diff --git a/day20/looping.go b/day20/looping.go new file mode 100644 index 0000000..559b2b5 --- /dev/null +++ b/day20/looping.go @@ -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 +} diff --git a/day20/modules.go b/day20/modules.go index 6aaffe1..3af62fd 100644 --- a/day20/modules.go +++ b/day20/modules.go @@ -2,7 +2,6 @@ package day20 import ( "fmt" - "log" "regexp" "slices" "strings" @@ -93,7 +92,7 @@ func ParseFlipFlop(line string) (result FlipFlop) { re := regexp.MustCompile(`%(?P\D+) -> (?P.+)`) 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.OutputNames = strings.Split(matches[2], ", ") @@ -220,7 +219,7 @@ func ParseConjunction(line string) (result Conjunction) { re := regexp.MustCompile(`&(?P\D+) -> (?P.+)`) 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.OutputNames = strings.Split(matches[2], ", ") @@ -256,7 +255,7 @@ func (b *Button)MermaidFlow() string { type Output struct {} func (o *Output)Receive(s Signal) []Signal { - log.Print("Outut received signal: ", s) + // log.Print("Outut received signal: ", s) return []Signal{} } diff --git a/day20/notes.org b/day20/notes.org index 2b0717a..154be40 100644 --- a/day20/notes.org +++ b/day20/notes.org @@ -67,3 +67,108 @@ but first let's check if loops over - 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 diff --git a/day20/pulsePropagation.go b/day20/pulsePropagation.go index 652ab20..f6fc7e9 100644 --- a/day20/pulsePropagation.go +++ b/day20/pulsePropagation.go @@ -13,15 +13,39 @@ func Run() int { filename := "day20/input" modules := ReadModules(filename) 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) - 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) { @@ -108,6 +132,41 @@ func PropagateButtonPress(modules map[string]Module, i int) (lowSignalsCount, hi 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 { @@ -132,7 +191,7 @@ func ReadModules(filename string) map[string]Module { result[parsed.Name] = &parsed } - log.Println(line) + // log.Println(line) } 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 // https://stackoverflow.com/a/54524991/2788805 states := make(map[string]string) - for name, module := range allModules { + for name, module := range modules { states[name] = module.StateSnapshot() } @@ -164,7 +223,7 @@ func ModulesState(allModules map[string]Module) string { } func AllMermaidFlowChard(allModules map[string]Module) (result string) { - result = "flowchart LR\n" + result = "flowchart TD\n" for _, module := range allModules { result += module.MermaidFlow() }