Implements logical operations for indexSets
This commit is contained in:
parent
a38e93a607
commit
2cbe030cff
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,20 +241,43 @@ 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)
|
||||||
{
|
{
|
||||||
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
|
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)
|
||||||
|
|
Loading…
Reference in New Issue