|
|
@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
using AdventOfCode.Common;
|
|
|
|
|
|
|
|
using AdventOfCode.Models;
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace AdventOfCode._2023
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public class Day5 : AOCDay
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static MapType[] MAP_ORDER = new MapType[] { MapType.SOIL, MapType.FERTILIZER, MapType.WATER, MapType.LIGHT, MapType.TEMPERATURE, MapType.HUMIDITY, MapType.LOCATION };
|
|
|
|
|
|
|
|
protected override AOCResponse ExecutePartA()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var inputs = this.SplitInputOnEmptyLine();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Convert our seeds into a long list
|
|
|
|
|
|
|
|
var seeds = inputs.First().First().Split(":")[1].Trim().Split(" ").Select(x => long.Parse(x)).ToDictionary(key => key, value => value);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Create our map objects
|
|
|
|
|
|
|
|
var maps = new List<AlmanacMap>();
|
|
|
|
|
|
|
|
foreach (var map in inputs.Skip(1))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
maps.Add(new AlmanacMap(map));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var prevType = MapType.SEED;
|
|
|
|
|
|
|
|
foreach (var mapType in MAP_ORDER)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Log($"Mapping from {prevType} to {mapType}");
|
|
|
|
|
|
|
|
var activeMap = maps.First(x => x.From == prevType && x.To == mapType);
|
|
|
|
|
|
|
|
foreach (var key in seeds.Keys)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
seeds[key] = activeMap.Transform(seeds[key]);
|
|
|
|
|
|
|
|
Log($"Seed {key} has a value of {seeds[key]} for {mapType}");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
prevType = mapType; //Denote our next mapping
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.Answer = seeds.Values.Min();
|
|
|
|
|
|
|
|
return this._response;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected override AOCResponse ExecutePartB()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var inputs = this.SplitInputOnEmptyLine();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Create our map objects
|
|
|
|
|
|
|
|
var maps = new List<AlmanacMap>();
|
|
|
|
|
|
|
|
foreach (var map in inputs.Skip(1))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
maps.Add(new AlmanacMap(map));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var finalizedSeedRanges = new List<SeedRange>();
|
|
|
|
|
|
|
|
var values = inputs.First().First().Split(":")[1].Trim().Split(" ").Select(x => long.Parse(x)).ToArray();
|
|
|
|
|
|
|
|
for (int i = 0; i < values.Length; i += 2)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var startSeed = values[i];
|
|
|
|
|
|
|
|
var endSeed = values[i] + values[i + 1]-1; //Minus one because it counts i
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var seed = new SeedRange() { Start = startSeed, End = endSeed };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var prevType = MapType.SEED;
|
|
|
|
|
|
|
|
var seedRanges = new List<SeedRange>() { seed };
|
|
|
|
|
|
|
|
foreach (var mapType in MAP_ORDER)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Log($"Mapping from {prevType} to {mapType}");
|
|
|
|
|
|
|
|
var activeMap = maps.First(x => x.From == prevType && x.To == mapType);
|
|
|
|
|
|
|
|
seedRanges = activeMap.GetSeedRanges(seedRanges);
|
|
|
|
|
|
|
|
prevType = mapType; //Denote our next mapping
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
finalizedSeedRanges = finalizedSeedRanges.Concat(seedRanges).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//var mapType = MapType.SOIL;
|
|
|
|
|
|
|
|
//var prevType = MapType.SEED;
|
|
|
|
|
|
|
|
//Log($"Mapping from {prevType} to {mapType}");
|
|
|
|
|
|
|
|
//var activeMap = maps.First(x => x.From == prevType && x.To == mapType);
|
|
|
|
|
|
|
|
//var postMapSeedRanges = activeMap.GetSeedRanges(seed);
|
|
|
|
|
|
|
|
//for (long x = startSeed ; x < endSeed; x++)
|
|
|
|
|
|
|
|
//{
|
|
|
|
|
|
|
|
// Console.WriteLine($"Seed {x} has a soil value of {activeMap.Transform(x)}");
|
|
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
//activeMap.GetSeedRanges(seed);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.Answer = finalizedSeedRanges.Min(x => x.Start);
|
|
|
|
|
|
|
|
return this._response;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum MapType
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
SEED, SOIL, FERTILIZER, WATER, LIGHT,TEMPERATURE, HUMIDITY, LOCATION
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AlmanacMap
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public MapType From { get; set; }
|
|
|
|
|
|
|
|
public MapType To { get; set; }
|
|
|
|
|
|
|
|
private readonly List<MapData> _mapData;
|
|
|
|
|
|
|
|
public AlmanacMap(string[] maps)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var types = maps.First().Split(" ")[0].Split("-to-");
|
|
|
|
|
|
|
|
From = Enum.Parse<MapType>(types[0].ToUpper());
|
|
|
|
|
|
|
|
To = Enum.Parse<MapType>(types[1].ToUpper());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var mapDataList = new List<MapData>();
|
|
|
|
|
|
|
|
foreach (var mapData in maps.Skip(1))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var splitMap = mapData.Split(" ").Select(x => long.Parse(x)).ToArray();
|
|
|
|
|
|
|
|
var destinationRange = splitMap[0];
|
|
|
|
|
|
|
|
var sourceRange = splitMap[1];
|
|
|
|
|
|
|
|
var rangeLength = splitMap[2];
|
|
|
|
|
|
|
|
mapDataList.Add(new MapData()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Destination = destinationRange,
|
|
|
|
|
|
|
|
Source = sourceRange,
|
|
|
|
|
|
|
|
Range = rangeLength
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_mapData = mapDataList.OrderBy(x => x.Source).ToList();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public long Transform(long value)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var result = value;
|
|
|
|
|
|
|
|
foreach (var mapData in _mapData)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (value >= mapData.Source && value < (mapData.Source + mapData.Range))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var rangeToBeAdded = value - mapData.Source;
|
|
|
|
|
|
|
|
result = mapData.Destination + rangeToBeAdded;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public List<SeedRange> GetSeedRanges(List<SeedRange> ranges)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var totalRanges = new List<SeedRange>();
|
|
|
|
|
|
|
|
Console.WriteLine("###############");
|
|
|
|
|
|
|
|
foreach (var range in ranges)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var seedRanges = new List<SeedRange>();
|
|
|
|
|
|
|
|
var originalSeedRanges = new List<SeedRange>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//DEBUG
|
|
|
|
|
|
|
|
Console.WriteLine("\n");
|
|
|
|
|
|
|
|
//for (long i = range.Start; i <= range.End; i++)
|
|
|
|
|
|
|
|
//{
|
|
|
|
|
|
|
|
// Console.WriteLine($"Seed {i} has value of {Transform(i)}");
|
|
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Ex) Range 55-67
|
|
|
|
|
|
|
|
foreach (var mapData in _mapData)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// Check if any part of rangeA is within rangeB, or vice versa
|
|
|
|
|
|
|
|
bool isRangeWithinMapData = (range.Start >= mapData.Source && range.Start <= mapData.SourceEnd) ||
|
|
|
|
|
|
|
|
(range.End >= mapData.Source && range.End <= mapData.SourceEnd) ||
|
|
|
|
|
|
|
|
(mapData.Source >= range.Start && mapData.Source <= range.End) ||
|
|
|
|
|
|
|
|
(mapData.SourceEnd >= range.Start && mapData.SourceEnd <= range.End);
|
|
|
|
|
|
|
|
//EX) MapData: 50-98 with a range of 48 and a start of 52
|
|
|
|
|
|
|
|
//if (range.Start >= mapData.Source || range.End <= mapData.SourceEnd)
|
|
|
|
|
|
|
|
if (isRangeWithinMapData)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var localizedRange = new SeedRange(Math.Max(0, range.Start - mapData.Source), Math.Min(mapData.Range - 1, range.End - mapData.Source) /* - 1 Since we count the start number minus 1 */);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//For the end range, 92 - 50 = 42 but we do a -1 so why its 41 which is wrong
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var inputStart = mapData.Source + localizedRange.Start; //50 + 5 = 55
|
|
|
|
|
|
|
|
var inputEnd = mapData.Source + localizedRange.End; //50 + 17 = 67
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
originalSeedRanges.Add(new SeedRange()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Start = inputStart,
|
|
|
|
|
|
|
|
End = inputEnd
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var outputStart = mapData.Destination + localizedRange.Start; //52 + 5 = 57
|
|
|
|
|
|
|
|
var outputEnd = mapData.Destination + localizedRange.End; //52 + 17 = 69
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
seedRanges.Add(new SeedRange()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Start = outputStart,
|
|
|
|
|
|
|
|
End = outputEnd
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//For any of the seed ranges created that doesn't cover seed range, create a 1-1 mapping
|
|
|
|
|
|
|
|
var validRangesNotFound = GetNonOverlappingRanges(range, originalSeedRanges.OrderBy(x => x.Start).ToList());
|
|
|
|
|
|
|
|
totalRanges = totalRanges.Concat(seedRanges.Concat(validRangesNotFound)).OrderBy(x => x.Start).ToList();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
totalRanges.ForEach(x => Console.WriteLine($"Range of: {x.Start} to {x.End}"));
|
|
|
|
|
|
|
|
return totalRanges;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private List<SeedRange> GetNonOverlappingRanges(SeedRange rangeA, List<SeedRange> exclusions)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var result = new List<SeedRange>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add the range before the first exclusion, if any
|
|
|
|
|
|
|
|
if (exclusions.Count > 0 && rangeA.Start < exclusions[0].Start)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result.Add(new SeedRange(rangeA.Start, Math.Min(rangeA.End, exclusions[0].Start - 1)));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add non-overlapping ranges between exclusions
|
|
|
|
|
|
|
|
for (int i = 0; i < exclusions.Count - 1; i++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
long start = exclusions[i].End + 1;
|
|
|
|
|
|
|
|
long end = exclusions[i + 1].Start - 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (start <= end)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result.Add(new SeedRange(start, end));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add the range after the last exclusion, if any
|
|
|
|
|
|
|
|
if (exclusions.Count > 0 && rangeA.End > exclusions[exclusions.Count - 1].End)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result.Add(new SeedRange(Math.Max(rangeA.Start, exclusions[exclusions.Count - 1].End + 1), rangeA.End));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (exclusions.Count == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result.Add(rangeA);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SeedRange
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public SeedRange() { }
|
|
|
|
|
|
|
|
public SeedRange(long Start, long End)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
this.Start = Start;
|
|
|
|
|
|
|
|
this.End = End;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public long Start { get; set; }
|
|
|
|
|
|
|
|
public long End { get; set; }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MapData
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public long Destination { get; set; }
|
|
|
|
|
|
|
|
public long Source { get; set; }
|
|
|
|
|
|
|
|
public long SourceEnd { get { return Source + Range; } }
|
|
|
|
|
|
|
|
public long Range { get; set; }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|