namespace FrameworkTests.Utils { public class Run { public Run(int start, int length) { Start = start; Length = length; } public int Start { get; } public int Length { get; private set; } public bool Includes(int index) { return index >= Start && index < (Start + Length); } 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 new RunUpdate(); } 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) { if (!Includes(index)) { return new RunUpdate(); } if (index == Start) { // First index: Replace self with new run at next index, unless empty. if (Length == 1) { return new RunUpdate( newRuns: Array.Empty(), removeRuns: [this] ); } return new RunUpdate( newRuns: [new Run(Start + 1, Length - 1)], removeRuns: [this] ); } if (index == (Start + Length - 1)) { // Last index: Become one smaller. Length--; return new RunUpdate(); } // Split: var newRunLength = (Start + Length - 1) - index; Length = index - Start; return new RunUpdate( newRuns: [new Run(index + 1, newRunLength)], removeRuns: Array.Empty() ); } public void Iterate(Action action) { for (var i = 0; i < Length; 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 RunUpdate() : this(Array.Empty(), Array.Empty()) { } public RunUpdate(Run[] newRuns, Run[] removeRuns) { NewRuns = newRuns; RemoveRuns = removeRuns; } public Run[] NewRuns { get; } public Run[] RemoveRuns { get; } } public partial class IndexSet { private readonly SortedList runs = new SortedList(); public IndexSet() { } public IndexSet(int[] indices) { foreach (var i in indices) Set(i); } public static IndexSet FromRunLengthEncoded(int[] rle) { var set = new IndexSet(); for (var i = 0; i < rle.Length; i += 2) { var start = rle[i]; var length = rle[i + 1]; set.runs.Add(start, new Run(start, length)); } return set; } public bool IsSet(int index) { if (runs.ContainsKey(index)) return true; var run = GetRunAt(index); if (run == null) return false; return true; } public void Set(int index) { if (IsSet(index)) return; var runBefore = GetRunAt(index - 1); var runAfter = GetRunExact(index + 1); if (runBefore == null) { 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] )); } } } public void Unset(int index) { if (runs.ContainsKey(index)) { HandleUpdate(runs[index].Unset(index)); } else { var run = GetRunAt(index); if (run == null) return; HandleUpdate(run.Unset(index)); } } public void Iterate(Action onIndex) { foreach (var run in runs.Values) { run.Iterate(onIndex); } } public int[] RunLengthEncoded() { return Encode().ToArray(); } public override string ToString() { return string.Join("&", runs.Select(r => r.ToString()).ToArray()); } private IEnumerable Encode() { foreach (var pair in runs) { yield return pair.Value.Start; yield return pair.Value.Length; } } private Run? GetRunAt(int index) { foreach (var run in runs.Values) { if (run.Includes(index)) return run; } return null; } private Run? GetRunExact(int index) { if (runs.ContainsKey(index)) return runs[index]; return null; } private void HandleUpdate(RunUpdate runUpdate) { 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) { if (runs.ContainsKey(index + 1)) { var length = runs[index + 1].Length + 1; runs.Add(index, new Run(index, length)); runs.Remove(index + 1); } else { runs.Add(index, new Run(index, 1)); } } } public partial class IndexSet { public IndexSet Overlap(IndexSet other) { var result = new IndexSet(); Iterate(i => { if (other.IsSet(i)) result.Set(i); }); return result; } public IndexSet Merge(IndexSet other) { var result = new IndexSet(); Iterate(result.Set); other.Iterate(result.Set); return result; } public IndexSet Without(IndexSet other) { var result = new IndexSet(); Iterate(i => { if (!other.IsSet(i)) result.Set(i); }); return result; } public override bool Equals(object? obj) { 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); } }