diff --git a/solutions/alessio/day16/aoc16 b/solutions/alessio/day16/aoc16 new file mode 100755 index 0000000..1abf3e6 Binary files /dev/null and b/solutions/alessio/day16/aoc16 differ diff --git a/solutions/alessio/day16/day16.go b/solutions/alessio/day16/day16.go index f512579..e85e021 100644 --- a/solutions/alessio/day16/day16.go +++ b/solutions/alessio/day16/day16.go @@ -35,21 +35,7 @@ func findStartAndEndPos(grid []string, rows int, cols int) (Pos, Pos) { return start, end } -func printGrid(grid []string, rows int, cols int, currPos Pos) { - for r := 0; r < rows; r++ { - for c := 0; c < cols; c++ { - if r == currPos.r && c == currPos.c { - fmt.Print("@") - } else { - fmt.Printf("%c", grid[r][c]) - } - } - fmt.Println() - } - fmt.Println() -} - -func nextDirs(curr Pos) []Pos { +func turnDirs(curr Pos) []Pos { left := Pos{-curr.c, curr.r} right := Pos{curr.c, -curr.r} return []Pos{left, right} @@ -57,83 +43,20 @@ func nextDirs(curr Pos) []Pos { var dirs []Pos = []Pos{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} // E, W, S, N -func checkInBounds(pos Pos, rows int, cols int) bool { - return pos.r >= 0 && pos.r < rows && pos.c >= 0 && pos.c < cols -} - type PosDir struct { pos, dir Pos } -func getDirSym(dir Pos) byte { - if dir == dirs[0] { - return '>' - } else if dir == dirs[1] { - return '<' - } else if dir == dirs[2] { - return 'v' - } else { - return '^' - } -} - -func printPath(grid []string, path []PosDir) { - pathmap := make(map[Pos]Pos) - for _, p := range path { - if _, exists := pathmap[p.pos]; exists { - fmt.Println("path is not unique") - } - pathmap[p.pos] = p.dir - } - rows, cols := len(grid), len(grid[0]) - for r := 0; r < rows; r++ { - for c := 0; c < cols; c++ { - if dir, exists := pathmap[Pos{r, c}]; exists { - fmt.Printf("%c", getDirSym(dir)) - } else { - fmt.Printf("%c", grid[r][c]) - } - } - fmt.Println() - } - fmt.Println() -} +func isIntersection(grid []string, pos Pos) bool { + e := grid[pos.r+dirs[0].r][pos.c+dirs[0].c] + w := grid[pos.r+dirs[1].r][pos.c+dirs[1].c] + s := grid[pos.r+dirs[2].r][pos.c+dirs[2].c] + n := grid[pos.r+dirs[3].r][pos.c+dirs[3].c] -func findPathRec(grid []string, currPos Pos, currDir Pos, currScore int, currMin *int, visited *map[Pos]bool, path []PosDir) { - if grid[currPos.r][currPos.c] == '#' { - return - } - if grid[currPos.r][currPos.c] == 'E' { // reached end of path - if currScore < *currMin { - fmt.Printf("path with score %d\n", currScore) - // printPath(grid, path) - *currMin = currScore - } - return - } - (*visited)[currPos] = true - // go along currDir - nextPos := Pos{currPos.r + currDir.r, currPos.c + currDir.c} - if !(*visited)[nextPos] && currScore+1 < *currMin { - findPathRec(grid, nextPos, currDir, currScore+1, currMin, visited, append(path, PosDir{currPos, currDir})) - } - // turn clockwise and counter-clockwise - for _, dir := range nextDirs(currDir) { - nextPos = Pos{currPos.r + dir.r, currPos.c + dir.c} - if !(*visited)[nextPos] && currScore+1001 < *currMin { - findPathRec(grid, nextPos, dir, currScore+1001, currMin, visited, append(path, PosDir{currPos, dir})) - } + if (n != '#' || s != '#') && (e != '#' || w != '#') { + return true } - (*visited)[currPos] = false // backtrack -} -func isIntersection(grid []string, pos Pos, currDir Pos) bool { - for _, dir := range nextDirs(currDir) { - next := Pos{pos.r + dir.r, pos.c + dir.c} - if grid[next.r][next.c] == '.' { - return true - } - } return false } @@ -151,68 +74,123 @@ func printMazeWithNodes(grid []string, rows int, cols int, graph *graph.Graph[Po fmt.Println() } -func buildGraphRec(grid []string, currPos Pos, currDir Pos, lastNode Pos, currWeight int, graph *graph.Graph[Pos]) { - if grid[currPos.r][currPos.c] == '#' { - return +func addGraphEdgesIter(grid []string, start Pos, graph *graph.Graph[Pos]) { + queue := []PosDir{{start, dirs[0]}} + visited := make(map[PosDir]bool) + + if grid[start.r-1][start.c] != '#' { + queue = append(queue, PosDir{start, dirs[3]}) } - visited := false - if isIntersection(grid, currPos, currDir) { - visited = graph.HasNode(currPos) - if !visited { - graph.AddNode(currPos) - } - if currPos != lastNode { - graph.AddEdge(lastNode, currPos, currWeight) - } - if grid[currPos.r][currPos.c] == 'E' { - return + if grid[start.r+1][start.c] != '#' { + queue = append(queue, PosDir{start, dirs[2]}) + } + + for len(queue) > 0 { + curr := queue[0] + queue = queue[1:] + if visited[curr] { + continue } - if !visited || grid[currPos.r][currPos.c] == 'S' { // if already visited, no sense recurring on it. - // restart counting from this node - // turn clockwise and counter-clockwise - for _, dir := range nextDirs(currDir) { - nextPos := Pos{currPos.r + dir.r, currPos.c + dir.c} - // lastNode = currPos and weight = 1001 (restart counting from this node) - buildGraphRec(grid, nextPos, dir, currPos, 1001, graph) + visited[curr] = true + nextPos := Pos{curr.pos.r + curr.dir.r, curr.pos.c + curr.dir.c} + dist := 0 + for grid[nextPos.r][nextPos.c] != '#' { + dist++ + if isIntersection(grid, nextPos) || grid[nextPos.r][nextPos.c] == 'E' { + if !graph.HasEdge(curr.pos, nextPos) { + graph.AddEdge(curr.pos, nextPos, dist, curr.dir) + } + for _, dir := range turnDirs(curr.dir) { + if !visited[PosDir{nextPos, dir}] { + queue = append(queue, PosDir{nextPos, dir}) + } + } + queue = append(queue, PosDir{nextPos, curr.dir}) + break } + nextPos.r += curr.dir.r + nextPos.c += curr.dir.c } } - - // go along currDir - nextPos := Pos{currPos.r + currDir.r, currPos.c + currDir.c} - buildGraphRec(grid, nextPos, currDir, lastNode, currWeight+1, graph) } -func getGraph(grid []string, rows int, cols int) *graph.Graph[Pos] { +func addGraphNodes(g *graph.Graph[Pos], grid []string, rows int, cols int) { + for r := 1; r < rows-1; r++ { + for c := 1; c < cols-1; c++ { + if grid[r][c] != '#' && (isIntersection(grid, Pos{r, c}) || grid[r][c] == 'E' || grid[r][c] == 'S') { + g.AddNode(Pos{r, c}) + } + } + } +} +func getGraph(grid []string, rows int, cols int) (*graph.Graph[Pos], Pos, Pos) { g := graph.Graph[Pos]{} g.Init(rows*cols, rows*cols) start, end := findStartAndEndPos(grid, rows, cols) - g.AddNode(start) - g.AddNode(end) - buildGraphRec(grid, start, dirs[0], start, 0, &g) - return &g + addGraphNodes(&g, grid, rows, cols) + addGraphEdgesIter(grid, start, &g) + return &g, start, end } func solve1(grid []string) { rows, cols := len(grid), len(grid[0]) - g := getGraph(grid, rows, cols) - printMazeWithNodes(grid, rows, cols, g) - // fmt.Println(g.Nodes) - - fmt.Println(g.AdjList) - // currPos, endPos := findStartAndEndPos(grid, rows, cols) - // fmt.Println(currPos, endPos) - // currDir := dirs[0] // start facing east + g, start, end := getGraph(grid, rows, cols) + // printMazeWithNodes(grid, rows, cols, g) + fmt.Println(g.ShortestPath(start, end, dirs[0])) +} - // currMin := math.MaxInt - // visited := make(map[Pos]bool) - // findPathRec(grid, currPos, currDir, 0, &currMin, &visited, []PosDir{}) - // fmt.Println(currMin) +func abs(x int) int { + if x >= 0 { + return x + } + return -x +} + +func getDir(from Pos, to Pos) Pos { + diffR := to.r - from.r + diffC := to.c - from.c + if diffR == 0 { + dirC := diffC / abs(diffC) + return Pos{0, dirC} + } else if diffC == 0 { + dirR := diffR / abs(diffR) + return Pos{dirR, 0} + } else { + fmt.Println("wtf") + return Pos{0, 0} + } } -func solve2(origGrid []string) { +func solve2(grid []string) { + rows, cols := len(grid), len(grid[0]) + g, start, end := getGraph(grid, rows, cols) + edges := g.GetFullPathsEdges(start, end, dirs[0]) + + tiles := map[Pos]bool{} + for _, e := range edges { + dir := getDir(e.From, e.To.Val) + curr := Pos{e.From.r, e.From.c} + for curr.r != e.To.Val.r || curr.c != e.To.Val.c { + tiles[curr] = true + curr.r += dir.r + curr.c += dir.c + } + tiles[curr] = true + } + // for r := 0; r < rows; r++ { + // for c := 0; c < cols; c++ { + // if tiles[Pos{r, c}] { + // fmt.Print("O") + // } else { + // fmt.Printf("%c", grid[r][c]) + // } + // } + // fmt.Println() + // } + // fmt.Println() + fmt.Println(len(tiles)) } func part1(grid []string) { @@ -224,7 +202,7 @@ func part2(grid []string) { } func main() { - data, err := os.ReadFile("./input16.txt") + data, err := os.ReadFile("./input16_def.txt") check(err) dataStr := strings.ReplaceAll(string(data), "\r\n", "\n") lines := strings.Split(strings.Trim(dataStr, "\n"), "\n") diff --git a/solutions/alessio/day16/graph/graph.go b/solutions/alessio/day16/graph/graph.go index fa027a9..d557e08 100644 --- a/solutions/alessio/day16/graph/graph.go +++ b/solutions/alessio/day16/graph/graph.go @@ -1,6 +1,10 @@ package graph -import "slices" +import ( + "fmt" + "math" + "slices" +) type Graph[T comparable] struct { Nodes []Node[T] @@ -14,7 +18,9 @@ type Node[T any] struct { type Edge[T any] struct { Weight int + From T To *Node[T] + Dir T } func (graph *Graph[T]) AddNode(val T) *Node[T] { @@ -23,17 +29,13 @@ func (graph *Graph[T]) AddNode(val T) *Node[T] { return &node } -func (graph *Graph[T]) AddEdge(from T, to T, weight int) *Edge[T] { - var toNode *Node[T] - - for _, n := range graph.Nodes { - if n.Val == to { - toNode = &n - break - } +func (graph *Graph[T]) AddEdge(from T, to T, weight int, dir T) *Edge[T] { + toNode := graph.GetNode(to) + if toNode == nil { + fmt.Println("Error: toNode is nil") + return nil } - - edge := Edge[T]{Weight: weight, To: toNode} + edge := Edge[T]{Weight: weight, To: toNode, Dir: dir} graph.Edges = append(graph.Edges, edge) graph.AdjList[from] = append(graph.AdjList[from], edge) return &edge @@ -43,8 +45,115 @@ func (graph *Graph[T]) HasNode(val T) bool { return slices.ContainsFunc(graph.Nodes, func(n Node[T]) bool { return n.Val == val }) } +func (graph *Graph[T]) HasEdge(from T, to T) bool { + for _, e := range graph.AdjList[from] { + if e.To.Val == to { + return true + } + } + return false +} + +func (graph *Graph[T]) GetNode(val T) *Node[T] { + for _, n := range graph.Nodes { + if n.Val == val { + return &n + } + } + return nil +} + func (graph *Graph[T]) Init(nodes int, edges int) { graph.Nodes = make([]Node[T], 0, nodes) graph.Edges = make([]Edge[T], 0, edges) graph.AdjList = make(map[T][]Edge[T], nodes) } + +func (graph *Graph[T]) ShortestPath(from T, to T, startDir T) int { + start := graph.GetNode(from) + end := graph.GetNode(to) + dist := make(map[T]int, len(graph.Nodes)) + minDir := make(map[T]T, len(graph.Nodes)) + path := map[T]bool{} + nodes := make([]T, 0, len(graph.Nodes)) + for _, n := range graph.Nodes { + dist[n.Val] = math.MaxInt + nodes = append(nodes, n.Val) + } + + dist[start.Val] = 0 + minDir[start.Val] = startDir + for len(path) < len(nodes) { + curr := graph.getClosest(nodes, dist, path) + if path[curr] { + fmt.Println("Error: curr is already in path") + } + path[curr] = true + + if curr == end.Val { + return dist[curr] + } + + for _, e := range graph.AdjList[curr] { + // relaxation on the edge + weight := e.Weight + if minDir[curr] != e.Dir { + weight += 1000 + } + if dist[curr]+weight < dist[e.To.Val] { + dist[e.To.Val] = dist[curr] + weight + minDir[e.To.Val] = e.Dir + } + } + } + return dist[end.Val] +} + +func (graph *Graph[T]) getClosest(nodes []T, dist map[T]int, visited map[T]bool) T { + var minNode T + minDist := math.MaxInt + + for _, n := range nodes { + if !visited[n] && dist[n] < minDist { + minDist = dist[n] + minNode = n + } + } + return minNode +} + +func (graph *Graph[T]) GetFullPathsEdges(start T, end T, dir T) []Edge[T] { + allEdges := []Edge[T]{} + min := math.MaxInt + graph.getBestPathsRec(start, dir, &map[T]bool{}, []Edge[T]{}, &allEdges, end, 0, &min) + return allEdges +} + +func (graph *Graph[T]) getBestPathsRec(currNode T, currDir T, visited *map[T]bool, currPath []Edge[T], edges *[]Edge[T], end T, currVal int, currMin *int) { + if currNode == end { + fmt.Println("found path") + if currVal < *currMin { + *currMin = currVal + (*edges) = append([]Edge[T]{}, currPath...) // replace all + } else if currVal == *currMin { + (*edges) = append((*edges), currPath...) + } + return + } + + (*visited)[currNode] = true + for _, e := range graph.AdjList[currNode] { + next := e.To.Val + if !(*visited)[next] { + e.From = currNode + weight := e.Weight + if currDir != e.Dir { + weight += 1000 + } + if currVal+weight <= *currMin { + graph.getBestPathsRec(next, e.Dir, visited, append(currPath, e), edges, end, currVal+weight, currMin) + } + } + } + (*visited)[currNode] = false +} diff --git a/solutions/alessio/day16/input16.txt b/solutions/alessio/day16/input16.txt index 6a5bb85..54f4cd7 100644 --- a/solutions/alessio/day16/input16.txt +++ b/solutions/alessio/day16/input16.txt @@ -1,15 +1,17 @@ -############### -#.......#....E# -#.#.###.#.###.# -#.....#.#...#.# -#.###.#####.#.# -#.#.#.......#.# -#.#.#####.###.# -#...........#.# -###.#.#####.#.# -#...#.....#.#.# -#.#.#.###.#.#.# -#.....#...#.#.# -#.###.#.#.#.#.# -#S..#.....#...# -############### \ No newline at end of file +################# +#...#...#...#..E# +#.#.#.#.#.#.#.#.# +#.#.#.#...#...#.# +#.#.#.#.###.#.#.# +#...#.#.#.....#.# +#.#.#.#.#.#####.# +#.#...#.#.#.....# +#.#.#####.#.###.# +#.#.#.......#...# +#.#.###.#####.### +#.#.#...#.....#.# +#.#.#.#####.###.# +#.#.#.........#.# +#.#.#.#########.# +#S#.............# +################# \ No newline at end of file diff --git a/solutions/alessio/day16/out.txt b/solutions/alessio/day16/out.txt index e1eb520..6f29117 100644 --- a/solutions/alessio/day16/out.txt +++ b/solutions/alessio/day16/out.txt @@ -1,21 +1,21 @@ -############### -#+.+...+#+...+# -#.#.###.#.###.# -#+.+.+#.#+.+#.# -#.###.#####.#.# -#.#.#+...+.+#.# -#.#.#####.###.# -#+.+.+...+.+#.# -###.#.#####.#.# -#+.+#+...+#.#.# -#.#.#.###.#.#.# -#+.+.+#+.+#.#.# -#.###.#.#.#.#.# -#+..#+.+.+#+.+# -############### +################# +#+.+#+.+#+.+#+.+# +#.#.#.#.#.#.#.#.# +#.#.#.#+.+#+.+#.# +#.#.#.#.###.#.#.# +#+.+#.#.#+.+.+#.# +#.#.#.#.#.#####.# +#.#+.+#.#.#+...+# +#.#.#####.#.###.# +#.#.#..+.+.+#+.+# +#.#.###.#####.### +#.#.#+.+#..+.+#.# +#.#.#.#####.###.# +#.#.#+.....+..#.# +#.#.#.#########.# +#+#+.+.........+# +################# -[{{13 1}} {{1 13}} {{11 1}} {{11 3}} {{9 3}} {{9 1}} {{7 3}} {{7 1}} {{3 1}} {{3 3}} {{1 3}} {{1 1}} {{1 7}} {{3 5}} {{5 5}} {{5 9}} {{7 9}} {{7 11}} {{13 11}} {{13 13}} {{7 5}} {{9 5}} {{9 9}} {{11 9}} {{11 7}} {{13 7}} {{13 9}} {{13 5}} {{11 5}} {{5 11}} {{3 11}} {{3 9}} {{1 9}}] -[{1002 0xc00000a140} {1002 0xc00000a180} {1002 0xc00000a1d0} {1002 0xc00000a230} {1002 0xc00000a260} {1004 0xc00000a270} {1004 0xc00000a2e0} {1002 0xc00000a360} {1004 0xc00000a3f0} {1002 0xc00000a490} {1002 0xc00000a540} {1002 0xc00000a600} {1002 0xc00000a690} {1006 0xc00000a710} {1004 0xc00000a7e0} {1004 0xc00000a8c0} {1002 0xc00000a9b0} {1004 0xc00000aab0} {1002 0xc00000abc0} {1002 0xc00000ace0} {1006 0xc00000ae10} {1002 0xc00000af50} {1012 0xc00000af70} {1004 0xc00000b0c0} {1002 0xc00000b220} {1004 0xc00000b390} {1002 0xc00000b510} {1002 0xc00000b6a0} {1002 0xc00000b840} {1002 0xc00000b9f0} {1002 0xc00000bb70} {1004 0xc00000bce0} {1002 0xc00000bea0} {1002 0xc00011e070} {1002 0xc00011e0b0} {1004 0xc00011e0e0} {1004 0xc00011e240} {1006 0xc00011e390} {1004 0xc00011e540} {1004 0xc00011e710} {1006 0xc00011e8d0} {1006 0xc00011e940} {1008 0xc00011e9c0} {1006 0xc00011eba0} {1002 0xc00011ed90} {1002 0xc00011ef90} {1002 0xc00011f1a0} {1004 0xc00011f1c0} {1006 0xc00011f280} {1002 0xc00011f3d0} {1006 0xc00011f4e0} {1008 0xc00011f600} {1004 0xc00011f7d0} {1004 0xc00011f830}] -map[{1 1}:[{1002 0xc00000a690} {1006 0xc00000a710}] {1 3}:[{1002 0xc00000a600} {1004 0xc00000a7e0}] {1 9}:[{1004 0xc00011f1c0}] {3 1}:[{1002 0xc00000a490} {1004 0xc00000a8c0}] {3 3}:[{1002 0xc00000a540}] {3 5}:[{1002 0xc00000a9b0}] {3 9}:[{1002 0xc00011f1a0}] {3 11}:[{1002 0xc00011ef90}] {5 5}:[{1004 0xc00000aab0} {1006 0xc00011eba0}] {5 9}:[{1002 0xc00000abc0}] {5 11}:[{1002 0xc00011ed90}] {7 1}:[{1004 0xc00000a3f0} {1006 0xc00011f280}] {7 3}:[{1002 0xc00000a360} {1002 0xc00011f3d0} {1006 0xc00011f4e0} {1008 0xc00011f600}] {7 5}:[{1002 0xc00000b220} {1004 0xc00011e710} {1006 0xc00011e8d0}] {7 9}:[{1002 0xc00000ace0} {1004 0xc00000b0c0} {1006 0xc00011e940} {1008 0xc00011e9c0}] {7 11}:[{1006 0xc00000ae10}] {9 1}:[{1002 0xc00000a260} {1004 0xc00000a270}] {9 3}:[{1002 0xc00000a230}] {9 5}:[{1004 0xc00000b390}] {9 9}:[{1002 0xc00000b510} {1004 0xc00011e540}] {11 1}:[{1002 0xc00000a180} {1004 0xc00011f7d0}] {11 3}:[{1002 0xc00000a1d0} {1004 0xc00000a2e0}] {11 5}:[{1002 0xc00011e0b0} {1004 0xc00011e0e0}] {11 7}:[{1002 0xc00000b840}] {11 9}:[{1002 0xc00000b6a0}] {13 1}:[{1002 0xc00000a140} {1004 0xc00011f830}] {13 5}:[{1002 0xc00011e070} {1004 0xc00011e240} {1006 0xc00011e390}] {13 7}:[{1002 0xc00000b9f0} {1002 0xc00000bea0}] {13 9}:[{1002 0xc00000bb70} {1004 0xc00000bce0}] {13 11}:[{1002 0xc00000af50}] {13 13}:[{1012 0xc00000af70}]] -part1: 1.7673ms +11048 +part1: 1.44325ms part2: 0s