Minor cleanup to day 22
parent
e0997658d8
commit
b0a87880cc
|
@ -94,62 +94,21 @@ func main() {
|
||||||
|
|
||||||
func part1(inputBricks []Brick) int {
|
func part1(inputBricks []Brick) int {
|
||||||
slammedBricks := settleBricks(inputBricks)
|
slammedBricks := settleBricks(inputBricks)
|
||||||
incoming, outgoing := buildBrickGraph(slammedBricks)
|
removable := removableBricks(slammedBricks)
|
||||||
removable := removableBricks(slammedBricks, incoming, outgoing)
|
|
||||||
|
|
||||||
return len(removable)
|
return len(removable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func part2(inputBricks []Brick) int {
|
func part2(inputBricks []Brick) int {
|
||||||
slammedBricks := settleBricks(inputBricks)
|
slammedBricks := settleBricks(inputBricks)
|
||||||
incoming, outgoing := buildBrickGraph(slammedBricks)
|
|
||||||
total := 0
|
total := 0
|
||||||
for i := range slammedBricks {
|
for i := range slammedBricks {
|
||||||
stableNodes := map[int]struct{}{}
|
total += numBricksFallingByRemoval(slammedBricks, i)
|
||||||
lastFalling := map[int]struct{}{}
|
|
||||||
for {
|
|
||||||
reachableNodes := outgoing.ReachableFromExcluding(i, stableNodes)
|
|
||||||
for reachable := range reachableNodes {
|
|
||||||
for _, parentOfReachable := range incoming[reachable] {
|
|
||||||
if _, ok := reachableNodes[parentOfReachable]; !ok && parentOfReachable != i {
|
|
||||||
// If any reachable node is accessible from another subgraph, it is "stable"
|
|
||||||
stableNodes[reachable] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
falling := outgoing.ReachableFromExcluding(i, stableNodes)
|
|
||||||
shouldBreak := mapKeysEqual(falling, lastFalling)
|
|
||||||
lastFalling = falling
|
|
||||||
if shouldBreak {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// We must repeat this process until we reach a state where no more stable nodes are found
|
|
||||||
// There are some cases where a node might be stable, but the children of said stable node
|
|
||||||
// must also be considered invalidated (think of it as second-order stability)
|
|
||||||
}
|
|
||||||
|
|
||||||
total += len(lastFalling)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapKeysEqual[T comparable, U any](m1, m2 map[T]U) bool {
|
|
||||||
if len(m1) != len(m2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for key := range m1 {
|
|
||||||
if _, ok := m2[key]; !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func settleBricks(bricks []Brick) []Brick {
|
func settleBricks(bricks []Brick) []Brick {
|
||||||
sorted := slices.Clone(bricks)
|
sorted := slices.Clone(bricks)
|
||||||
sortByHeight(sorted)
|
sortByHeight(sorted)
|
||||||
|
@ -214,7 +173,9 @@ func buildBrickGraph(bricks []Brick) (incoming, outgoing BrickGraph) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func removableBricks(allBricks []Brick, incoming BrickGraph, outgoing BrickGraph) []int {
|
func removableBricks(allBricks []Brick) []int {
|
||||||
|
incoming, outgoing := buildBrickGraph(allBricks)
|
||||||
|
|
||||||
removable := []int{}
|
removable := []int{}
|
||||||
for i := range allBricks {
|
for i := range allBricks {
|
||||||
dependents := outgoing[i]
|
dependents := outgoing[i]
|
||||||
|
@ -237,6 +198,38 @@ func removableBricks(allBricks []Brick, incoming BrickGraph, outgoing BrickGraph
|
||||||
return removable
|
return removable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func numBricksFallingByRemoval(allBricks []Brick, removeBrick int) int {
|
||||||
|
if removeBrick >= len(allBricks) {
|
||||||
|
panic("cannot remove brick not in bricks list")
|
||||||
|
}
|
||||||
|
|
||||||
|
incoming, outgoing := buildBrickGraph(allBricks)
|
||||||
|
stableNodes := map[int]struct{}{}
|
||||||
|
lastFalling := map[int]struct{}{}
|
||||||
|
for {
|
||||||
|
reachableNodes := outgoing.ReachableFromExcluding(removeBrick, stableNodes)
|
||||||
|
for reachable := range reachableNodes {
|
||||||
|
for _, parentOfReachable := range incoming[reachable] {
|
||||||
|
if _, ok := reachableNodes[parentOfReachable]; !ok && parentOfReachable != removeBrick {
|
||||||
|
// If any reachable node is accessible from another subgraph, it is "stable"
|
||||||
|
stableNodes[reachable] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
falling := outgoing.ReachableFromExcluding(removeBrick, stableNodes)
|
||||||
|
if mapKeysEqual(falling, lastFalling) {
|
||||||
|
return len(lastFalling)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFalling = falling
|
||||||
|
|
||||||
|
// We must repeat this process until we reach a state where no more stable nodes are found
|
||||||
|
// There are some cases where a node might be stable, but the children of said stable node
|
||||||
|
// must also be considered invalidated (think of it as second-order stability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func sortByHeight(bricks []Brick) {
|
func sortByHeight(bricks []Brick) {
|
||||||
slices.SortFunc(bricks, func(brick1, brick2 Brick) int {
|
slices.SortFunc(bricks, func(brick1, brick2 Brick) int {
|
||||||
min1Z := brick1.LowestPoint()
|
min1Z := brick1.LowestPoint()
|
||||||
|
@ -341,3 +334,17 @@ func tryParse[T any](items []string, parse func(string) (T, error)) ([]T, error)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapKeysEqual[T comparable, U any](m1, m2 map[T]U) bool {
|
||||||
|
if len(m1) != len(m2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range m1 {
|
||||||
|
if _, ok := m2[key]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue