Files
AdventOfCode/2022/Day12CSharp/Program.cs
2025-11-30 20:28:10 -05:00

279 lines
8.6 KiB
C#

// See https://aka.ms/new-console-template for more information
using System.Runtime.CompilerServices;
Part01();
Part02();
void Part01()
{
Console.WriteLine("!! === Part 1 === !!");
Console.WriteLine(" === Example ===");
Tuple<int, int> exampleStart, exampleEnd;
var exampleHeightMap = HeightMap.LoadHeightMap("example-input.txt", out exampleStart, out exampleEnd);
var exampleGraph = exampleHeightMap.ToGraph();
exampleGraph.PrintShortestPath(exampleStart, exampleEnd);
Console.WriteLine();
Console.WriteLine(" === Puzzle ===");
Tuple<int, int> puzzleStart, puzzleEnd;
var puzzleHeightMap = HeightMap.LoadHeightMap("puzzle-input.txt", out puzzleStart, out puzzleEnd);
var puzzleGraph = puzzleHeightMap.ToGraph();
puzzleGraph.PrintShortestPath(puzzleStart, puzzleEnd);
}
void Part02()
{
Console.WriteLine("!! === Part 2 === !!");
Console.WriteLine(" === Example ===");
Tuple<int, int> exampleStart, exampleEnd;
var exampleHeightMap = HeightMap.LoadHeightMap("example-input.txt", out exampleStart, out exampleEnd);
var exampleGraph = exampleHeightMap.ToGraph();
Console.WriteLine("Shortest steps from anywhere: {0}", exampleGraph.GetShortestNumberOfStepsAnywhere(0, exampleEnd));
Console.WriteLine();
Console.WriteLine(" === Puzzle ===");
Tuple<int, int> puzzleStart, puzzleEnd;
var puzzleHeightMap = HeightMap.LoadHeightMap("puzzle-input.txt", out puzzleStart, out puzzleEnd);
var puzzleGraph = puzzleHeightMap.ToGraph();
Console.WriteLine("Shortest steps from anywhere: {0}", puzzleGraph.GetShortestNumberOfStepsAnywhere(0, puzzleEnd));
}
class GraphNode
{
public int Height { get; set; }
public Tuple<int, int> Coordinates { get; set; }
public List<GraphNode> AdjacentNodes { get; set; } = new List<GraphNode>();
public bool Explored { get; set; }
public GraphNode(int height, Tuple<int, int> coordinates)
{
Height = height;
Coordinates = coordinates;
}
public void Reset()
{
Explored = false;
}
}
class Graph
{
public Dictionary<Tuple<int, int>, GraphNode> Vertices { get; set; } = new Dictionary<Tuple<int, int>, GraphNode>();
public GraphNode GetAt(Tuple<int, int> index)
{
return Vertices[index];
}
public int GetShortestNumberOfStepsAnywhere(int startingHeight, Tuple<int, int> end)
{
int minSteps = int.MaxValue;
foreach (var g in Vertices)
{
if (g.Value.Height == startingHeight)
{
minSteps = Math.Min(minSteps, GetShortestNumberOfSteps(g.Value.Coordinates, end));
}
}
return minSteps;
}
public GraphNode GetAt(int x, int y)
{
return GetAt(new Tuple<int, int>(x, y));
}
public int GetShortestNumberOfSteps(Tuple<int, int> start, Tuple<int, int> end)
{
Dictionary<Tuple<int, int>, Tuple<GraphNode?, int>> path = new Dictionary<Tuple<int, int>, Tuple<GraphNode?, int>>();
BFS(start, end, path);
return path[end].Item2;
}
public void PrintShortestPath(Tuple<int, int> start, Tuple<int, int> end)
{
Console.WriteLine("Shortest path from ({0},{1}) to ({2},{3}): {4} steps", start.Item1, start.Item2, end.Item1, end.Item2, GetShortestNumberOfSteps(start, end));
}
public bool BFS(Tuple<int,int> start, Tuple<int,int> end, Dictionary<Tuple<int, int>, Tuple<GraphNode?, int>> path)
{
Queue<GraphNode> queue = new Queue<GraphNode>();
int visited = 0;
// label all verts as unexplored
foreach (var g in Vertices)
{
g.Value.Reset();
path.Add(g.Value.Coordinates, new Tuple<GraphNode?, int>(null, int.MaxValue));
}
// Get the root
GraphNode root = GetAt(start);
Vertices[root.Coordinates].Explored = true;
path[root.Coordinates] = new Tuple<GraphNode?,int>(path[root.Coordinates].Item1, 0);
queue.Enqueue(root);
while (queue.Count != 0)
{
GraphNode v = queue.Dequeue();
// We done if we found it
if (v.Coordinates.Equals(end))
{
return true;
}
foreach (var w in v.AdjacentNodes)
{
if (!w.Explored)
{
visited++;
//Console.WriteLine("Visited: ({0},{1}). Count: {2}", w.Coordinates.Item1, w.Coordinates.Item2, visited);
w.Explored = true;
path[w.Coordinates] = new Tuple<GraphNode?, int>(v, path[v.Coordinates].Item2 + 1);
queue.Enqueue(w);
}
}
}
return false;
}
}
class HeightMap
{
public Graph ToGraph()
{
Graph graph = new Graph();
// Add in all the verts
for (int j = 0; j < heightmap.Count; j++)
{
for (int i = 0; i < heightmap[0].Count; i++)
{
GraphNode gn = new GraphNode(GetHeight(i, j), new Tuple<int, int>(i, j));
graph.Vertices.Add(gn.Coordinates, gn);
}
}
// Create the edges
for (int j = 0; j < heightmap.Count; j++)
{
for (int i = 0; i < heightmap[0].Count; i++)
{
Tuple<int, int> current = new Tuple<int,int>(i, j);
Tuple<int, int> result = new Tuple<int, int>(0, 0);
char[] edges = { 'u', 'd', 'l', 'r' };
foreach (var edge in edges)
{
if (CanGo(edge, i, j, out result))
{
graph.Vertices[current].AdjacentNodes.Add(graph.Vertices[result]);
}
}
}
}
return graph;
}
private HeightMap()
{
}
public int GetHeight(int x, int y)
{
return heightmap[y][x];
}
public bool CanGo(char direction, int x, int y, out Tuple<int,int> newSpot)
{
int currentHeight = GetHeight(x, y);
switch (direction)
{
case 'u':
newSpot = new Tuple<int, int>(x, y - 1);
return (y > 0 && currentHeight >= GetHeight(x, y - 1) - 1);
case 'd':
newSpot = new Tuple<int, int>(x, y + 1);
return (y < heightmap.Count - 1 && currentHeight >= GetHeight(x, y + 1) - 1);
case 'l':
newSpot = new Tuple<int, int>(x - 1, y);
return (x > 0 && currentHeight >= GetHeight(x - 1, y) - 1);
case 'r':
newSpot = new Tuple<int, int>(x + 1, y);
return (x < heightmap[0].Count - 1 && currentHeight >= GetHeight(x + 1, y) - 1);
default:
throw new ArgumentException();
}
}
private List<List<int>> heightmap = new List<List<int>>();
public static HeightMap LoadHeightMap(string filename, out Tuple<int, int> outStartPoint, out Tuple<int, int> outEndPoint)
{
HeightMap hm = new HeightMap();
Tuple<int, int>? startPoint = null;
Tuple<int, int>? endPoint = null;
using (StreamReader reader = System.IO.File.OpenText(filename))
{
while (!reader.EndOfStream)
{
string? line = reader.ReadLine();
if (line == null) throw new InvalidDataException();
List<int> row = new List<int>();
foreach (char c in line)
{
if (!char.IsLetter(c)) throw new InvalidDataException();
if (c == 'S')
{
row.Add('a' - 'a');
startPoint = new Tuple<int, int>(row.Count - 1, hm.heightmap.Count);
}
else if (c == 'E')
{
row.Add('z' - 'a');
endPoint = new Tuple<int, int>(row.Count - 1, hm.heightmap.Count);
}
else
{
row.Add(c - 'a');
}
}
hm.heightmap.Add(row);
}
}
if (startPoint == null || endPoint == null) throw new InvalidDataException();
outStartPoint = startPoint;
outEndPoint = endPoint;
return hm;
}
}