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