Added Day 5 (#1)
continuous-integration/drone/push Build is passing Details

Added Day 5 with comments
pull/2/head
97WaterPolo 12 months ago
parent 2df4382b76
commit f334a7746a

@ -0,0 +1,251 @@
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>();
//Get our seed ranges
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 };
Log($"Transforming seed range {seed.Start} - {seed.End}");
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);
seedRanges.ForEach(x => Log($"Range: {x.Start} - {x.End}"));
prevType = mapType; //Denote our next mapping
}
//Add the output ranges to our total list to be minimized later
finalizedSeedRanges = finalizedSeedRanges.Concat(seedRanges).ToList();
}
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;
}
/// <summary>
/// Generate the given seed ranges for Part B with the large dataset
/// </summary>
/// <param name="ranges"></param>
/// <returns></returns>
public List<SeedRange> GetSeedRanges(List<SeedRange> ranges)
{
//All valid return ranges for the given input seed range
var totalRanges = new List<SeedRange>();
foreach (var range in ranges)
{
var seedRanges = new List<SeedRange>();
var originalSeedRanges = new List<SeedRange>();
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);
if (isRangeWithinMapData)
{
//Calculate the localized range in relation to the given range and the given map data
//Math.Max(0, range.Start - mapData.Source) - In the event the range start is below the start of the mapData, take 0 as that is within the range
// Math.Min(mapData.Range - 1, range.End - mapData.Source) - if the range extends past the bounds of mapData, take mapData -1. -1 is because we count the initial starting source in range
var localizedRange = new SeedRange(Math.Max(0, range.Start - mapData.Source), Math.Min(mapData.Range - 1, range.End - mapData.Source) );
//Calculate the Source values (we need this to track any values that aren't within our output bounds)
var inputStart = mapData.Source + localizedRange.Start;
var inputEnd = mapData.Source + localizedRange.End;
originalSeedRanges.Add(new SeedRange()
{
Start = inputStart,
End = inputEnd
});
//Calculate the valid output ranges for the given mapData (this is the actual transformation for the given ranges)
var outputStart = mapData.Destination + localizedRange.Start;
var outputEnd = mapData.Destination + localizedRange.End;
seedRanges.Add(new SeedRange()
{
Start = outputStart,
End = outputEnd
});
}
}
//FOr all the ranges NOT covered by the original seed range, create a 1-1 mapping.
var validRangesNotFound = GetNonOverlappingRanges(range, originalSeedRanges.OrderBy(x => x.Start).ToList());
//Concat all of the seed ranges together
totalRanges = totalRanges.Concat(seedRanges.Concat(validRangesNotFound)).OrderBy(x => x.Start).ToList();
}
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; }
}
}
Loading…
Cancel
Save