// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System.Collections.Generic; using System.Diagnostics; using System; using System.Text; namespace Avalonia.Controls { internal class IndexToValueTable : IEnumerable> { private List> _list; public IndexToValueTable() { _list = new List>(); } /// /// Total number of indices represented in the table /// public int IndexCount { get { int indexCount = 0; foreach (Range range in _list) { indexCount += range.Count; } return indexCount; } } /// /// Returns true if the table is empty /// public bool IsEmpty { get { return _list.Count == 0; } } /// /// Returns the number of index ranges in the table /// public int RangeCount { get { return _list.Count; } } /// /// Add a value with an associated index to the table /// /// Index where the value is to be added or updated /// Value to add public void AddValue(int index, T value) { AddValues(index, 1, value); } /// /// Add multiples values with an associated start index to the table /// /// index where first value is added /// Total number of values to add (must be greater than 0) /// Value to add public void AddValues(int startIndex, int count, T value) { Debug.Assert(count > 0); AddValuesPrivate(startIndex, count, value, null); } /// /// Clears the index table /// public void Clear() { _list.Clear(); } /// /// Returns true if the given index is contained in the table /// /// index to search for /// True if the index is contained in the table public bool Contains(int index) { return IsCorrectRangeIndex(this.FindRangeIndex(index), index); } /// /// Returns true if the entire given index range is contained in the table /// /// beginning of the range /// end of the range /// True if the entire index range is present in the table public bool ContainsAll(int startIndex, int endIndex) { int start = -1; int end = -1; foreach (Range range in _list) { if (start == -1 && range.UpperBound >= startIndex) { if (startIndex < range.LowerBound) { return false; } start = startIndex; end = range.UpperBound; if (end >= endIndex) { return true; } } else if (start != -1) { if (range.LowerBound > end + 1) { return false; } end = range.UpperBound; if (end >= endIndex) { return true; } } } return false; } /// /// Returns true if the given index is contained in the table with the the given value /// /// index to search for /// value expected /// true if the given index is contained in the table with the the given value public bool ContainsIndexAndValue(int index, T value) { int lowerRangeIndex = this.FindRangeIndex(index); return ((IsCorrectRangeIndex(lowerRangeIndex, index)) && (_list[lowerRangeIndex].ContainsValue(value))); } /// /// Returns a copy of this IndexToValueTable /// /// copy of this IndexToValueTable public IndexToValueTable Copy() { IndexToValueTable copy = new IndexToValueTable(); foreach (Range range in this._list) { copy._list.Add(range.Copy()); } return copy; } public int GetNextGap(int index) { int targetIndex = index + 1; int rangeIndex = FindRangeIndex(targetIndex); if (IsCorrectRangeIndex(rangeIndex, targetIndex)) { while (rangeIndex < _list.Count - 1 && _list[rangeIndex].UpperBound == _list[rangeIndex + 1].LowerBound - 1) { rangeIndex++; } return _list[rangeIndex].UpperBound + 1; } else { return targetIndex; } } public int GetNextIndex(int index) { int targetIndex = index + 1; int rangeIndex = FindRangeIndex(targetIndex); if (IsCorrectRangeIndex(rangeIndex, targetIndex)) { return targetIndex; } else { rangeIndex++; return rangeIndex < _list.Count ? _list[rangeIndex].LowerBound : -1; } } public int GetPreviousGap(int index) { int targetIndex = index - 1; int rangeIndex = FindRangeIndex(targetIndex); if (IsCorrectRangeIndex(rangeIndex, targetIndex)) { while (rangeIndex > 0 && _list[rangeIndex].LowerBound == _list[rangeIndex - 1].UpperBound + 1) { rangeIndex--; } return _list[rangeIndex].LowerBound - 1; } else { return targetIndex; } } public int GetPreviousIndex(int index) { int targetIndex = index - 1; int rangeIndex = FindRangeIndex(targetIndex); if (IsCorrectRangeIndex(rangeIndex, targetIndex)) { return targetIndex; } else { return rangeIndex >= 0 && rangeIndex < _list.Count ? _list[rangeIndex].UpperBound : -1; } } /// /// Returns the inclusive index count between lowerBound and upperBound of all indexes with the given value /// /// lowerBound criteria /// upperBound criteria /// value to look for /// Number of indexes contained in the table between lowerBound and upperBound (inclusive) public int GetIndexCount(int lowerBound, int upperBound, T value) { Debug.Assert(upperBound >= lowerBound); if (_list.Count == 0) { return 0; } int count = 0; int index = FindRangeIndex(lowerBound); if (IsCorrectRangeIndex(index, lowerBound) && _list[index].ContainsValue(value)) { count += _list[index].UpperBound - lowerBound + 1; } index++; while (index < _list.Count && _list[index].UpperBound <= upperBound) { if (_list[index].ContainsValue(value)) { count += _list[index].Count; } index++; } if (index < _list.Count && IsCorrectRangeIndex(index, upperBound) && _list[index].ContainsValue(value)) { count += upperBound - _list[index].LowerBound; } return count; } /// /// Returns the inclusive index count between lowerBound and upperBound /// /// lowerBound criteria /// upperBound criteria /// Number of indexes contained in the table between lowerBound and upperBound (inclusive) public int GetIndexCount(int lowerBound, int upperBound) { if (upperBound < lowerBound || _list.Count == 0) { return 0; } int count = 0; int index = this.FindRangeIndex(lowerBound); if (IsCorrectRangeIndex(index, lowerBound)) { count += _list[index].UpperBound - lowerBound + 1; } index++; while (index < _list.Count && _list[index].UpperBound <= upperBound) { count += _list[index].Count; index++; } if (index < _list.Count && IsCorrectRangeIndex(index, upperBound)) { count += upperBound - _list[index].LowerBound; } return count; } /// /// Returns the number indexes in this table after a given startingIndex but before /// reaching a gap of indexes of a given size /// /// Index to start at /// Size of index gap /// public int GetIndexCountBeforeGap(int startingIndex, int gapSize) { if (_list.Count == 0) { return 0; } int count = 0; int currentIndex = startingIndex; int rangeIndex = 0; int gap = 0; while (gap <= gapSize && rangeIndex < _list.Count) { gap += _list[rangeIndex].LowerBound - currentIndex; if (gap <= gapSize) { count += _list[rangeIndex].UpperBound - _list[rangeIndex].LowerBound + 1; currentIndex = _list[rangeIndex].UpperBound + 1; rangeIndex++; } } return count; } /// /// Returns an enumerator that goes through the indexes present in the table /// /// an enumerator that enumerates the indexes present in the table public IEnumerable GetIndexes() { Debug.Assert(_list != null); foreach (Range range in _list) { for (int i = range.LowerBound; i <= range.UpperBound; i++) { yield return i; } } } /// /// Returns all the indexes on or after a starting index /// /// start index /// public IEnumerable GetIndexes(int startIndex) { Debug.Assert(_list != null); int rangeIndex = FindRangeIndex(startIndex); if (rangeIndex == -1) { rangeIndex++; } while (rangeIndex < _list.Count) { for (int i = _list[rangeIndex].LowerBound; i <= _list[rangeIndex].UpperBound; i++) { if (i >= startIndex) { yield return i; } } rangeIndex++; } } /// /// Return the index of the Nth element in the table. /// /// n public int GetNthIndex(int n) { Debug.Assert(n >= 0 && n < this.IndexCount); int cumulatedEntries = 0; foreach (Range range in _list) { if (cumulatedEntries + range.Count > n) { return range.LowerBound + n - cumulatedEntries; } else { cumulatedEntries += range.Count; } } return -1; } /// /// Returns the value at a given index or the default value if the index is not in the table /// /// index to search for /// the value at the given index or the default value if index is not in the table public T GetValueAt(int index) { return GetValueAt(index, out bool found); } /// /// Returns the value at a given index or the default value if the index is not in the table /// /// index to search for /// set to true by the method if the index was found; otherwise, false /// the value at the given index or the default value if index is not in the table public T GetValueAt(int index, out bool found) { int rangeIndex = this.FindRangeIndex(index); if (this.IsCorrectRangeIndex(rangeIndex, index)) { found = true; return _list[rangeIndex].Value; } else { found = false; return default(T); } } /// /// Returns an index's index within this table /// /// /// public int IndexOf(int index) { int cumulatedIndexes = 0; foreach (Range range in _list) { if (range.UpperBound >= index) { cumulatedIndexes += index - range.LowerBound; break; } else { cumulatedIndexes += range.Count; } } return cumulatedIndexes; } /// /// Inserts an index at the given location. This does not alter values in the table /// /// index location to insert an index public void InsertIndex(int index) { InsertIndexes(index, 1); } /// /// Inserts an index into the table with the given value /// /// index to insert /// value for the index public void InsertIndexAndValue(int index, T value) { InsertIndexesAndValues(index, 1, value); } /// /// Inserts multiple indexes into the table. This does not alter Values in the table /// /// first index to insert /// total number of indexes to insert public void InsertIndexes(int startIndex, int count) { Debug.Assert(count > 0); InsertIndexesPrivate(startIndex, count, this.FindRangeIndex(startIndex)); } /// /// Inserts multiple indexes into the table with the given value /// /// Index to insert first value /// Total number of values to insert (must be greater than 0) /// Value to insert public void InsertIndexesAndValues(int startIndex, int count, T value) { Debug.Assert(count > 0); int lowerRangeIndex = this.FindRangeIndex(startIndex); InsertIndexesPrivate(startIndex, count, lowerRangeIndex); if ((lowerRangeIndex >= 0) && (_list[lowerRangeIndex].LowerBound > startIndex)) { // Because of the insert, the original range no longer contains the startIndex lowerRangeIndex--; } AddValuesPrivate(startIndex, count, value, lowerRangeIndex); } /// /// Removes an index from the table. This does not alter Values in the table /// /// index to remove public void RemoveIndex(int index) { RemoveIndexes(index, 1); } /// /// Removes a value and its index from the table /// /// index to remove public void RemoveIndexAndValue(int index) { RemoveIndexesAndValues(index, 1); } /// /// Removes multiple indexes from the table. This does not alter Values in the table /// /// first index to remove /// total number of indexes to remove public void RemoveIndexes(int startIndex, int count) { int lowerRangeIndex = this.FindRangeIndex(startIndex); if (lowerRangeIndex < 0) { lowerRangeIndex = 0; } int i = lowerRangeIndex; while (i < _list.Count) { Range range = _list[i]; if (range.UpperBound >= startIndex) { if (range.LowerBound >= startIndex + count) { // Both bounds will remain after the removal range.LowerBound -= count; range.UpperBound -= count; } else { int currentIndex = i; if (range.LowerBound <= startIndex) { // Range gets split up if (range.UpperBound >= startIndex + count) { i++; _list.Insert(i, new Range(startIndex, range.UpperBound - count, range.Value)); } range.UpperBound = startIndex - 1; } else { range.LowerBound = startIndex; range.UpperBound -= count; } if (RemoveRangeIfInvalid(range, currentIndex)) { i--; } } } i++; } if (!this.Merge(lowerRangeIndex)) { this.Merge(lowerRangeIndex + 1); } } /// /// Removes multiple values and their indexes from the table /// /// first index to remove /// total number of indexes to remove public void RemoveIndexesAndValues(int startIndex, int count) { RemoveValues(startIndex, count); RemoveIndexes(startIndex, count); } /// /// Removes a value from the table at the given index. This does not alter other indexes in the table. /// /// index where value should be removed public void RemoveValue(int index) { RemoveValues(index, 1); } /// /// Removes multiple values from the table. This does not alter other indexes in the table. /// /// first index where values should be removed /// total number of values to remove public void RemoveValues(int startIndex, int count) { Debug.Assert(count > 0); int lowerRangeIndex = this.FindRangeIndex(startIndex); if (lowerRangeIndex < 0) { lowerRangeIndex = 0; } while ((lowerRangeIndex < _list.Count) && (_list[lowerRangeIndex].UpperBound < startIndex)) { lowerRangeIndex++; } if (lowerRangeIndex >= _list.Count || _list[lowerRangeIndex].LowerBound > startIndex + count - 1) { // If all the values are above our below our values, we have nothing to remove return; } if (_list[lowerRangeIndex].LowerBound < startIndex) { // Need to split this up _list.Insert(lowerRangeIndex, new Range(_list[lowerRangeIndex].LowerBound, startIndex - 1, _list[lowerRangeIndex].Value)); lowerRangeIndex++; } _list[lowerRangeIndex].LowerBound = startIndex + count; if (!RemoveRangeIfInvalid(_list[lowerRangeIndex], lowerRangeIndex)) { lowerRangeIndex++; } while ((lowerRangeIndex < _list.Count) && (_list[lowerRangeIndex].UpperBound < startIndex + count)) { _list.RemoveAt(lowerRangeIndex); } if ((lowerRangeIndex < _list.Count) && (_list[lowerRangeIndex].UpperBound >= startIndex + count) && (_list[lowerRangeIndex].LowerBound < startIndex + count)) { // Chop off the start of the remaining Range if it contains values that we're removing _list[lowerRangeIndex].LowerBound = startIndex + count; RemoveRangeIfInvalid(_list[lowerRangeIndex], lowerRangeIndex); } } private void AddValuesPrivate(int startIndex, int count, T value, int? startRangeIndex) { Debug.Assert(count > 0); int endIndex = startIndex + count - 1; Range newRange = new Range(startIndex, endIndex, value); if (_list.Count == 0) { _list.Add(newRange); } else { int lowerRangeIndex = startRangeIndex ?? FindRangeIndex(startIndex); Range lowerRange = (lowerRangeIndex < 0) ? null : _list[lowerRangeIndex]; if (lowerRange == null) { if (lowerRangeIndex < 0) { lowerRangeIndex = 0; } _list.Insert(lowerRangeIndex, newRange); } else { if (!lowerRange.Value.Equals(value) && (lowerRange.UpperBound >= startIndex)) { // Split up the range if (lowerRange.UpperBound > endIndex) { _list.Insert(lowerRangeIndex + 1, new Range(endIndex + 1, lowerRange.UpperBound, lowerRange.Value)); } lowerRange.UpperBound = startIndex - 1; if (!RemoveRangeIfInvalid(lowerRange, lowerRangeIndex)) { lowerRangeIndex++; } _list.Insert(lowerRangeIndex, newRange); } else { _list.Insert(lowerRangeIndex + 1, newRange); if (!Merge(lowerRangeIndex)) { lowerRangeIndex++; } } } // At this point the newRange has been inserted in the correct place, now we need to remove // any subsequent ranges that no longer make sense and possibly update the one at newRange.UpperBound int upperRangeIndex = lowerRangeIndex + 1; while ((upperRangeIndex < _list.Count) && (_list[upperRangeIndex].UpperBound < endIndex)) { _list.RemoveAt(upperRangeIndex); } if (upperRangeIndex < _list.Count) { Range upperRange = _list[upperRangeIndex]; if (upperRange.LowerBound <= endIndex) { // Update the range upperRange.LowerBound = endIndex + 1; RemoveRangeIfInvalid(upperRange, upperRangeIndex); } Merge(lowerRangeIndex); } } } // Returns the index of the range that contains the input or the range before if the input is not found private int FindRangeIndex(int index) { if (_list.Count == 0) { return -1; } // Do a binary search for the index int front = 0; int end = _list.Count - 1; Range range = null; while (end > front) { int median = (front + end) / 2; range = _list[median]; if (range.UpperBound < index) { front = median + 1; } else if (range.LowerBound > index) { end = median - 1; } else { // we found it return median; } } if (front == end) { range = _list[front]; if (range.ContainsIndex(index) || (range.UpperBound < index)) { // we found it or the index isn't there and we're one range before return front; } else { // not found and we're one range after return front - 1; } } else { // end is one index before front in this case so it's the range before return end; } } private bool Merge(int lowerRangeIndex) { int upperRangeIndex = lowerRangeIndex + 1; if ((lowerRangeIndex >= 0) && (upperRangeIndex < _list.Count)) { Range lowerRange = _list[lowerRangeIndex]; Range upperRange = _list[upperRangeIndex]; if ((lowerRange.UpperBound + 1 >= upperRange.LowerBound) && (lowerRange.Value.Equals(upperRange.Value))) { lowerRange.UpperBound = Math.Max(lowerRange.UpperBound, upperRange.UpperBound); _list.RemoveAt(upperRangeIndex); return true; } } return false; } private void InsertIndexesPrivate(int startIndex, int count, int lowerRangeIndex) { Debug.Assert(count > 0); // Same as AddRange after we fix the indicies affected by the insertion int startRangeIndex = (lowerRangeIndex >= 0) ? lowerRangeIndex : 0; for (int i = startRangeIndex; i < _list.Count; i++) { Range range = _list[i]; if (range.LowerBound >= startIndex) { range.LowerBound += count; } else { if (range.UpperBound >= startIndex) { // Split up this range i++; _list.Insert(i, new Range(startIndex, range.UpperBound + count, range.Value)); range.UpperBound = startIndex - 1; continue; } } if (range.UpperBound >= startIndex) { range.UpperBound += count; } } } private bool IsCorrectRangeIndex(int rangeIndex, int index) { return (-1 != rangeIndex) && (_list[rangeIndex].ContainsIndex(index)); } private bool RemoveRangeIfInvalid(Range range, int rangeIndex) { if (range.UpperBound < range.LowerBound) { _list.RemoveAt(rangeIndex); return true; } return false; } public IEnumerator> GetEnumerator() { return _list.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } #if DEBUG public void PrintIndexes() { Debug.WriteLine(this.IndexCount + " indexes"); foreach (Range range in _list) { Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} - {1}", range.LowerBound, range.UpperBound)); } } #endif } }