diff --git a/2023/day23/main.go b/2023/day23/main.go index c2d82b4..5b4fb0b 100644 --- a/2023/day23/main.go +++ b/2023/day23/main.go @@ -2,138 +2,195 @@ package main import ( "fmt" - "os" h "git.bullercodeworks.com/brian/adventofcode/helpers" ) func main() { inp := h.StdinToStringSlice() - if len(os.Args) == 1 || os.Args[1] != "-p2" { - part1(inp) - } - if len(os.Args) == 1 { - fmt.Println() - } - if len(os.Args) == 1 || os.Args[1] != "-p1" { - part2(inp) - } + part1(inp) + fmt.Println() + part2(inp) } -var history map[string]int - -func part1(input []string) { - history = make(map[string]int) - m := h.StringSliceToCoordByteMap(input) - start, _ := m.FindFirst('.') - start.Y++ - end, _ := m.FindLast('.') +func part1(inp []string) { fmt.Println("# Part 1") - fmt.Println(len(findPath(&m, start, end, []h.Coordinate{}))) + start, end := h.Coordinate{}, h.Coordinate{} + start.X, start.Y = findStart(inp) + end.X, end.Y = findEnd(inp) + visited := map[h.Coordinate]int{start: 0} + currentDir := S + walk(inp, start, currentDir, visited) + fmt.Println(visited[end]) } -func part2(input []string) { - history = make(map[string]int) - m := h.StringSliceToCoordByteMap(input) - start, _ := m.FindFirst('.') - start.Y++ - end, _ := m.FindLast('.') - m.ReplaceAll('^', '.') - m.ReplaceAll('>', '.') - m.ReplaceAll('v', '.') - m.ReplaceAll('<', '.') +func part2(inp []string) { fmt.Println("# Part 2") - fmt.Println(len(findPath(&m, start, end, []h.Coordinate{}))) + start, end := h.Coordinate{}, h.Coordinate{} + start.X, start.Y = findStart(inp) + end.X, end.Y = findEnd(inp) + conns := getConnections(inp) + conns[start] = true + conns[end] = true + + paths := getPaths(inp, conns) + visited := make([]bool, len(conns)) + visited[paths[start][0].index] = true + fmt.Println(part2Walk(inp, paths, start, end, 0, visited)) } -func findPath(m *h.CoordByteMap, pos h.Coordinate, end h.Coordinate, path []h.Coordinate) []h.Coordinate { - ret := []h.Coordinate{pos} - if pos.Equals(end) { - //printPath(m, pos, path) - //time.Sleep(time.Second / 20) - return ret +var ( + N = h.Coordinate{X: 0, Y: -1} + E = h.Coordinate{X: 1, Y: 0} + S = h.Coordinate{X: 0, Y: 1} + W = h.Coordinate{X: -1, Y: 0} + dirs = []h.Coordinate{N, E, S, W} + badSlope = map[h.Coordinate]byte{ + N: 'v', E: '<', S: '^', W: '>', } +) - path = append(path, pos) - switch m.Get(pos) { - case '^': - if inPath(pos.North(), path) { - return ret - } else { - return append(ret, findPath(m, pos.North(), end, path)...) - } - case '>': - if inPath(pos.East(), path) { - return ret - } else { - return append(ret, findPath(m, pos.East(), end, path)...) - } - case 'v': - if inPath(pos.South(), path) { - return ret - } else { - return append(ret, findPath(m, pos.South(), end, path)...) - } - case '<': - if inPath(pos.West(), path) { - return ret - } else { - return append(ret, findPath(m, pos.West(), end, path)...) +func getLeft(from h.Coordinate) h.Coordinate { + switch { + case from.Equals(N): + return W + case from.Equals(E): + return N + case from.Equals(S): + return E + default: + return S + } +} +func getRight(from h.Coordinate) h.Coordinate { + switch { + case from.Equals(N): + return E + case from.Equals(E): + return S + case from.Equals(S): + return W + default: + return N + } +} + +func findStart(inp []string) (int, int) { + y := 0 + for x := 0; x < len(inp[0]); x++ { + if inp[y][x] == '.' { + return x, y } } + return -1, -1 +} - var longestPath []h.Coordinate - longest := h.MIN_INT - for _, chk := range []h.Coordinate{pos.North(), pos.East(), pos.South(), pos.West()} { - if m.Get(chk) == '#' || inPath(chk, path) || !m.ContainsCoord(chk) { - continue +func findEnd(inp []string) (int, int) { + y := len(inp) - 1 + for x := 0; x < len(inp[0]); x++ { + if inp[y][x] == '.' { + return x, y } + } + return -1, -1 +} - var run bool - var v int - if v, run = history[chk.String()]; run { - run = v < len(path)+1 - } - if run { - history[chk.String()] = len(path) + 1 - chkPath := append(ret, findPath(m, chk, end, path)...) - if inPath(end, chkPath) && len(chkPath) > longest { - longest = len(chkPath) - longestPath = chkPath +func isValidCoord(inp []string, c h.Coordinate) bool { + return c.Y >= 0 && c.Y < len(inp) && c.X >= 0 && c.X < len(inp[0]) +} + +func walk(inp []string, start, currDir h.Coordinate, visited map[h.Coordinate]int) { + curr := start + currStep := visited[curr] + + for _, dir := range []h.Coordinate{currDir, getLeft(currDir), getRight(currDir)} { + next := h.Coordinate{X: (curr.X + dir.X), Y: (curr.Y + dir.Y)} + if isValidCoord(inp, next) && inp[next.Y][next.X] != '#' { + bt := inp[next.Y][next.X] + if badSlope[dir] == bt { + continue + } + if val, found := visited[next]; !found || val < currStep+1 { + visited[next] = currStep + 1 + walk(inp, next, dir, visited) } } } - return longestPath } -func inPath(pos h.Coordinate, path []h.Coordinate) bool { - for _, chk := range path { - if chk.Equals(pos) { - return true - } - } - return false -} - -func printPath(m *h.CoordByteMap, pos h.Coordinate, path []h.Coordinate) { - limit := (m.BRY - m.TLY) > 30 - fmt.Print(h.CLEAR_SCREEN) - for y := m.TLY; y <= m.BRY; y++ { - if limit && h.Abs(pos.Y-y) > 15 { - continue - } - for x := m.TLX; x <= m.BRX; x++ { - chk := h.Coordinate{X: x, Y: y} - if chk.Equals(pos) { - fmt.Print("@") - } else { - if inPath(chk, path) { - fmt.Print("O") - } else { - fmt.Print(string(m.Get(chk))) +func getConnections(inp []string) map[h.Coordinate]bool { + conns := map[h.Coordinate]bool{} + for y, line := range inp { + for x, bt := range line { + if bt == '#' { + continue + } + pos := h.Coordinate{X: x, Y: y} + neighbors := 0 + for _, nbr := range pos.GetOrthNeighbors() { + if isValidCoord(inp, nbr) && inp[nbr.Y][nbr.X] != '#' { + neighbors++ } } + if neighbors > 2 { + conns[pos] = true + } } - fmt.Println() + } + return conns +} + +type Path struct { + end h.Coordinate + length, index int +} + +func getPaths(inp []string, conns map[h.Coordinate]bool) map[h.Coordinate][]Path { + paths := map[h.Coordinate][]Path{} + connIdx := 0 + for c := range conns { + for _, d := range dirs { + curr := h.Coordinate{X: c.X + d.X, Y: c.Y + d.Y} + if isValidCoord(inp, curr) && inp[curr.Y][curr.X] != '#' { + path := getPath(inp, c, curr, d, 1, conns) + path.index = connIdx + paths[c] = append(paths[c], path) + } + } + connIdx++ + } + return paths +} + +func getPath(inp []string, start, curr, dir h.Coordinate, length int, conns map[h.Coordinate]bool) Path { + for _, dir := range []h.Coordinate{dir, getLeft(dir), getRight(dir)} { + next := h.Coordinate{X: curr.X + dir.X, Y: curr.Y + dir.Y} + if inp[next.Y][next.X] != '#' { + if _, found := conns[next]; found { + return Path{next, length + 1, 0} + } else { + return getPath(inp, start, next, dir, length+1, conns) + } + } + } + return Path{ + end: h.Coordinate{X: -1, Y: -1}, + length: 0, index: 0, } } + +func part2Walk(inp []string, paths map[h.Coordinate][]Path, start, end h.Coordinate, step int, visited []bool) int { + var max int + for _, path := range paths[start] { + if path.end == end { + return step + path.length + } + index := paths[path.end][0].index + if !visited[index] { + visited[index] = true + max = h.Max(max, part2Walk(inp, paths, path.end, end, step+path.length, visited)) + visited[index] = false + } + } + return max +} diff --git a/helpers/queue.go b/helpers/queue.go index 235361a..0b12f27 100644 --- a/helpers/queue.go +++ b/helpers/queue.go @@ -1,10 +1,10 @@ package aoc -type Queue[T comparable] struct { +type Queue[T any] struct { items []T } -func NewQueue[T comparable](items []T) *Queue[T] { +func NewQueue[T any](items []T) *Queue[T] { return &Queue[T]{ items: items, } @@ -46,14 +46,18 @@ func (q *Queue[T]) Cut(at, count int) []T { 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]) DeleteAt(idx int) { + q.items = append(q.items[:idx], q.items[idx+1:]...) } + +// 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:]...)...) }