using System.Diagnostics; namespace HauntedWasteland; public class Program { public static async Task Main(string[] args) { if (args.Length is 0) { Console.WriteLine("Please provide a path to the input file."); return -1; } if (File.Exists(args[0]) is false) { Console.WriteLine("The provided file does not exist."); return -2; } var isPart2 = args.Length > 1 && args[1] == "part2"; var input = await File.ReadAllLinesAsync(args[0]); var stopwatch = new Stopwatch(); stopwatch.Start(); var map = Map.Parse(input); var result = isPart2 ? map.CountStepsToAllZNodes() : map.CountStepsToZ(); stopwatch.Stop(); Console.WriteLine($"The number of steps is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); return (int)result; } } /// /// A map of the Haunted Wasteland. /// /// The turns to take at each step. /// The nodes in the map. /// An instance of . public class Map( List turns, List nodes ) { /// /// Gets the turns to take at each step. /// public List Turns { get; init; } = turns; /// /// Gets the nodes in the map. /// public List Nodes { get; init; } = nodes; /// /// Parses a map from a string array. /// /// The map input. /// An instance of . public static Map Parse(string[] mapInput) { var turns = mapInput[0].ToList(); var nodes = mapInput[2..] .Select(Node.Parse) .ToList(); return new Map(turns, nodes); } /// /// Counts the number of steps to the Z node. /// /// The number of steps to the Z node. public int CountStepsToZ() { var current = Nodes.First(n => n.Current == "AAA"); var steps = 0; while (current.Current != "ZZZ") { var next = Turns[steps % Turns.Count] switch { 'R' => current.Right, 'L' => current.Left, _ => throw new Exception("Invalid turn") }; current = Nodes.First(n => n.Current == next); steps++; } return steps; } /// /// Finds the prime factors of a number. /// /// The number to find the prime factors of. /// The prime factors of the number. public List FindPrimeFactors(long number) { var factors = new List(); // Start with the smallest prime number, 2. var divisor = 2; // Continue until the number is reduced to 2 or less. while (number >= 2) { // If the number is divisible by the current divisor, if (number % divisor == 0) { // Add the divisor to the list of factors. factors.Add(divisor); // Divide the number by the divisor to reduce it. number /= divisor; } else { // If the number is not divisible by the current divisor, increment the divisor. divisor++; } } return factors; } /// /// Finds the least common multiple of a list of numbers. /// /// The numbers to find the least common multiple of. /// The least common multiple of the numbers. public long FindLeastCommonMultiple(List numbers) { var primeFactors = numbers.Select(FindPrimeFactors).ToList(); var uniquePrimeFactors = primeFactors .SelectMany(pf => pf) .Distinct() .ToList(); var maxPrimeFactors = uniquePrimeFactors .Select(upf => primeFactors.Max(pf => pf.Count(f => f == upf))) .ToList(); var result = uniquePrimeFactors .Zip(maxPrimeFactors) .Aggregate( (long)1, (acc, b) => // b.First is the prime factor // b.Second is the number of times it occurs acc * (long)Math.Pow(b.First, b.Second) ); return result; } /// /// Counts the number of steps to all Z nodes. /// /// The number of steps to all Z nodes. public long CountStepsToAllZNodes() { var startNodes = Nodes.Where(n => n.Current.EndsWith('A')).ToList(); var nodeSteps = new List(); foreach (var startNode in startNodes) { var current = startNode; var steps = 0; while (current.Current.EndsWith('Z') is false) { var next = Turns[steps % Turns.Count] switch { 'R' => current.Right, 'L' => current.Left, _ => throw new Exception("Invalid turn") }; current = Nodes.First(n => n.Current == next); steps++; } nodeSteps.Add(steps); } return FindLeastCommonMultiple(nodeSteps); } } /// /// A node in the map. /// /// The current node. /// The left node. /// The right node. /// An instance of . public class Node( string current, string left, string right ) { /// /// Gets the current node. /// public string Current { get; init; } = current; /// /// Gets the left node. /// public string Left { get; init; } = left; /// /// Gets the right node. /// public string Right { get; init; } = right; /// /// Casts the node to a string. /// public override string ToString() { return $"{Current} = ({Left},{Right})"; } /// /// Parses a node from a string. /// /// The node string. /// An instance of . public static Node Parse(string nodeString) { var parts = nodeString.Split( '=', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ); var current = parts[0]; var nextNodes = parts[1].Split( ',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ); var left = nextNodes[0].Trim('('); var right = nextNodes[1].Trim(')'); return new Node(current, left, right); } }