@@ 2,38 2,13 @@ package day14
import (
"bufio"
- "container/heap"
- // "fmt"
"os"
+ "sort"
"strconv"
"strings"
"williewillus/aoc_2022/util"
)
-// cargo culted from container/heap doc
-// An IntHeap is a min-heap of ints.
-type IntHeap []int
-
-func (h IntHeap) Len() int { return len(h) }
-func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
-func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
-
-func (h *IntHeap) Push(x interface{}) {
- // Push and Pop use pointer receivers because they modify the slice's length,
- // not just its contents.
- *h = append(*h, x.(int))
-}
-
-func (h *IntHeap) Pop() interface{} {
- old := *h
- n := len(old)
- x := old[n-1]
- *h = old[0 : n-1]
- return x
-}
-
-// End cargo cult
-
type vec2 util.Vec2
func parse(s string) (int, int) {
@@ 57,10 32,72 @@ func iorder(a, b int) (int, int) {
}
}
+const spawnX int = 500
+const spawnY int = 0
+
+func posOccupied(state map[int][]int, pos vec2) bool {
+ col, ok := state[pos.X]
+ if !ok {
+ return false
+ }
+ idx := sort.SearchInts(col, pos.Y)
+ return idx < len(col) && col[idx] == pos.Y
+}
+
+// Get the next occupied y coordinate "below" (greater than) the given x, y coordinate.
+// Returns -1 if one doesn't exist
+func getNextOccupiedBelow(state map[int][]int, pos vec2) int {
+ col, ok := state[pos.X]
+ if !ok {
+ return -1
+ }
+ nextYIdx := sort.Search(len(col), func(idx int) bool {
+ return col[idx] > pos.Y
+ })
+
+ if nextYIdx < len(col) {
+ return col[nextYIdx]
+ } else {
+ return -1
+ }
+}
+
+// Spawn a sand at spawnX, spawnY and simulate it until it comes to rest
+// Second return value is false if the particle falls off the board, vec2 holds the last known step before falling off
+// if it's true, then vec2 is the place it came to rest
+func simulateNextSand(state map[int][]int) (vec2, bool) {
+ pos := vec2{spawnX, spawnY}
+ for {
+ // Try moving down
+ nextOccupied := getNextOccupiedBelow(state, pos)
+ if nextOccupied == -1 {
+ return pos, false
+ }
+
+ toMoveDownTo := vec2{pos.X, nextOccupied - 1}
+
+ if toMoveDownTo == pos {
+ // Already as far down as we can go, try moving sideways
+ downLeft := vec2{pos.X-1, pos.Y + 1}
+ downRight := vec2{pos.X+1, pos.Y + 1}
+ if !posOccupied(state, downLeft) {
+ pos = downLeft
+ } else if !posOccupied(state, downRight) {
+ pos = downRight
+ } else {
+ // Nowhere to go, come to rest
+ return pos, true
+ }
+ } else {
+ pos = toMoveDownTo
+ }
+ }
+}
+
func Run() (string, string) {
scanner := bufio.NewScanner(os.Stdin)
- // x -> heap of filled y's
- state := make(map[int]*IntHeap)
+ // x -> filled Y coords (sorted)
+ state := make(map[int][]int)
for scanner.Scan() {
line := scanner.Text()
points := []vec2{}
@@ 78,22 115,26 @@ func Run() (string, string) {
for x := startX; x <= endX; x++ {
for y := startY; y <= endY; y++ {
- h, ok := state[x]
- if !ok {
- h = &IntHeap{}
- }
- heap.Push(h, y)
- state[x] = h
+ state[x] = append(state[x], y)
}
}
}
}
- /*
- for x, ys := range state {
- fmt.Printf("top at x=%d is y=%d\n", x, (*ys)[0])
+ for _, col := range state {
+ sort.Ints(col)
+ }
+
+ p1 := 0
+ for ; ; p1++ {
+ restPos, didRest := simulateNextSand(state)
+ if !didRest {
+ break
+ } else {
+ state[restPos.X] = append(state[restPos.X], restPos.Y)
+ sort.Ints(state[restPos.X])
}
- */
+ }
- return "incomplete", "incomplete"
+ return strconv.Itoa(p1), "incomplete"
}