diff --git a/2023/day21/main.go b/2023/day21/main.go index 37a23f5..2b64b44 100644 --- a/2023/day21/main.go +++ b/2023/day21/main.go @@ -1,194 +1,85 @@ package main import ( + "container/list" "fmt" - - h "git.bullercodeworks.com/brian/adventofcode/helpers" + "image" + "os" + "strings" ) func main() { - inp := h.StdinToStringSlice() - part1(inp) - fmt.Println() - part2(inp) -} + data, _ := os.ReadFile("input") + answer := solution(data, 64) -func part1(input []string) { - m := h.StringSliceToCoordByteMap(input) - start, _ := m.FindFirst('S') - m.Put(start, '.') - result := []h.Coordinate{start} - for i := 0; i < 64; i++ { - result = findNext(&m, result) - } fmt.Println("# Part 1") - fmt.Println(len(result)) -} - -// 253370351984188 is too low -func part2(input []string) { - m := h.StringSliceToCoordByteMap(input) - start, _ := m.FindFirst('S') - m.Put(start, '.') - steps := 26501365 - p := make([]int, 3) - size := len(input) - half := size / 2 - fmt.Println("Getting Plots: p[0]") - p[0], _ = getPlots(&m, half, 5) - fmt.Println("Getting Plots: p[1]") - m = h.StringSliceToCoordByteMap(input) - p[1], _ = getPlots(&m, half+size, 5) - fmt.Println("Getting Plots: p[2]") - m = h.StringSliceToCoordByteMap(input) - p[2], _ = getPlots(&m, half+2*size, 5) - a := (p[2] + p[0] - 2*p[1]) / 2 - b := p[1] - p[0] - a - c := p[0] - n := steps / size - result := a*n*n + b*n + c - + fmt.Println(answer[0]) fmt.Println("# Part 2") - fmt.Println(result) + fmt.Println(answer[1]) } -var visited map[string]bool - -func getPlots(m *h.CoordByteMap, steps int, factor int) (int, error) { - sx, sy := m.ColCount(), m.RowCount() - l := sy - expandMap(m, factor) - sx = l * (factor / 2) - sy = l * (factor / 2) - visited = make(map[string]bool) - return len(nextStep(sx, sy, 0, steps, m)), nil -} - -func isPlot(m *h.CoordByteMap, x, y int) bool { - return m.Get(h.Coordinate{X: x, Y: y}) == '.' -} -func nextStep(x, y, step, max int, m *h.CoordByteMap) []h.Coordinate { - var acc []h.Coordinate - var key = fmt.Sprintf("%d;%d;%d", x, y, step) - if _, ok := visited[key]; ok { - return []h.Coordinate{} - } else { - visited[key] = true - } - if step == max { - return []h.Coordinate{{X: x, Y: y}} - } - if x > 0 && isPlot(m, x-1, y) { - acc = append(acc, nextStep(x-1, y, step+1, max, m)...) - } - if x < m.ColCount() && isPlot(m, x+1, y) { - acc = append(acc, nextStep(x, y-1, step+1, max, m)...) - } - if y > 0 && isPlot(m, x, y-1) { - acc = append(acc, nextStep(x, y-1, step+1, max, m)...) - } - if y < m.RowCount() && isPlot(m, x, y+1) { - acc = append(acc, nextStep(x, y+1, step+1, max, m)...) - } - return acc -} - -func expandMap(m *h.CoordByteMap, factor int) { - stx, edx := m.TLX, m.BRX - for f := 1; f < factor; f++ { - for x := stx; x <= edx; x++ { - col := m.GetCol(x) - m.AddCol(col) - } - sty, edy := m.TLY, m.BRY - for y := sty; y <= edy; y++ { - row := m.GetRow(y) - m.AddRow(row) +func solution(data []byte, total_steps int) []int { + lines := strings.Split(strings.TrimSpace(string(data)), "\n") + q := list.New() + symbols := map[image.Point]string{} + for y, line := range lines { + l := strings.TrimSpace(line) + //fmt.Println(len(s)) + for x, r := range l { + symbols[image.Point{x, y}] = string(r) + if r == 'S' { + q.PushBack(image.Point{x, y}) + } } } -} -func part2BruteForce(input []string) { - // Brute force just isn't going to cut it... - m := h.StringSliceToCoordByteMap(input) - m.StringEmptyIsSpace = true - m.StringEmptyByte = '.' - seen := make(map[h.Coordinate]bool) - start, _ := m.FindFirst('S') - m.Put(start, '.') - result := []h.Coordinate{start} - steps := 26501365 - for i := 0; i < steps; i++ { - fmt.Println(h.CLEAR_SCREEN) - fmt.Printf("%d/%d (%f)\n", i, steps, float64(i)/float64(steps)) - seen, result = findNextInfiniteRepeat(&m, seen, result) - if i == 100 { - break - } - } - fmt.Println("# Part 2") - fmt.Println(len(result)) - for i := range result { - m.Put(result[i], 'O') - } - fmt.Println(m) - // It's making a diamond shape... -} + steps := 0 + total := 26501365 + part_1 := 0 + part_2 := 0 -func findNext(m *h.CoordByteMap, curr []h.Coordinate) []h.Coordinate { - var result []h.Coordinate - for _, c := range curr { - for _, tst := range []h.Coordinate{ - c.North(), c.East(), c.South(), c.West(), - } { - if m.ContainsCoord(tst) && m.Get(tst) == '.' { - var contains bool - for i := range result { - if result[i].Equals(tst) { - contains = true - break + directions := []image.Point{ + {-1, 0}, {0, -1}, {0, 1}, {1, 0}, + } + + polynomial := make([]int, 0) + for steps < total { + new_Q := list.New() + visited := make(map[image.Point]bool) + + for q.Len() > 0 { + element := q.Front() + val := element.Value.(image.Point) + q.Remove(element) + x, y := val.X, val.Y + for _, d := range directions { + newPos := image.Point{x + d.X, y + d.Y} + ref := image.Point{((newPos.Y % len(lines)) + len(lines)) % len(lines), ((newPos.X % len(lines)) + len(lines)) % len(lines)} + if symbols[ref] != "#" { + if _, ok := visited[newPos]; !ok { + visited[newPos] = true + new_Q.PushBack(newPos) } } - if !contains { - result = append(result, tst) - } } } - } - return result -} + steps += 1 + q = new_Q //update queue + if steps%(len(lines)) == total%len(lines) { + polynomial = append(polynomial, len(visited)) + if len(polynomial) == 3 { + p0 := polynomial[0] + p1 := polynomial[1] - polynomial[0] + p2 := polynomial[2] - polynomial[1] -func findNextInfiniteRepeat(m *h.CoordByteMap, seen map[h.Coordinate]bool, curr []h.Coordinate) (map[h.Coordinate]bool, []h.Coordinate) { - var result []h.Coordinate - for _, c := range curr { - for _, tst := range []h.Coordinate{ - c.North(), c.East(), c.South(), c.West(), - } { - if seen[tst] { - continue - } - wrkX, wrkY := tst.X, tst.Y - for wrkX < m.TLX { - wrkX += m.BRX - } - for wrkY < m.TLY { - wrkY += m.BRY - } - wrk := h.Coordinate{X: (wrkX % m.BRX), Y: (wrkY % m.BRY)} - if m.Get(wrk) == '.' { - var contains bool - for i := range result { - if result[i].Equals(tst) { - contains = true - break - } - } - if !contains { - result = append(result, tst) - seen[tst] = true - } + part_2 = p0 + (p1 * (total / len(lines))) + ((total/len(lines))*((total/len(lines))-1)/2)*(p2-p1) + break } } + if steps == total_steps { + part_1 = len(visited) + } } - return seen, result + + return []int{part_1, part_2} } diff --git a/helpers/coordinateByteMap.go b/helpers/coordinateByteMap.go index 0fccc2e..65dcf0c 100644 --- a/helpers/coordinateByteMap.go +++ b/helpers/coordinateByteMap.go @@ -17,6 +17,10 @@ type CoordByteMap struct { // Options for the 'String' method StringEmptyIsSpace bool StringEmptyByte byte + + // If 'Repeats' is true, then any coordinate request will be + // modded by BRX/BRY + Repeats bool } func NewCoordByteMap() CoordByteMap { @@ -49,6 +53,9 @@ func (m *CoordByteMap) ColCount() int { return m.BRX - m.TLX } func (m *CoordByteMap) GetRow(y int) []byte { + if m.Repeats { + y = (y + m.BRY) % m.BRY + } var resp []byte for x := m.TLX; x <= m.BRX; x++ { resp = append(resp, m.Get(Coordinate{X: x, Y: y})) @@ -56,6 +63,9 @@ func (m *CoordByteMap) GetRow(y int) []byte { return resp } func (m *CoordByteMap) GetCol(x int) []byte { + if m.Repeats { + x = (x + m.BRX) % m.BRX + } var resp []byte for y := m.TLY; y <= m.BRY; y++ { resp = append(resp, m.Get(Coordinate{X: x, Y: y})) @@ -76,10 +86,18 @@ func (m *CoordByteMap) AddCol(col []byte) { } func (m *CoordByteMap) GetPos(x, y int) byte { + if m.Repeats { + x = (x + m.BRX) % m.BRX + y = (y + m.BRY) % m.BRY + } return m.Get(Coordinate{X: x, Y: y}) } func (m *CoordByteMap) Get(pos Coordinate) byte { + if m.Repeats { + pos.X = (pos.X + m.BRX) % m.BRX + pos.Y = (pos.Y + m.BRY) % m.BRY + } if v, ok := m.Field[pos]; ok { return v } @@ -87,6 +105,10 @@ func (m *CoordByteMap) Get(pos Coordinate) byte { } func (m *CoordByteMap) Opt(pos Coordinate, def byte) byte { + if m.Repeats { + pos.X = (pos.X + m.BRX) % m.BRX + pos.Y = (pos.Y + m.BRY) % m.BRY + } if v, ok := m.Field[pos]; ok { return v } @@ -110,6 +132,10 @@ func (m *CoordByteMap) Put(pos Coordinate, val byte) { } func (m *CoordByteMap) Delete(pos Coordinate) { + if m.Repeats { + pos.X = (pos.X + m.BRX) % m.BRX + pos.Y = (pos.Y + m.BRY) % m.BRY + } delete(m.Field, pos) } @@ -135,6 +161,10 @@ func (m *CoordByteMap) ToByteSlices() [][]byte { // ContainsCoord returns true if the passed coordinate is within the bounds // of the map func (m *CoordByteMap) ContainsCoord(c Coordinate) bool { + if m.Repeats { + c.X = (c.X + m.BRX) % m.BRX + c.Y = (c.Y + m.BRY) % m.BRY + } return c.X >= m.TLX && c.X <= m.BRX && c.Y >= m.TLY && c.Y <= m.BRY } diff --git a/helpers/priorityQueue.go b/helpers/priorityQueue.go new file mode 100644 index 0000000..0651924 --- /dev/null +++ b/helpers/priorityQueue.go @@ -0,0 +1,62 @@ +package aoc + +type PriorityQueue[T comparable] struct { + items []T + comparator func(a, b T) int +} + +func NewPriorityQueue[T comparable](comparator func(a, b T) int) PriorityQueue[T] { + return PriorityQueue[T]{comparator: comparator} +} +func (pq *PriorityQueue[T]) Push(item T) { + pq.items = append(pq.items, item) + pq.heapifyUp(len(pq.items) - 1) +} +func (pq *PriorityQueue[T]) Pop() T { + top := pq.items[0] + lastIndex := len(pq.items) - 1 + pq.items[0], pq.items[lastIndex] = pq.items[lastIndex], pq.items[0] + pq.items = pq.items[:lastIndex] + pq.heapifyDown(0) + return top +} +func (pq *PriorityQueue[T]) Peek() T { + return pq.items[0] +} +func (pq *PriorityQueue[T]) Len() int { + return len(pq.items) +} +func (pq *PriorityQueue[T]) IsEmpty() bool { + return len(pq.items) == 0 +} +func (pq *PriorityQueue[T]) IsNotEmpty() bool { + return !pq.IsEmpty() +} +func (pq *PriorityQueue[T]) heapifyUp(index int) { + for index > 0 { + parentIndex := (index - 1) / 2 + if pq.comparator(pq.items[index], pq.items[parentIndex]) < 0 { + pq.items[index], pq.items[parentIndex] = pq.items[parentIndex], pq.items[index] + index = parentIndex + } else { + break + } + } +} +func (pq *PriorityQueue[T]) heapifyDown(index int) { + for { + leftChild, rightChild, smallest := 2*index+1, 2*index+2, index + if leftChild < len(pq.items) && pq.comparator(pq.items[leftChild], pq.items[smallest]) < 0 { + smallest = leftChild + } + if rightChild < len(pq.items) && pq.comparator(pq.items[rightChild], pq.items[smallest]) < 0 { + smallest = rightChild + } + if smallest != index { + pq.items[index], pq.items[smallest] = pq.items[smallest], pq.items[index] + index = smallest + } else { + break + } + } +} diff --git a/helpers/queue.go b/helpers/queue.go new file mode 100644 index 0000000..235361a --- /dev/null +++ b/helpers/queue.go @@ -0,0 +1,78 @@ +package aoc + +type Queue[T comparable] struct { + items []T +} + +func NewQueue[T comparable](items []T) *Queue[T] { + return &Queue[T]{ + items: items, + } +} + +func (q *Queue[T]) Len() int { + return len(q.items) +} + +func (q *Queue[T]) Push(item T) { + q.items = append(q.items, item) +} +func (q *Queue[T]) Pop() T { + var x T + x, q.items = q.items[len(q.items)-1], q.items[:len(q.items)-1] + return x +} +func (q *Queue[T]) Unshift(item T) { + q.items = append([]T{item}, q.items...) +} +func (q *Queue[T]) Shift() T { + var x T + x, q.items = q.items[0], q.items[1:] + return x +} + +func (q *Queue[T]) Append(items []T) { + q.items = append(q.items, items...) +} +func (q *Queue[T]) Copy() *Queue[T] { + ret := &Queue[T]{ + items: make([]T, len(q.items)), + } + copy(ret.items, q.items) + return ret +} +func (q *Queue[T]) Cut(at, count int) []T { + ret := q.items[at : at+count] + q.items = append(q.items[:at], q.items[at+count:]...) + return ret +} +func (q *Queue[T]) Delete(item T) { + for i := range q.items { + if q.items[i] == item { + q.items = append(q.items[:i], q.items[i+1:]...) + return + } + } +} +func (q *Queue[T]) Expand(at, by int) { + q.items = append(q.items[:at], append(make([]T, by), q.items[at:]...)...) +} +func (q *Queue[T]) Extend(by int) { + q.items = append(q.items, make([]T, by)...) +} +func (q *Queue[T]) FilterInPlace(keep func(item T) bool) { + n := 0 + for _, x := range q.items { + if keep(x) { + q.items[n] = x + n++ + } + } + q.items = q.items[:n] +} +func (q *Queue[T]) Insert(item T, location int) { + q.InsertVector([]T{item}, location) +} +func (q *Queue[T]) InsertVector(items []T, location int) { + q.items = append(q.items[:location], append(items, q.items[location:]...)...) +}