Add day 22 part 2 solution

master
Nick Krichevsky 2023-12-23 14:57:23 -05:00
parent 30d8c35d61
commit e0997658d8
1 changed files with 133 additions and 68 deletions

View File

@ -23,12 +23,31 @@ type Brick []Coordinate
// BrickGraph is a map from indexes in a brick array to the dependent indexes
type BrickGraph map[int][]int
func (b Brick) HighestPoint() Coordinate {
maxZFunc := func(a, b Coordinate) int {
return cmp.Compare(a.Z, b.Z)
func (graph BrickGraph) ReachableFrom(idx int) map[int]struct{} {
return graph.ReachableFromExcluding(idx, nil)
}
return slices.MaxFunc(b, maxZFunc)
// ReachableFromExcluding will finds all nodes reachable from the given node index, but will not explore neighbors
// in the "excluding" set.
func (graph BrickGraph) ReachableFromExcluding(idx int, excluding map[int]struct{}) map[int]struct{} {
visited := map[int]struct{}{}
toVisit := []int{idx}
for len(toVisit) > 0 {
visiting := toVisit[0]
toVisit = toVisit[1:]
for _, neighbor := range graph[visiting] {
if _, ok := visited[neighbor]; ok {
continue
} else if _, ok := excluding[neighbor]; ok {
continue
}
visited[neighbor] = struct{}{}
toVisit = append(toVisit, neighbor)
}
}
return visited
}
func (b Brick) LowestPoint() Coordinate {
@ -70,14 +89,73 @@ func main() {
}
fmt.Printf("Part 1: %d\n", part1(bricks))
fmt.Printf("Part 2: %d\n", part2(bricks))
}
func part1(inputBricks []Brick) int {
bricks := slices.Clone(inputBricks)
sortByHeight(bricks)
slammedBricks := settleBricks(inputBricks)
incoming, outgoing := buildBrickGraph(slammedBricks)
removable := removableBricks(slammedBricks, incoming, outgoing)
slammedBricks := slices.Clone(bricks)
for i := range bricks {
return len(removable)
}
func part2(inputBricks []Brick) int {
slammedBricks := settleBricks(inputBricks)
incoming, outgoing := buildBrickGraph(slammedBricks)
total := 0
for i := range slammedBricks {
stableNodes := map[int]struct{}{}
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
}
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 {
sorted := slices.Clone(bricks)
sortByHeight(sorted)
slammedBricks := slices.Clone(sorted)
for i := range sorted {
brick, err := moveBrickDown(slammedBricks, i)
if err != nil {
// can't happen with our bounds
@ -87,66 +165,7 @@ func part1(inputBricks []Brick) int {
slammedBricks[i] = brick
}
incoming, outgoing := buildBrickGraph(slammedBricks)
removable := 0
for i := range bricks {
dependents := outgoing[i]
// Nothing depends on this, so it's ok to remove
if len(dependents) == 0 {
removable++
continue
}
allDependentsSafe := true
for _, dependent := range dependents {
// There is more than one item which has this dependent as a dependent, so removing i would
// not allow this to fall
if len(incoming[dependent]) <= 1 {
allDependentsSafe = false
break
}
}
if allDependentsSafe {
removable++
}
}
return removable
}
func buildBrickGraph(bricks []Brick) (incoming, outgoing BrickGraph) {
occupied := occupiedPositions(bricks)
outgoing = make(BrickGraph)
incoming = make(BrickGraph)
for i, brick := range bricks {
neighboring := map[int]struct{}{}
for _, block := range brick {
above := Coordinate{X: block.X, Y: block.Y, Z: block.Z + 1}
if occupiedBy, ok := occupied[above]; ok && occupiedBy != i {
neighboring[occupiedBy] = struct{}{}
}
}
for neighbor := range neighboring {
outgoing[i] = append(outgoing[i], neighbor)
incoming[neighbor] = append(incoming[neighbor], i)
}
}
// start := 'A'
// for i, item := range bricks {
// // name := start + rune(i)
// fmt.Printf("%d[label=\"%d\\n%v\"]\n", i, i, item)
// for _, dependency := range outgoing[i] {
// // depName := start + rune(dependency)
// fmt.Printf("%d -> %d\n", i, dependency)
// }
// }
return
return slammedBricks
}
func moveBrickDown(bricks []Brick, brickIdx int) (Brick, error) {
@ -172,6 +191,52 @@ func moveBrickDown(bricks []Brick, brickIdx int) (Brick, error) {
return brick, nil
}
func buildBrickGraph(bricks []Brick) (incoming, outgoing BrickGraph) {
occupied := occupiedPositions(bricks)
outgoing = make(BrickGraph)
incoming = make(BrickGraph)
for i, brick := range bricks {
neighboring := map[int]struct{}{}
for _, block := range brick {
above := Coordinate{X: block.X, Y: block.Y, Z: block.Z + 1}
if occupiedBy, ok := occupied[above]; ok && occupiedBy != i {
neighboring[occupiedBy] = struct{}{}
}
}
for neighbor := range neighboring {
outgoing[i] = append(outgoing[i], neighbor)
incoming[neighbor] = append(incoming[neighbor], i)
}
}
return
}
func removableBricks(allBricks []Brick, incoming BrickGraph, outgoing BrickGraph) []int {
removable := []int{}
for i := range allBricks {
dependents := outgoing[i]
allDependentsSafe := true
for _, dependent := range dependents {
// There is more than one item which has this dependent as a dependent, so removing i would
// not allow this to fall
if len(incoming[dependent]) <= 1 {
allDependentsSafe = false
break
}
}
if allDependentsSafe {
removable = append(removable, i)
}
}
return removable
}
func sortByHeight(bricks []Brick) {
slices.SortFunc(bricks, func(brick1, brick2 Brick) int {
min1Z := brick1.LowestPoint()