/* * Copyright 2018 faddenSoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; namespace CommonUtil { /// /// Compact representation of a set of typed integers that tend to be adjacent. /// We expect there to be relatively few different types of things. /// /// The default enumeration is a series of integers, not a series of ranges. Use /// RangeListIterator to get the latter. /// /// Most operations operate in log(N) time, where N is the number of /// regions. /// public class TypedRangeSet : IEnumerable { /// /// List of ranges, in sorted order. /// private List mRangeList = new List(); /// /// Number of values in the set. /// public int Count { get; private set; } /// /// Returns the number of Range elements in the list. /// public int RangeCount { get { return mRangeList.Count; } } /// /// Represents a contiguous range of values. /// public struct TypedRange { /// /// Lowest value (inclusive). /// public int Low { get; set; } /// /// Highest value (inclusive). /// public int High { get; set; } /// /// Value type in this range. /// public int Type { get; set; } public TypedRange(int low, int high, int type) { Debug.Assert(low <= high); Low = low; High = high; Type = type; } public bool Contains(int val) { return (val >= Low && val <= High); } } /// /// Value + type pair. Returned from foreach enumerator. /// public struct Tuple { public int Value; public int Type; public Tuple(int value, int type) { Value = value; Type = type; } public static bool operator ==(Tuple a, Tuple b) { return a.Value == b.Value && a.Type == b.Type; } public static bool operator !=(Tuple a, Tuple b) { return !(a == b); } public override bool Equals(object obj) { return obj is Tuple && this == (Tuple)obj; } public override int GetHashCode() { return Value ^ Type; } public override string ToString() { return Value + " (" + Type + ")"; } } /// /// Iterator definition. /// private class TypedRangeSetIterator : IEnumerator { /// /// The TypedRangeSet we're iterating over. /// private TypedRangeSet mSet; // Index of current Range element in mSet.mRangeList. private int mListIndex = -1; // Current range, extracted from mRangeList. private TypedRange mCurrentRange; // Current value in mCurrentRange. private int mCurrentVal; /// /// Constructor. /// /// TypedRangeSet to iterate over. public TypedRangeSetIterator(TypedRangeSet set) { mSet = set; Reset(); } // IEnumerator: current element public object Current { get { if (mListIndex < 0) { // not started return null; } return new Tuple(mCurrentVal, mCurrentRange.Type); } } // IEnumerator Tuple IEnumerator.Current { get { return (Tuple)Current; } } /// /// Puts the next range in the list in mCurrentRange. /// /// True on success, false if we reached the end of the list. private bool GetNextRange() { mListIndex++; // increments to 0 on first invocation if (mListIndex == mSet.mRangeList.Count) { // no more ranges return false; } mCurrentRange = mSet.mRangeList[mListIndex]; mCurrentVal = mCurrentRange.Low; return true; } // IEnumerator: move to the next element, returning false if there isn't one public bool MoveNext() { if (mListIndex < 0) { // just started return GetNextRange(); } else { // iterating within range object mCurrentVal++; if (mCurrentVal > mCurrentRange.High) { // finished with this one, move on to the next return GetNextRange(); } else { return true; } } } // IEnumerator: reset state public void Reset() { mListIndex = -1; } // IEnumerator public void Dispose() { mSet = null; } } /// /// Constructor. Creates an empty set. /// public TypedRangeSet() { Count = 0; } /// /// Returns an enumerator that iterates through the range list, returning Range objects. /// public IEnumerator RangeListIterator { get { return mRangeList.GetEnumerator(); } } /// /// Removes all values from the set. /// public void Clear() { mRangeList.Clear(); Count = 0; } // IEnumerable: get an enumerator instance that returns integer values public IEnumerator GetEnumerator() { return new TypedRangeSetIterator(this); } // IEnumerable IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator)GetEnumerator(); } /// /// Finds the range that contains "val", or an appropriate place in the list to /// insert a new range. /// /// Value to find. /// The index of the matching element, or a negative value indicating /// the index to insert at. 2C doesn't support negative 0, so the insertion /// index will be incremented before negation. private int FindValue(int val) { int low = 0; int high = mRangeList.Count - 1; while (low <= high) { int mid = (low + high) / 2; TypedRange midRange = mRangeList[mid]; if (midRange.Contains(val)) { // found it return mid; } else if (val < midRange.Low) { // too big, move the high end in high = mid - 1; } else if (val > midRange.High) { // too small, move the low end in low = mid + 1; } else { // WTF... list not sorted? throw new Exception("Bad binary search"); } } // Not found, insert before "low". return -(low + 1); } /// /// Determines whether val is a member of the set. /// /// Value to check. /// True if the value is a member of the set. public bool Contains(int val) { return (FindValue(val) >= 0); } #if false /// /// Finds a range that contains searchVal, or identifies the one that immediately /// follows. The caller can determine which by checking to see if range.Low is /// greater than searchVal. /// /// Value to find. /// Result. /// True if a valid range was returned. public bool GetContainingOrSubsequentRange(int searchVal, out TypedRange range) { int index = FindValue(searchVal); if (index >= 0) { // found a range that contains val range = mRangeList[index]; return true; } // No matching range, so the index of the insertion point was returned. The // indexed range will have a "low" value that is greater than searchVal. If // we've reached the end of the list, the index will be past the end. index = -index - 1; if (index >= mRangeList.Count) { // reached the end of the list range = new TypedRange(-128, -128, -128); return false; } range = mRangeList[index]; return true; } #endif /// /// Gets the type of the specified value. /// /// Value to query. /// Receives the type, or -1 if the value is not in the set. /// True if the value is in the set. public bool GetType(int val, out int type) { int listIndex = FindValue(val); if (listIndex >= 0) { type = mRangeList[listIndex].Type; return true; } else { type = -1; return false; } } /// /// Adds or changes a value to the set. If the value is already present and has /// a matching type, nothing changes. /// /// Value to add. /// Value's type. public void Add(int val, int type) { int listIndex = FindValue(val); if (listIndex >= 0) { // Value is present in set, check type. if (mRangeList[listIndex].Type == type) { // It's a match, do nothing. return; } // Wrong type. Remove previous entry, then fall through to add new. Remove(val); listIndex = FindValue(val); // get insertion point } Count++; if (mRangeList.Count == 0) { // Empty list, skip the gymnastics. mRangeList.Add(new TypedRange(val, val, type)); return; } // Negate and decrement to get insertion index. This value may == Count if // the value is higher than all current members. listIndex = -listIndex - 1; if (listIndex > 0 && mRangeList[listIndex - 1].High == val - 1 && mRangeList[listIndex - 1].Type == type) { // Expand prior range. Check to see if it blends into next as well. if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1 && mRangeList[listIndex].Type == type) { // Combine ranges. TypedRange prior = mRangeList[listIndex - 1]; TypedRange next = mRangeList[listIndex]; Debug.Assert(prior.High + 2 == next.Low); prior.High = next.High; mRangeList[listIndex - 1] = prior; mRangeList.RemoveAt(listIndex); } else { // Nope, just expand the prior range. TypedRange prior = mRangeList[listIndex - 1]; Debug.Assert(prior.High == val - 1); prior.High = val; mRangeList[listIndex - 1] = prior; } } else if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1 && mRangeList[listIndex].Type == type) { // Expand next range. TypedRange next = mRangeList[listIndex]; Debug.Assert(next.Low == val + 1); next.Low = val; mRangeList[listIndex] = next; } else { // Nothing adjacent, add a new single-entry element. mRangeList.Insert(listIndex, new TypedRange(val, val, type)); } } /// /// Adds a range of contiguous values to the set. /// /// Lowest value (inclusive). /// Highest value (inclusive). /// Value type. public void AddRange(int low, int high, int type) { // There's probably some very efficient way to do this. Keeping it simple for now. // (TODO: do a quick check to see if there's anything overlapping; if not, just // create a new item and insert it into the list. Should handle the common case.) Debug.Assert(low <= high); // adding an empty set is valid but weird for (int i = low; i <= high; i++) { Add(i, type); } } /// /// Removes a value from the set. If the value is not present, nothing changes. /// /// Value to remove. public void Remove(int val) { int listIndex = FindValue(val); if (listIndex < 0) { // not found return; } Count--; TypedRange rng = mRangeList[listIndex]; if (rng.Low == val && rng.High == val) { // Single-value range. Remove. mRangeList.RemoveAt(listIndex); } else if (rng.Low == val) { // We're at the low end, reduce range. rng.Low = val + 1; mRangeList[listIndex] = rng; } else if (rng.High == val) { // We're at the high end, reduce range. rng.High = val - 1; mRangeList[listIndex] = rng; } else { // We're in the middle, split the range. TypedRange next = new TypedRange(val + 1, rng.High, rng.Type); rng.High = val - 1; mRangeList[listIndex] = rng; mRangeList.Insert(listIndex + 1, next); } } public void DebugDump(string name) { Debug.WriteLine(name + " has " + RangeCount + " ranges"); IEnumerator iter = RangeListIterator; while (iter.MoveNext()) { TypedRange rng = iter.Current; Debug.WriteLine("[+{0:x6},+{1:x6}] ({2:x2})", rng.Low, rng.High, rng.Type); } } /// /// Internal test function. /// private static bool CheckTypedRangeSet(TypedRangeSet set, int expectedRanges, Tuple[] expected) { if (set.RangeCount != expectedRanges) { Debug.WriteLine("Expected " + expectedRanges + " ranges, got " + set.RangeCount); return false; } // Compare actual vs. expected. If we have more actual than expected we'll // throw on the array access. int expIndex = 0; foreach (TypedRangeSet.Tuple val in set) { if (val != expected[expIndex]) { Debug.WriteLine("Expected " + expected[expIndex] + ", got " + val); return false; } expIndex++; } // See if we have more expected than actual. if (expIndex != expected.Length) { Debug.WriteLine("Expected " + expected.Length + " elements, found " + expIndex); return false; } // The count is maintained separately, so check it. if (set.Count != expected.Length) { Debug.WriteLine("Expected Count=" + expected.Length + ", got " + set.Count); return false; } return true; } /// /// Executes unit tests. /// /// True if all goes well. public static bool Test() { bool result = true; TypedRangeSet one = new TypedRangeSet(); one.Add(7, 100); one.Add(5, 100); one.Add(3, 100); one.Add(9, 100); one.Add(7, 100); one.Add(8, 100); one.Add(2, 100); one.Add(4, 100); result &= CheckTypedRangeSet(one, 2, new Tuple[] { new Tuple(2, 100), new Tuple(3, 100), new Tuple(4, 100), new Tuple(5, 100), new Tuple(7, 100), new Tuple(8, 100), new Tuple(9, 100) }); one.Remove(2); one.Remove(9); one.Remove(4); result &= CheckTypedRangeSet(one, 3, new Tuple[] { new Tuple(3, 100), new Tuple(5, 100), new Tuple(7, 100), new Tuple(8, 100) }); one.Clear(); one.Add(1, 200); one.Add(3, 100); one.Add(7, 100); one.Add(5, 100); one.Add(9, 100); one.Add(6, 100); one.Add(8, 100); one.Add(6, 200); one.Add(2, 200); one.Add(4, 300); one.Add(4, 100); result &= CheckTypedRangeSet(one, 4, new Tuple[] { new Tuple(1, 200), new Tuple(2, 200), new Tuple(3, 100), new Tuple(4, 100), new Tuple(5, 100), new Tuple(6, 200), new Tuple(7, 100), new Tuple(8, 100), new Tuple(9, 100) }); one.Add(6, 100); result &= CheckTypedRangeSet(one, 2, new Tuple[] { new Tuple(1, 200), new Tuple(2, 200), new Tuple(3, 100), new Tuple(4, 100), new Tuple(5, 100), new Tuple(6, 100), new Tuple(7, 100), new Tuple(8, 100), new Tuple(9, 100) }); Debug.WriteLine("TypedRangeSet: test complete (ok=" + result + ")"); return result; } } }