Implements logical operations for indexSets

This commit is contained in:
benbierens 2024-10-14 11:19:40 +02:00
parent a38e93a607
commit 2cbe030cff
No known key found for this signature in database
GPG Key ID: 877D2C2E09A22F3A
3 changed files with 257 additions and 40 deletions

View File

@ -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<SortedList<int, Run>>.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);
}
}

View File

@ -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<Exception>());
Assert.That(() => run.ExpandToInclude(6), Throws.TypeOf<Exception>());
}
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<Exception>());
Assert.That(() => run.ExpandToInclude(3), Throws.TypeOf<Exception>());
}
[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<int>();
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<Run>(), 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<Run>());
return new RunUpdate(
newRuns: [new Run(index + 1, newRunLength)],
removeRuns: Array.Empty<Run>()
);
}
public void Iterate(Action<int> 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

View File

@ -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<int> 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)