namespace GearRatios; public class Program { public async static 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 puzzleSolver = new PuzzleSolver(); var input = await File.ReadAllLinesAsync(args[0]); var result = args.Length > 1 && args[1] == "part2" ? puzzleSolver.SumGearRatios(input) : puzzleSolver.SumPartNumbers(input); if (args.Length > 1 && args[1] == "part2") { Console.WriteLine($"The sum of all gear ratios is {result}."); } else { Console.WriteLine($"The sum of all part numbers is {result}."); } return result; } } /// /// Solves the puzzle. /// public class PuzzleSolver { /// /// Sums all part numbers in the schematic. /// /// The schematic to parse. /// The sum of all part numbers. public int SumPartNumbers(string[] schematic) { var allPartNumbers = new List(); for (var i = 0; i < schematic.Length; i++) { var currentLine = Line.Parse(schematic[i]); var prevLine = i > 0 ? Line.Parse(schematic[i - 1]) : null; var nextLine = i < schematic.Length - 1 ? Line.Parse(schematic[i + 1]) : null; var linePartNumbers = currentLine.GetPartNumbers(prevLine, nextLine); allPartNumbers.AddRange(linePartNumbers); } return allPartNumbers.Sum(); } /// /// Sums all gear ratios in the schematic. /// /// The schematic to parse. /// The sum of all gear ratios. public int SumGearRatios(string[] schematic) { var allGearRatios = new List(); for (var i = 0; i < schematic.Length; i++) { var currentLine = Line.Parse(schematic[i]); var prevLine = i > 0 ? Line.Parse(schematic[i - 1]) : null; var nextLine = i < schematic.Length - 1 ? Line.Parse(schematic[i + 1]) : null; var lineGearRatios = currentLine.GetGearRatios(prevLine, nextLine); allGearRatios.AddRange(lineGearRatios); } return allGearRatios.Sum(); } } /// /// Represents a line in the schematic. /// /// The numbers in the line. /// The special characters in the line. /// A line in the schematic. public class Line( List> numbers, List> specialCharacters ) { /// /// Gets the numbers in the line. /// public List> Numbers { get; init; } = numbers; /// /// Gets the special characters in the line. /// public List> SpecialCharacters { get; init; } = specialCharacters; /// /// Gets the indexes of all special characters in the line. /// public List SpecialCharacterIndexes => SpecialCharacters.SelectMany(dict => dict.Values).Select(v => v.Start).ToList(); private bool IsGearCharacter(string character) => character == "*"; /// /// Gets the gear ratios in the line. /// /// The previous line in the schematic. /// The next line in the schematic. /// A list of gear ratios. public List GetGearRatios(Line? previousLine = null, Line? nextLine = null) { var gearRatios = new List(); var gearCharacters = SpecialCharacters.Where(dict => dict.Keys.Any(IsGearCharacter)).ToList(); if (previousLine is not null) { Numbers.AddRange(previousLine.Numbers); } if (nextLine is not null) { Numbers.AddRange(nextLine.Numbers); } foreach (var gear in gearCharacters) { foreach (var (key, value) in gear) { var beforeGear = value.Start - 1; var afterGear = value.End + 1; var gearNumbers = Numbers .Where( n => n.Values.Any( v => v.Start >= beforeGear && v.Start <= afterGear || v.End >= beforeGear && v.End <= afterGear ) ) .SelectMany(n => n.Keys) .ToList(); if (gearNumbers.Count == 2) { gearRatios.Add(gearNumbers.First() * gearNumbers.Last()); } } } return gearRatios; } /// /// Gets the part numbers in the line. /// /// The previous line in the schematic. /// The next line in the schematic. /// A list of part numbers. public List GetPartNumbers(Line? previousLine = null, Line? nextLine = null) { var partNumbers = new List(); if (previousLine is not null) { SpecialCharacters.AddRange(previousLine.SpecialCharacters); } if (nextLine is not null) { SpecialCharacters.AddRange(nextLine.SpecialCharacters); } foreach (var number in Numbers) { foreach (var (key, value) in number) { var beforeNumber = value.Start - 1; var afterNumber = value.End + 1; if (SpecialCharacterIndexes.Any(idx => idx >= beforeNumber && idx <= afterNumber)) { partNumbers.Add(key); } } } return partNumbers; } /// /// Parses a line in the schematic. /// /// The line to parse. /// An instance of . public static Line Parse(string line) { var numbers = new List>(); var specialCharacters = new List>(); var number = string.Empty; int? numberStart = null; int? numberEnd; for (var i = 0; i < line.Length; i++) { var currentCharacter = line[i]; if (char.IsDigit(currentCharacter)) { number += line[i]; numberStart ??= i; if (i == line.Length - 1) { numberEnd = i; numbers.Add(new() { { int.Parse(number), new Indexes(numberStart.Value, numberEnd.Value) } }); } } else { if (numberStart.HasValue) { numberEnd = i - 1; numbers.Add(new() { { int.Parse(number), new Indexes(numberStart.Value, numberEnd.Value) } }); number = string.Empty; numberStart = null; } if (currentCharacter == '.') { continue; } specialCharacters.Add(new() { { currentCharacter.ToString(), new Indexes(i, i) } }); } } return new Line(numbers, specialCharacters); } } /// /// Represents the start and end indexes of a number or special character. /// public class Indexes(int start, int end) { /// /// Gets the start index. The start index is the position of the first character in the number or special character. /// public int Start { get; init; } = start; /// /// Gets the end index. The end index is the position of the last character in the number or special character. /// public int End { get; init; } = end; }