using System.Diagnostics; namespace IYGASAF; 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 input = await File.ReadAllLinesAsync(args[0]); var seedsAreRanges = args.Length > 1 && args[1] == "part2"; Console.WriteLine("Parsing almanac..."); var almanac = Almanac.Parse(input); Console.WriteLine("Getting lowest seed location..."); var stopwatch = new Stopwatch(); stopwatch.Start(); var result = almanac.GetLowestSeedLocation(seedsAreRanges); stopwatch.Stop(); Console.WriteLine($"The lowest seed location is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); return (int)result; } } /// /// Represents an almanac. /// /// The seeds. /// The seed ranges. /// The maps. /// An instance of . public class Almanac( List seeds, List seedRanges, List maps ) { /// /// Gets the seed ranges. /// public List SeedRanges { get; init; } = seedRanges; /// /// Gets the seeds. /// public List Seeds { get; init; } = seeds; /// /// Gets the maps. /// public List Maps { get; init; } = maps; private static List GetSeedsFromString(string seedsString) => seedsString .Split( ' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) .Select(long.Parse) .ToList(); /// /// Gets the seed location. /// /// The seed. /// The seed location. public long GetSeedLocation(long seed) => Maps.Aggregate( seed, (currentSeed, map) => map.ConvertSourceToDestination(currentSeed) ); private class LocationResult { public long Value { get; set; } = long.MaxValue; } /// /// Gets the lowest seed location. /// /// If set to true seeds are treated as ranges /// The lowest seed location. public long GetLowestSeedLocation(bool seedsAreRanges = false) { var lowestLocation = new LocationResult(); if (seedsAreRanges) { Parallel.ForEach(SeedRanges, (currentSeedRange) => { var currentSeedRangeStart = currentSeedRange.Start; var currentSeedRangeEnd = currentSeedRange.End; for (long j = currentSeedRangeStart; j <= currentSeedRangeEnd; j++) { var currentSeed = j; var currentSeedLocation = GetSeedLocation(currentSeed); lock (lowestLocation) { if (currentSeedLocation < lowestLocation.Value) { lowestLocation.Value = currentSeedLocation; } } } }); } else { for (int i = 0; i < Seeds.Count; i++) { var currentSeed = Seeds[i]; var currentSeedLocation = GetSeedLocation(currentSeed); if (currentSeedLocation < lowestLocation.Value) { lowestLocation.Value = currentSeedLocation; } } } return lowestLocation.Value; } /// /// Parses the seeds as ranges. /// /// The seeds string. /// The seeds as ranges. public static List ParseSeedsAsRanges(string seedsString) { var seeds = new List(); var seedNumbers = GetSeedsFromString(seedsString); for (int i = 0; i < seedNumbers.Count; i += 2) { var start = seedNumbers[i]; var length = seedNumbers[i + 1]; seeds.Add(new(start, length)); } return seeds; } /// /// Parses the specified almanac. /// /// The almanac. /// An instance of . public static Almanac Parse(string[] almanac) { var seedsString = almanac[0].Split(':')[1]; var seeds = GetSeedsFromString(seedsString); var seedRanges = ParseSeedsAsRanges(seedsString); var maps = new List(); var ranges = new List(); for (int i = 1; i < almanac.Length; i++) { var currentLine = almanac[i]; if (string.IsNullOrWhiteSpace(currentLine)) { continue; } if (currentLine.Contains(':')) { if (ranges.Count is not 0) { maps.Add(Map.Parse([.. ranges])); ranges.Clear(); } continue; } ranges.Add(currentLine); if (i == almanac.Length - 1) { maps.Add(Map.Parse([.. ranges])); } } return new Almanac( seeds, seedRanges, maps ); } } /// /// Represents a map. /// /// The ranges contained within the map /// An instance of . public class Map( List ranges ) { /// /// Gets the ranges. /// public List Ranges { get; init; } = [.. ranges.OrderBy(range => range.SourceStart)]; /// /// Parses a map from the specified ranges. /// /// The ranges. /// An instance of . public static Map Parse(string[] ranges) => new( ranges.Select(Range.Parse).ToList() ); /// /// Gets the source range. /// /// The source value. /// The source . public Range? GetSourceRange(long sourceValue) => Ranges .Where(range => range.SourceStart <= sourceValue && range.SourceEnd >= sourceValue) .FirstOrDefault(); /// /// Converts the source to destination. /// /// The value to convert. /// The converted value. public long ConvertSourceToDestination(long valueToConvert) { var sourceRange = GetSourceRange(valueToConvert); if (sourceRange is null) { return valueToConvert; } var sourceToDestinationOffset = Math.Abs(sourceRange.SourceStart - sourceRange.DestinationStart); return sourceRange.DestinationStart <= sourceRange.SourceStart ? valueToConvert - sourceToDestinationOffset : valueToConvert + sourceToDestinationOffset; } } /// /// Represents a seed range. /// /// The start. /// The length. /// An instance of . public class SeedRange( long start, long length ) { /// /// Gets the start of the range. It is inclusive. /// public long Start { get; init; } = start; /// /// Gets the length of the range. /// public long Length { get; init; } = length; /// /// Gets the end of the range. It is inclusive. /// public long End => Start + Length - 1; } /// /// Represents a map range. /// /// Length of the range. /// The source start. /// The destination start. /// An instance of . public class Range( long rangeLength, long sourceStart, long destinationStart ) { /// /// Gets the length of the range. /// public long RangeLength { get; init; } = rangeLength; /// /// Gets the start of the source range. It is inclusive. /// public long SourceStart { get; init; } = sourceStart; /// /// Gets the end of the source range. It is inclusive. /// public long SourceEnd => SourceStart + RangeLength - 1; /// /// Gets the start of the destination range. It is inclusive. /// public long DestinationStart { get; init; } = destinationStart; /// /// Gets the end of the destination range. It is inclusive. /// public long DestinationEnd => DestinationStart + RangeLength - 1; /// /// Parses the specified range. /// /// The range. /// An instance of . /// Range must be in the format of 'DestinationRangeStart:long SourceRangeStart:long RangeLength:long' public static Range Parse(string range) { var parts = range.Split( ' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ); if (parts.Length is not 3 || parts.Any(part => long.TryParse(part, out _) is false)) { throw new ArgumentException("Range must be in the format of 'DestinationRangeStart:long SourceRangeStart:long RangeLength:long'"); } var rangeLength = long.Parse(parts[2]); var sourceStart = long.Parse(parts[1]); var destinationStart = long.Parse(parts[0]); return new Range( rangeLength, sourceStart, destinationStart ); } }