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] [TestFixture]
public class RunLengthEncodingLogicalTests 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] [Test]
public void Overlap() public void Overlap()
{ {
@ -46,28 +96,59 @@ namespace FrameworkTests.Utils
{ {
public IndexSet Overlap(IndexSet other) 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) 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) 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) public override bool Equals(object? obj)
{ {
return obj is IndexSet set && if (obj is IndexSet set)
EqualityComparer<SortedList<int, Run>>.Default.Equals(runs, set.runs); {
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() public override int GetHashCode()
{ {
return HashCode.Combine(runs); 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] [TestFixture]
public class RunLengthEncodingRunTests 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] [Test]
[Combinatorial] [Combinatorial]
public void RunIncludes( public void RunIncludes(
@ -31,23 +61,58 @@ namespace FrameworkTests.Utils
} }
[Test] [Test]
public void RunExpandToInclude() public void RunExpandThrowsWhenIndexNotAdjacent()
{ {
var run = new Run(2, 3); var run = new Run(2, 3);
Assert.That(!run.Includes(1));
Assert.That(run.Includes(2)); Assert.That(run.Includes(2));
Assert.That(run.Includes(4)); Assert.That(run.Includes(4));
Assert.That(!run.Includes(5)); Assert.That(!run.Includes(5));
Assert.That(run.ExpandToInclude(1), Is.False); Assert.That(() => run.ExpandToInclude(0), Throws.TypeOf<Exception>());
Assert.That(run.ExpandToInclude(2), Is.False); Assert.That(() => run.ExpandToInclude(6), Throws.TypeOf<Exception>());
Assert.That(run.ExpandToInclude(4), Is.False); }
Assert.That(run.ExpandToInclude(6), Is.False);
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(5));
Assert.That(!run.Includes(6)); 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] [Test]
public void RunCanUnsetLastIndex() public void RunCanUnsetLastIndex()
{ {
@ -98,7 +163,7 @@ namespace FrameworkTests.Utils
{ {
var run = new Run(2, 4); var run = new Run(2, 4);
var seen = new List<int>(); var seen = new List<int>();
run.Iterate(i => seen.Add(i)); run.Iterate(seen.Add);
CollectionAssert.AreEqual(new[] { 2, 3, 4, 5 }, seen); CollectionAssert.AreEqual(new[] { 2, 3, 4, 5 }, seen);
} }
@ -120,14 +185,22 @@ namespace FrameworkTests.Utils
return index >= Start && index < (Start + Length); 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)) if (index == (Start + Length))
{ {
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) public RunUpdate Unset(int index)
@ -145,8 +218,8 @@ namespace FrameworkTests.Utils
return new RunUpdate(Array.Empty<Run>(), new[] { this }); return new RunUpdate(Array.Empty<Run>(), new[] { this });
} }
return new RunUpdate( return new RunUpdate(
newRuns: new[] { new Run(Start + 1, Length - 1) }, newRuns: [new Run(Start + 1, Length - 1)],
removeRuns: new[] { this } removeRuns: [this]
); );
} }
@ -160,7 +233,10 @@ namespace FrameworkTests.Utils
// Split: // Split:
var newRunLength = (Start + Length - 1) - index; var newRunLength = (Start + Length - 1) - index;
Length = index - Start; 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) public void Iterate(Action<int> action)
@ -170,6 +246,32 @@ namespace FrameworkTests.Utils
action(Start + i); 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 public class RunUpdate

View File

@ -114,6 +114,19 @@ namespace FrameworkTests.Utils
}, encoded); }, 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] [Test]
public void SetIndexAfterRun() public void SetIndexAfterRun()
{ {
@ -228,21 +241,44 @@ namespace FrameworkTests.Utils
{ {
if (runs.ContainsKey(index)) return true; if (runs.ContainsKey(index)) return true;
var run = GetRunBefore(index); var run = GetRunAt(index);
if (run == null) return false; if (run == null) return false;
return true;
return run.Includes(index);
} }
public void Set(int index) public void Set(int index)
{ {
if (runs.ContainsKey(index)) return; if (IsSet(index)) return;
var run = GetRunBefore(index); var runBefore = GetRunAt(index - 1);
if (run == null || !run.ExpandToInclude(index)) var runAfter = GetRunExact(index + 1);
if (runBefore == null)
{
if (runAfter == null)
{ {
CreateNewRun(index); 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]
));
}
}
} }
public void Unset(int index) public void Unset(int index)
@ -253,7 +289,7 @@ namespace FrameworkTests.Utils
} }
else else
{ {
var run = GetRunBefore(index); var run = GetRunAt(index);
if (run == null) return; if (run == null) return;
HandleUpdate(run.Unset(index)); HandleUpdate(run.Unset(index));
} }
@ -274,13 +310,7 @@ namespace FrameworkTests.Utils
public override string ToString() public override string ToString()
{ {
var result = ""; return string.Join("&", runs.Select(r => r.ToString()).ToArray());
var encoded = RunLengthEncoded();
foreach (var pair in runs)
{
result += $"[{pair.Value.Start},{pair.Value.Length}]]";
}
return result;
} }
private IEnumerable<int> Encode() 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 run in runs.Values)
foreach (var pair in runs)
{ {
if (pair.Key < index) result = pair.Value; if (run.Includes(index)) return run;
else return result;
} }
return result; return null;
}
private Run? GetRunExact(int index)
{
if (runs.ContainsKey(index)) return runs[index];
return null;
} }
private void HandleUpdate(RunUpdate runUpdate) 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 removeRun in runUpdate.RemoveRuns) runs.Remove(removeRun.Start);
foreach (var newRun in runUpdate.NewRuns) runs.Add(newRun.Start, newRun);
} }
private void CreateNewRun(int index) private void CreateNewRun(int index)