diff --git a/Solutions/2023/Day23.cs b/Solutions/2023/Day23.cs index 3c486ea..e52bf53 100644 --- a/Solutions/2023/Day23.cs +++ b/Solutions/2023/Day23.cs @@ -1,7 +1,5 @@ using static AdventOfCode.Solutions._2023.Day23; -using Map = char[,]; - namespace AdventOfCode.Solutions._2023; /// @@ -12,26 +10,26 @@ namespace AdventOfCode.Solutions._2023; public sealed partial class Day23 { [Init] - public static void Init(string[] input, params object[]? args) => LoadHikingTrails(input); + public static void Init(string[] input, params object[]? args) => LoadMap(input); public static string Part1(string[] input, params object[]? args) => Solution1().ToString(); public static string Part2(string[] input, params object[]? args) => Solution2().ToString(); - public const char PATH = '.'; - public const char FOREST = '#'; - public const char SLOPE_RIGHT = '>'; - public const char SLOPE_LEFT = '<'; - public const char SLOPE_UP = '^'; - public const char SLOPE_DOWN = 'v'; - public static readonly char[] SLOPES = [SLOPE_RIGHT, SLOPE_LEFT, SLOPE_UP, SLOPE_DOWN]; + public const char PATH = '.'; + public const char FOREST = '#'; + public const char SLOPE_RIGHT = '>'; + public const char SLOPE_LEFT = '<'; + public const char SLOPE_UP = '^'; + public const char SLOPE_DOWN = 'v'; + public static readonly char[] SLOPES = [SLOPE_RIGHT, SLOPE_LEFT, SLOPE_UP, SLOPE_DOWN]; - private static Map _map = default!; - private static Point _start; - private static Point _end; + public static char[,] _map = default!; + public static Point _start; + public static Point _end; - private static void LoadHikingTrails(string[] input) { - _map = input.To2dArray(); + private static void LoadMap(string[] input) { + _map = input.To2dArray(); _start = new(_map.RowAsString(0).IndexOf(PATH), 0); - _end = new(_map.RowAsString(_map.YMax()).IndexOf(PATH), _map.YMax()); + _end = new(_map.RowAsString(_map.YMax()).IndexOf(PATH), _map.YMax()); } private static int Solution1() { @@ -40,101 +38,124 @@ private static int Solution1() { .Max(); } - private static string Solution2() { - if (_map.RowAsString(0) == "#.###########################################################################################################################################") { - // my input - return "6302 (too slow)"; - } - - return _map - .FindAllPathLengths2(_start, _end) - .Max() - .ToString(); + private static int Solution2() + { + return + _map + .CondenseMap([_start, _end]) + .BuildGraph() + .FindMaxSteps_DepthFirstSearch(_start, []); } + } file static class Day23Helpers { - public static List FindAllPathLengths(this Map map, Point current, Point end, List isVisited) + public static List CondenseMap(this char[,] map, List initialPoints) { - if (current == end) { - return [isVisited.Count]; - } + List points = [.. initialPoints]; - List pathLengths = []; + for (int y = 0; y < map.RowsCount(); y++) { + for (int x = 0; x < map.ColsCount(); x++) { + Point current = new(x, y); + if (map[current.X, current.Y] == FOREST) { + continue; + } - foreach (Point next in map.GetNeighbours(current)) { - if (isVisited.Contains(next) is false) { - pathLengths.AddRange(FindAllPathLengths(map, next, end, [.. isVisited, next])); + if (map.GetAdjacentCells(current).Where(adj => adj.Value != FOREST).Count() >= 3) { + points.Add(current); + } } } - return pathLengths; + return points; } - public static List FindAllPathLengths2(this Map map, Point start, Point end) + public static Dictionary> BuildGraph(this List points) { - int maxPathLength = int.MinValue; + Dictionary> graph = points.ToDictionary(pt => pt, pt => new Dictionary()); - List result = []; - Stack<(Point, List)> stack = new(); - stack.Push((start, new List() { start })); + foreach (Point current in points) { + Stack<(Point, int)> stack = new(); + HashSet seen = []; - while (stack.Count != 0) { - var (current, visited) = stack.Pop(); + stack.Push((current, 0)); + _ = seen.Add(current); - if (current == end) { - result.Add(visited.Count - 1); - int count = result.Count; - if (visited.Count - 1 > maxPathLength) { - maxPathLength = int.Max(maxPathLength, visited.Count - 1); - Console.WriteLine($"{count, 20} {maxPathLength}"); - } - if (count % 200 == 0) { - Console.WriteLine($"{count, 20} {maxPathLength}"); + while (stack.Count > 0) { + (Point point, int steps) = stack.Pop(); + + if (steps != 0 && points.Contains(point)) { + graph[current][point] = steps; + continue; } - continue; - } - foreach (Point next in GetNeighbours(map, current, true)) { - if (!visited.Contains(next)) { - List newVisited = [.. visited, .. new[] { next }]; - stack.Push((next, newVisited)); + foreach (Point neighbour in _map.GetAdjacentCells(point).Where(adj => adj.Value != FOREST)) { + if (!seen.Contains(neighbour)) { + stack.Push((neighbour, steps + 1)); + _ = seen.Add(neighbour); + } } } } - return result; + return graph; } - // 4838 too low - // 6034 too low - // 6050 too low - // 6054 too low - // 6138 too low (19934 iterations) - // 6302 correct!!! + public static int FindMaxSteps_DepthFirstSearch(this Dictionary> graph, Point point, HashSet visited) + { + if (point == _end) { + return 0; + } + + int maxSteps = int.MinValue; - private static IEnumerable GetNeighbours(this Map grid, Point current, bool ignoreSlopes = false) + _ = visited.Add(point); + foreach (KeyValuePair next in graph[point]) { + if (!visited.Contains(next.Key)) { + maxSteps = Math.Max(maxSteps, FindMaxSteps_DepthFirstSearch(graph, next.Key, visited) + graph[point][next.Key]); + } + } + _ = visited.Remove(point); + + return maxSteps; + } + + public static List FindAllPathLengths(this char[,] map, Point current, Point end, List isVisited) { - char currentValue = grid[current.X, current.Y]; - - if (!ignoreSlopes) { - if (currentValue.IsIn(SLOPES)) { - yield return currentValue switch - { - SLOPE_RIGHT => current.Right(), - SLOPE_LEFT => current.Left(), - SLOPE_UP => current.Up(), - SLOPE_DOWN => current.Down(), - _ => throw new NotImplementedException(), - }; - yield break; + if (current == end) { + return [isVisited.Count]; + } + + List pathLengths = []; + + foreach (Point neighbour in map.GetNeighbours(current)) { + if (isVisited.Contains(neighbour) is false) { + pathLengths.AddRange(FindAllPathLengths(map, neighbour, end, [.. isVisited, neighbour])); } } - foreach (Cell item in grid.GetAdjacentCells(current).Where(adj => adj.Value != FOREST)) { + return pathLengths; + } + + private static IEnumerable GetNeighbours(this char[,] _map, Point current) + { + char currentValue = _map[current.X, current.Y]; + + if (currentValue.IsIn(SLOPES)) { + yield return currentValue switch + { + SLOPE_RIGHT => current.Right(), + SLOPE_LEFT => current.Left(), + SLOPE_UP => current.Up(), + SLOPE_DOWN => current.Down(), + _ => throw new NotImplementedException(), + }; + yield break; + } + + foreach (Cell item in _map.GetAdjacentCells(current).Where(adj => adj.Value != FOREST)) { yield return item.Index; } } - }