advent_of_code_2024/Level2/DataStreamConstraintVerifier.cs

213 lines
6.7 KiB
C#
Raw Normal View History

2024-12-02 18:51:49 +01:00
namespace AoC24;
using AoC24.Common;
using AoCLevelInputProvider;
using Parsing;
using Parsing.Schema;
public class DataStreamConstraintVerifier<T>
{
public interface IIntegerDataStreamConstraint
{
public int Verify(T leftValue, T rightValue);
}
private class DefaultTrendChecker : IIntegerDataStreamConstraint
{
public int Verify(T leftValue, T rightValue)
{
return string.Compare(leftValue?.ToString(), rightValue?.ToString()) < 0 ? 1 : -1;
}
}
private class DefaultDistanceChecker : IIntegerDataStreamConstraint
{
public int Verify(T leftValue, T rightValue)
{
return string.Compare(leftValue?.ToString(), rightValue?.ToString());
}
}
private List<T> dataSet = new List<T>();
private int distanceLowerLimit = 0;
private int distanceUpperLimit = 1;
private int faultTolerance = int.MaxValue;
private int allowedIgnores = 0;
private int numFaults = 0;
private bool checkFaults = false;
private IIntegerDataStreamConstraint trendChecker = new DefaultTrendChecker();
private IIntegerDataStreamConstraint distanceChecker = new DefaultDistanceChecker();
// compute a trend value between two items in the dataset
// the same trend has to be returned by all checks for the constraint to pass
public DataStreamConstraintVerifier<T> WithTrendChecker(IIntegerDataStreamConstraint trendChecker)
{
this.trendChecker = trendChecker;
return this;
}
public DataStreamConstraintVerifier<T> OnData(List<T> dataSet)
{
this.dataSet = dataSet;
return this;
}
// set number of faults that get ignored until check is considered failed
public DataStreamConstraintVerifier<T> WithFaultTolerance(int faultTolerance)
{
if(this.allowedIgnores > 0)
{
throw new Exception("Cannot enable fault tolerance along with allowed ignores!");
}
this.faultTolerance = faultTolerance;
this.checkFaults = true;
return this;
}
// set number of faults that get ignored until check is considered failed
public DataStreamConstraintVerifier<T> WithAllowedIgnores(int allowedIgnores)
{
if(this.checkFaults)
{
throw new Exception("Cannot enable allowed ignores along with fault tolerance!");
}
this.allowedIgnores = allowedIgnores;
return this;
}
// set specific limits for the distance checks
public DataStreamConstraintVerifier<T> WithDistanceLimits(int lowerLimit, int upperLimit)
{
this.distanceLowerLimit = lowerLimit;
this.distanceUpperLimit = upperLimit;
return this;
}
// compute a distance value between two items in the dataset
// the distance returned by all checks has to be within distance limits for constraint to pass
public DataStreamConstraintVerifier<T> WithDistanceChecker(IIntegerDataStreamConstraint distanceChecker)
{
this.distanceChecker = distanceChecker;
return this;
}
private int TranslateIndex(int index, List<int> indicesToIgnore)
{
var newIndex = index;
foreach(var value in indicesToIgnore)
{
if(value <= index)
{
newIndex++;
}
}
return newIndex;
}
private bool VerifyDistance(int remainingAllowedIgnores, List<int> indicesToIgnore)
{
for(int i = 0; i < (this.dataSet.Count-indicesToIgnore.Count-1); i++)
{
T comparisonLeft = this.dataSet[TranslateIndex(i,indicesToIgnore)];
T comparisonRight = this.dataSet[TranslateIndex(i+1,indicesToIgnore)];
int distance = this.distanceChecker.Verify(comparisonLeft, comparisonRight);
if(distance < this.distanceLowerLimit || distance > this.distanceUpperLimit)
{
this.numFaults++;
// small optimization:
if (!this.checkFaults || this.numFaults > this.faultTolerance)
{
return false;
}
}
}
return true;
}
private bool VerifyTrend(int remainingAllowedIgnores, List<int> indicesToIgnore)
{
T firstComparisonLeft = this.dataSet[TranslateIndex(0,indicesToIgnore)];
T firstComparisonRight = this.dataSet[TranslateIndex(1,indicesToIgnore)];
int initialTrend = this.trendChecker.Verify(firstComparisonLeft, firstComparisonRight);
for(int i = 0; i < (this.dataSet.Count-indicesToIgnore.Count-1); i++)
{
T comparisonLeft = this.dataSet[TranslateIndex(i,indicesToIgnore)];
T comparisonRight = this.dataSet[TranslateIndex(i+1,indicesToIgnore)];
int trend = this.trendChecker.Verify(comparisonLeft, comparisonRight);
if(initialTrend != trend)
{
this.numFaults++;
// small optimization:
if (!this.checkFaults || this.numFaults > this.faultTolerance)
{
return false;
}
}
}
return true;
}
private bool Verify(int remainingAllowedIgnores, List<int> indicesToIgnore)
{
if(remainingAllowedIgnores > 0)
{
bool success = this.Verify(0, indicesToIgnore);
if (success)
{
return true;
}
for(int i = 0; i < this.dataSet.Count; i++)
{
if(indicesToIgnore.Contains(i))
{
continue;
}
List<int> newIndicesToIgnore = new List<int>();
newIndicesToIgnore.AddRange(indicesToIgnore);
newIndicesToIgnore.Add(i);
success = this.Verify(remainingAllowedIgnores-1, newIndicesToIgnore);
if (success)
{
return true;
}
}
return false;
}
else
{
this.numFaults = 0;
bool success = true;
if(this.distanceChecker != null)
{
success = this.VerifyDistance(this.allowedIgnores, indicesToIgnore);
}
if (!success || (this.checkFaults && this.numFaults > this.faultTolerance))
{
return false;
}
if(this.trendChecker != null)
{
success = this.VerifyTrend(this.allowedIgnores, indicesToIgnore);
}
if (this.checkFaults && this.numFaults > this.faultTolerance)
{
return false;
}
return success;
}
}
public bool Verify()
{
return this.Verify(this.allowedIgnores, new List<int>());
}
}