213 lines
6.7 KiB
C#
213 lines
6.7 KiB
C#
|
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>());
|
|||
|
}
|
|||
|
}
|