diff --git a/Tests/FrameworkTests/Utils/RunLengthEncodingLogicalTests.cs b/Tests/FrameworkTests/Utils/RunLengthEncodingLogicalTests.cs index e212d24..b2a2f4a 100644 --- a/Tests/FrameworkTests/Utils/RunLengthEncodingLogicalTests.cs +++ b/Tests/FrameworkTests/Utils/RunLengthEncodingLogicalTests.cs @@ -5,6 +5,56 @@ namespace FrameworkTests.Utils [TestFixture] public class RunLengthEncodingLogicalTests { + [Test] + public void EqualityTest() + { + var setA = new IndexSet([1, 2, 3, 4]); + var setB = new IndexSet([1, 2, 3, 4]); + + Assert.That(setA, Is.EqualTo(setB)); + Assert.That(setA == setB); + } + + [Test] + public void InequalityTest1() + { + var setA = new IndexSet([1, 2, 4, 5]); + var setB = new IndexSet([1, 2, 3, 4]); + + Assert.That(setA, Is.Not.EqualTo(setB)); + Assert.That(setA != setB); + } + + [Test] + public void InequalityTest2() + { + var setA = new IndexSet([1, 2, 3]); + var setB = new IndexSet([1, 2, 3, 4]); + + Assert.That(setA, Is.Not.EqualTo(setB)); + Assert.That(setA != setB); + } + + [Test] + public void InequalityTest3() + { + var setA = new IndexSet([2, 3, 4, 5]); + var setB = new IndexSet([1, 2, 3, 4]); + + Assert.That(setA, Is.Not.EqualTo(setB)); + Assert.That(setA != setB); + } + + [Test] + public void InequalityTest() + { + var setA = new IndexSet([2, 3, 4]); + var setB = new IndexSet([1, 2, 3, 4]); + + Assert.That(setA, Is.Not.EqualTo(setB)); + Assert.That(setA != setB); + } + [Test] public void Overlap() { @@ -46,28 +96,59 @@ namespace FrameworkTests.Utils { public IndexSet Overlap(IndexSet other) { - return this; + var result = new IndexSet(); + Iterate(i => + { + if (other.IsSet(i)) result.Set(i); + }); + return result; } public IndexSet Merge(IndexSet other) { - return this; + var result = new IndexSet(); + Iterate(result.Set); + other.Iterate(result.Set); + return result; } public IndexSet Without(IndexSet other) { - return this; + var result = new IndexSet(); + Iterate(i => + { + if (!other.IsSet(i)) result.Set(i); + }); + return result; } public override bool Equals(object? obj) { - return obj is IndexSet set && - EqualityComparer>.Default.Equals(runs, set.runs); + if (obj is IndexSet set) + { + if (set.runs.Count != runs.Count) return false; + foreach (var pair in runs) + { + if (!set.runs.ContainsKey(pair.Key)) return false; + if (set.runs[pair.Key] != pair.Value) return false; + } + return true; + } + return false; } public override int GetHashCode() { return HashCode.Combine(runs); } + + public static bool operator ==(IndexSet? obj1, IndexSet? obj2) + { + if (ReferenceEquals(obj1, obj2)) return true; + if (ReferenceEquals(obj1, null)) return false; + if (ReferenceEquals(obj2, null)) return false; + return obj1.Equals(obj2); + } + public static bool operator !=(IndexSet? obj1, IndexSet? obj2) => !(obj1 == obj2); } } diff --git a/Tests/FrameworkTests/Utils/RunLengthEncodingRunTests.cs b/Tests/FrameworkTests/Utils/RunLengthEncodingRunTests.cs index 0f2730d..83c1e5d 100644 --- a/Tests/FrameworkTests/Utils/RunLengthEncodingRunTests.cs +++ b/Tests/FrameworkTests/Utils/RunLengthEncodingRunTests.cs @@ -5,6 +5,36 @@ namespace FrameworkTests.Utils [TestFixture] public class RunLengthEncodingRunTests { + [Test] + public void EqualityTest() + { + var runA = new Run(1, 4); + var runB = new Run(1, 4); + + Assert.That(runA, Is.EqualTo(runB)); + Assert.That(runA == runB); + } + + [Test] + public void InequalityTest1() + { + var runA = new Run(1, 4); + var runB = new Run(1, 5); + + Assert.That(runA, Is.Not.EqualTo(runB)); + Assert.That(runA != runB); + } + + [Test] + public void InequalityTest2() + { + var runA = new Run(1, 4); + var runB = new Run(2, 4); + + Assert.That(runA, Is.Not.EqualTo(runB)); + Assert.That(runA != runB); + } + [Test] [Combinatorial] public void RunIncludes( @@ -31,23 +61,58 @@ namespace FrameworkTests.Utils } [Test] - public void RunExpandToInclude() + public void RunExpandThrowsWhenIndexNotAdjacent() { var run = new Run(2, 3); + Assert.That(!run.Includes(1)); Assert.That(run.Includes(2)); Assert.That(run.Includes(4)); Assert.That(!run.Includes(5)); - Assert.That(run.ExpandToInclude(1), Is.False); - Assert.That(run.ExpandToInclude(2), Is.False); - Assert.That(run.ExpandToInclude(4), Is.False); - Assert.That(run.ExpandToInclude(6), Is.False); + Assert.That(() => run.ExpandToInclude(0), Throws.TypeOf()); + Assert.That(() => run.ExpandToInclude(6), Throws.TypeOf()); + } - Assert.That(run.ExpandToInclude(5), Is.True); + [Test] + public void RunExpandThrowsWhenIndexAlreadyIncluded() + { + var run = new Run(2, 3); + Assert.That(!run.Includes(1)); + Assert.That(run.Includes(2)); + Assert.That(run.Includes(4)); + Assert.That(!run.Includes(5)); + + Assert.That(() => run.ExpandToInclude(2), Throws.TypeOf()); + Assert.That(() => run.ExpandToInclude(3), Throws.TypeOf()); + } + + [Test] + public void RunExpandToIncludeAfter() + { + var run = new Run(2, 3); + var update = run.ExpandToInclude(5); + Assert.That(update, Is.Not.Null); + Assert.That(update.NewRuns.Length, Is.EqualTo(0)); + Assert.That(update.RemoveRuns.Length, Is.EqualTo(0)); Assert.That(run.Includes(5)); Assert.That(!run.Includes(6)); } + [Test] + public void RunExpandToIncludeBefore() + { + var run = new Run(2, 3); + var update = run.ExpandToInclude(1); + + Assert.That(update, Is.Not.Null); + Assert.That(update.NewRuns.Length, Is.EqualTo(1)); + Assert.That(update.RemoveRuns.Length, Is.EqualTo(1)); + + Assert.That(update.RemoveRuns[0], Is.SameAs(run)); + Assert.That(update.NewRuns[0].Start, Is.EqualTo(1)); + Assert.That(update.NewRuns[0].Length, Is.EqualTo(4)); + } + [Test] public void RunCanUnsetLastIndex() { @@ -98,7 +163,7 @@ namespace FrameworkTests.Utils { var run = new Run(2, 4); var seen = new List(); - run.Iterate(i => seen.Add(i)); + run.Iterate(seen.Add); CollectionAssert.AreEqual(new[] { 2, 3, 4, 5 }, seen); } @@ -120,14 +185,22 @@ namespace FrameworkTests.Utils return index >= Start && index < (Start + Length); } - public bool ExpandToInclude(int index) + public RunUpdate ExpandToInclude(int index) { + if (Includes(index)) throw new Exception("Run already includes this index. Run: {ToString()} index: {index}"); if (index == (Start + Length)) { Length++; - return true; + return new RunUpdate(); } - return false; + if (index == (Start - 1)) + { + return new RunUpdate( + newRuns: [new Run(Start - 1, Length + 1)], + removeRuns: [this] + ); + } + throw new Exception($"Run cannot expand to include index. Run: {ToString()} index: {index}"); } public RunUpdate Unset(int index) @@ -145,8 +218,8 @@ namespace FrameworkTests.Utils return new RunUpdate(Array.Empty(), new[] { this }); } return new RunUpdate( - newRuns: new[] { new Run(Start + 1, Length - 1) }, - removeRuns: new[] { this } + newRuns: [new Run(Start + 1, Length - 1)], + removeRuns: [this] ); } @@ -160,7 +233,10 @@ namespace FrameworkTests.Utils // Split: var newRunLength = (Start + Length - 1) - index; Length = index - Start; - return new RunUpdate(new[] { new Run(index + 1, newRunLength) }, Array.Empty()); + return new RunUpdate( + newRuns: [new Run(index + 1, newRunLength)], + removeRuns: Array.Empty() + ); } public void Iterate(Action action) @@ -170,6 +246,32 @@ namespace FrameworkTests.Utils action(Start + i); } } + + public override string ToString() + { + return $"[{Start},{Length}]"; + } + + public override bool Equals(object? obj) + { + return obj is Run run && + Start == run.Start && + Length == run.Length; + } + + public override int GetHashCode() + { + return HashCode.Combine(Start, Length); + } + + public static bool operator ==(Run? obj1, Run? obj2) + { + if (ReferenceEquals(obj1, obj2)) return true; + if (ReferenceEquals(obj1, null)) return false; + if (ReferenceEquals(obj2, null)) return false; + return obj1.Equals(obj2); + } + public static bool operator !=(Run? obj1, Run? obj2) => !(obj1 == obj2); } public class RunUpdate diff --git a/Tests/FrameworkTests/Utils/RunLengthEncodingTests.cs b/Tests/FrameworkTests/Utils/RunLengthEncodingTests.cs index 6d9fe2a..4e12f90 100644 --- a/Tests/FrameworkTests/Utils/RunLengthEncodingTests.cs +++ b/Tests/FrameworkTests/Utils/RunLengthEncodingTests.cs @@ -114,6 +114,19 @@ namespace FrameworkTests.Utils }, encoded); } + [Test] + public void SetIndexBetweenRuns() + { + var set = new IndexSet(new[] {8, 9, 10, 12, 13, 14 }); + set.Set(11); + var encoded = set.RunLengthEncoded(); + + CollectionAssert.AreEqual(new[] + { + 8, 7 + }, encoded); + } + [Test] public void SetIndexAfterRun() { @@ -228,20 +241,43 @@ namespace FrameworkTests.Utils { if (runs.ContainsKey(index)) return true; - var run = GetRunBefore(index); + var run = GetRunAt(index); if (run == null) return false; - - return run.Includes(index); + return true; } public void Set(int index) { - if (runs.ContainsKey(index)) return; + if (IsSet(index)) return; - var run = GetRunBefore(index); - if (run == null || !run.ExpandToInclude(index)) + var runBefore = GetRunAt(index - 1); + var runAfter = GetRunExact(index + 1); + + if (runBefore == null) { - CreateNewRun(index); + if (runAfter == null) + { + CreateNewRun(index); + } + else + { + HandleUpdate(runAfter.ExpandToInclude(index)); + } + } + else + { + if (runAfter == null) + { + HandleUpdate(runBefore.ExpandToInclude(index)); + } + else + { + // new index will connect runBefore with runAfter. We merge! + HandleUpdate(new RunUpdate( + newRuns: [new Run(runBefore.Start, runBefore.Length + 1 + runAfter.Length)], + removeRuns: [runBefore, runAfter] + )); + } } } @@ -253,7 +289,7 @@ namespace FrameworkTests.Utils } else { - var run = GetRunBefore(index); + var run = GetRunAt(index); if (run == null) return; HandleUpdate(run.Unset(index)); } @@ -274,13 +310,7 @@ namespace FrameworkTests.Utils public override string ToString() { - var result = ""; - var encoded = RunLengthEncoded(); - foreach (var pair in runs) - { - result += $"[{pair.Value.Start},{pair.Value.Length}]]"; - } - return result; + return string.Join("&", runs.Select(r => r.ToString()).ToArray()); } private IEnumerable Encode() @@ -292,21 +322,25 @@ namespace FrameworkTests.Utils } } - private Run? GetRunBefore(int index) + private Run? GetRunAt(int index) { - Run? result = null; - foreach (var pair in runs) + foreach (var run in runs.Values) { - if (pair.Key < index) result = pair.Value; - else return result; + if (run.Includes(index)) return run; } - return result; + return null; + } + + private Run? GetRunExact(int index) + { + if (runs.ContainsKey(index)) return runs[index]; + return null; } private void HandleUpdate(RunUpdate runUpdate) { - foreach (var newRun in runUpdate.NewRuns) runs.Add(newRun.Start, newRun); foreach (var removeRun in runUpdate.RemoveRuns) runs.Remove(removeRun.Start); + foreach (var newRun in runUpdate.NewRuns) runs.Add(newRun.Start, newRun); } private void CreateNewRun(int index)