2023-20 phew part 2 was scary at first

This commit is contained in:
alexchao26
2024-08-13 13:53:28 -04:00
parent c06369e6ff
commit c7f2581dc7
2 changed files with 273 additions and 0 deletions
+215
View File
@@ -0,0 +1,215 @@
package main
import (
_ "embed"
"flag"
"fmt"
"math"
"strings"
"github.com/alexchao26/advent-of-code-go/util"
)
//go:embed input.txt
var input string
func init() {
// do this in init (not main) so test file has same input
input = strings.TrimRight(input, "\n")
if len(input) == 0 {
panic("empty input.txt file")
}
}
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
ans := pulsePropagation(input, part)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
func pulsePropagation(input string, part int) int {
modules := parseInput(input)
var lowPulses, highPulses int
buttonPresses := 1000
if part == 2 {
// let this cycle infinitely so I can figure out cycle times for part 2
buttonPresses = math.MaxInt64
}
// for part 2:
// looking at input, rx's only input is &lb, which is a conjunction, so needs to get ALL high signals to send a low to rx
// lb is fed from four other modules that all need to send high signals:
// &rz, &lf, &br, &fk
// figuring out the cycle times of these four then maybe the LCM will be the answer if the input is kind?
lastCycleForHighPulse := map[string]int{
"rz": -1,
"lf": -1,
"br": -1,
"fk": -1,
}
cycles := []int{}
for i := 0; i < buttonPresses; i++ {
if part == 2 && len(cycles) == 4 {
break
}
queue := []pulse{}
queue = append(queue, pulse{
isLowPulse: true,
source: "button",
destination: "broadcaster",
})
for len(queue) > 0 {
p := queue[0]
queue = queue[1:]
if p.isLowPulse {
lowPulses++
} else {
highPulses++
}
if val, ok := lastCycleForHighPulse[p.source]; ok && !p.isLowPulse {
// fmt.Println("found for ", p.source, i+1)
if val == -1 {
lastCycleForHighPulse[p.source] = i + 1
} else {
cycles = append(cycles, (i+1)-val)
}
}
if _, ok := modules[p.destination]; !ok {
continue
}
switch modules[p.destination].moduleType {
case "broadcaster":
for _, dest := range modules[p.destination].destinations {
queue = append(queue, pulse{
isLowPulse: p.isLowPulse,
source: "broadcaster",
destination: dest,
})
}
case "flipflop":
if p.isLowPulse {
for _, dest := range modules[p.destination].destinations {
queue = append(queue, pulse{
// if it was on, it flips off and sends a low pulse
// if it was off, then sends a high pulse (isLowPulse = false)
isLowPulse: modules[p.destination].flipFlopIsOn,
source: p.destination,
destination: dest,
})
}
// flip it
modules[p.destination].flipFlopIsOn = !modules[p.destination].flipFlopIsOn
}
case "conjunction":
modules[p.destination].conjunctionInputsMapWasLastPulseHigh[p.source] = !p.isLowPulse
allHigh := true
for source, wasStrongPulse := range modules[p.destination].conjunctionInputsMapWasLastPulseHigh {
_ = source
if !wasStrongPulse {
allHigh = false
break
}
}
for _, dest := range modules[p.destination].destinations {
queue = append(queue, pulse{
// all high sends a low pulse, otherwise high pulse
isLowPulse: allHigh,
source: p.destination,
destination: dest,
})
}
default:
panic("unexpected module type" + modules[p.destination].moduleType)
}
}
}
// wow that worked, super generous on the inputs...
if part == 2 {
ans := 1
for _, c := range cycles {
ans *= c
}
return ans
}
return lowPulses * highPulses
}
type module struct {
moduleType string
name string
flipFlopIsOn bool
conjunctionInputsMapWasLastPulseHigh map[string]bool
destinations []string
}
type pulse struct {
isLowPulse bool
source, destination string
}
func parseInput(input string) (ans map[string]*module) {
ans = map[string]*module{}
for _, line := range strings.Split(input, "\n") {
parts := strings.Split(line, " -> ")
mod := module{
moduleType: "",
flipFlopIsOn: false,
conjunctionInputsMapWasLastPulseHigh: map[string]bool{},
destinations: []string{},
}
if parts[0] == "broadcaster" {
mod.moduleType = "broadcaster"
mod.name = "broadcaster"
mod.destinations = strings.Split(parts[1], ", ")
} else if parts[0][:1] == "%" {
mod.moduleType = "flipflop"
mod.name = parts[0][1:]
mod.destinations = strings.Split(parts[1], ", ")
} else if parts[0][:1] == "&" {
mod.moduleType = "conjunction"
mod.name = parts[0][1:]
mod.destinations = strings.Split(parts[1], ", ")
} else {
panic("unidentified module type: " + line)
}
ans[mod.name] = &mod
}
// initialize conjunction maps with all their source modules
for name, module := range ans {
for _, dest := range module.destinations {
if _, ok := ans[dest]; !ok {
continue
}
if ans[dest].moduleType == "conjunction" {
ans[dest].conjunctionInputsMapWasLastPulseHigh[name] = false
}
}
}
return ans
}
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"testing"
)
var example = `broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a`
var example2 = `broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output`
func Test_pulsePropagation(t *testing.T) {
tests := []struct {
name string
input string
part int
want int
}{
{
name: "example",
input: example,
part: 1,
want: 32000000,
},
{
name: "example2",
input: example2,
part: 1,
want: 11687500,
},
{
name: "actual",
input: input,
part: 1,
want: 817896682,
},
{
name: "actual",
input: input,
part: 2,
want: 250924073918341,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := pulsePropagation(tt.input, tt.part); got != tt.want {
t.Errorf("pulsePropagation() = %v, want %v", got, tt.want)
}
})
}
}