namespace AoC24; using AoC24.Common; using AoCLevelInputProvider; using Parsing; using Parsing.Schema; public class DataStreamConstraintVerifier { 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 dataSet = new List(); 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 WithTrendChecker(IIntegerDataStreamConstraint trendChecker) { this.trendChecker = trendChecker; return this; } public DataStreamConstraintVerifier OnData(List dataSet) { this.dataSet = dataSet; return this; } // set number of faults that get ignored until check is considered failed public DataStreamConstraintVerifier 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 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 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 WithDistanceChecker(IIntegerDataStreamConstraint distanceChecker) { this.distanceChecker = distanceChecker; return this; } private int TranslateIndex(int index, List indicesToIgnore) { var newIndex = index; foreach(var value in indicesToIgnore) { if(value <= index) { newIndex++; } } return newIndex; } private bool VerifyDistance(int remainingAllowedIgnores, List 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 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 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 newIndicesToIgnore = new List(); 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()); } }