/// <summary>Find the largest element in the store</summary> /// <returns>Largest element found, or default(T) if the store is empty</returns> public T Max() { switch (m_count) { case 0: return(default(T)); case 1: return(m_root[0]); case 2: return(m_levels[1][1]); default: { int level = ColaStore.LowestBit(m_count); int end = ColaStore.HighestBit(m_count); T max = m_levels[level][0]; while (level <= end) { if (!IsFree(level) && m_comparer.Compare(max, m_levels[level][0]) < 0) { max = m_levels[level][0]; } ++level; } return(max); } } }
/// <summary>Returns the smallest and largest element in the store</summary> /// <param name="min">Receives the value of the smallest element (or default(T) is the store is Empty)</param> /// <param name="max">Receives the value of the largest element (or default(T) is the store is Empty)</param> /// <remarks>If the store contains only one element, than min and max will be equal</remarks> public void GetBounds(out T min, out T max) { switch (m_count) { case 0: { min = default(T); max = default(T); break; } case 1: { min = m_root[0]; max = min; break; } case 2: { min = m_levels[1][0]; max = m_levels[1][1]; break; } default: { int level = ColaStore.LowestBit(m_count); int end = ColaStore.HighestBit(m_count); var segment = m_levels[level]; min = segment[0]; max = segment[segment.Length - 1]; while (level <= end) { if (IsFree(level)) { continue; } segment = m_levels[level]; if (m_comparer.Compare(min, segment[0]) > 0) { min = segment[0]; } if (m_comparer.Compare(max, segment[segment.Length - 1]) < 0) { min = segment[segment.Length - 1]; } ++level; } break; } } }
/// <summary>Iterate over all the values in the set, using their natural order</summary> internal static IEnumerable <T> IterateOrdered <T>(int count, T[][] inputs, IComparer <T> comparer, bool reverse) { Contract.Requires(count >= 0 && inputs != null && comparer != null && count < (1 << inputs.Length)); // NOT TESTED !!!!! // NOT TESTED !!!!! // NOT TESTED !!!!! Contract.Requires(count >= 0 && inputs != null && comparer != null); // We will use a list of N cursors, set to the start of their respective levels. // A each turn, look for the smallest key referenced by the cursors, return that one, and advance its cursor. // Once a cursor is past the end of its level, it is set to -1 and is ignored for the rest of the operation if (count > 0) { // setup the cursors, with the empty levels already marked as completed var cursors = new int[inputs.Length]; for (int i = 0; i < cursors.Length; i++) { if (ColaStore.IsFree(i, count)) { cursors[i] = NOT_FOUND; } } // pre compute the first/last active level int min = ColaStore.LowestBit(count); int max = ColaStore.HighestBit(count); while (count-- > 0) { T item; int pos; if (reverse) { pos = IterateFindPrevious(inputs, cursors, min, max, comparer, out item); } else { pos = IterateFindNext(inputs, cursors, min, max, comparer, out item); } if (pos == NOT_FOUND) { // we unexpectedly ran out of stuff before the end ? //TODO: should we fail or stop here ? throw new InvalidOperationException("Not enough data in the source arrays to fill the output array"); } yield return(item); // update the bounds if needed if (pos == max) { if (cursors[max] == NOT_FOUND) { --max; } } else if (pos == min) { if (cursors[min] == NOT_FOUND) { ++min; } } } } }
/// <summary>Remove the value at the specified location</summary> /// <param name="level">Index of the level (0-based)</param> /// <param name="offset">Offset in the level (0-based)</param> /// <returns>Value that was removed</returns> public T RemoveAt(int level, int offset) { Contract.Assert(level >= 0 && offset >= 0 && offset < 1 << level); //TODO: check if level is allocated ? var segment = m_levels[level]; Contract.Assert(segment != null && segment.Length == 1 << level); T removed = segment[offset]; if (level == 0) { // removing the last inserted value segment[0] = default(T); } else if (level == 1) { // split the first level in two if (IsFree(0)) { // move up to root // ex: remove 'b' at (1,1) and move the 'a' back to the root // 0 [_] => [a] // 1 [a,b] => [_,_] m_root[0] = segment[1 - offset]; segment[0] = default(T); segment[1] = default(T); } else { // merge the root in missing spot // ex: remove 'b' at (1,1) and move the 'c' down a level // N = 3 N = 2 // 0 [c] => 0 [_] // 1 [a,b] => 1 [a,c] ColaStore.MergeSimple <T>(segment, m_root[0], segment[1 - offset], m_comparer); m_root[0] = default(T); } } else if ((m_count & 1) == 1) { // Remove an item from an odd-numbered set // Since the new count will be even, we only need to merge the root in place with the level that is missing a spot // ex: replace the 'b' at (2,1) with the 'e' in the root // N = 5 N = 4 // 0 [e] => 0 [_] // 1 [_,_] 1 [_,_] // 2 [a,b,c,d] => 2 [a,c,d,e] ColaStore.MergeInPlace <T>(segment, offset, m_root[0], m_comparer); m_root[0] = default(T); } else { // we are missing a spot in out modified segment, that need to fill // > we will take the first non empty segment, and break it in pieces // > its last item will be used to fill the empty spot // > the rest of its items will be spread to all the previous empty segments // find the first non empty segment that can be broken int firstNonEmptyLevel = ColaStore.LowestBit(m_count); if (firstNonEmptyLevel == level) { // we are the first level, this is easy ! // move the empty spot at the start if (offset > 0) { Array.Copy(segment, 0, segment, 1, offset); } // and spread the rest to all the previous levels ColaStore.SpreadLevel(level, m_levels); //TODO: modify SpreadLevel(..) to take the offset of the value to skip ? } else { // break that level, and merge its last item with the level that is missing one spot // break down this level T tmp = ColaStore.SpreadLevel(firstNonEmptyLevel, m_levels); // merge its last item with the empty spot in the modified level ColaStore.MergeInPlace(m_levels[level], offset, tmp, m_comparer); } } --m_count; if (m_levels.Length > MAX_SPARE_ORDER) { // maybe release the last level if it is empty ShrinkIfRequired(); } CheckInvariants(); return(removed); }
/// <summary>Insert one or more new elements in the set.</summary> /// <param name="values">Array of elements to insert. Warning: if a value already exist, the store will be corrupted !</param> /// <param name="ordered">If true, the entries in <paramref name="values"/> are guaranteed to already be sorted (using the store default comparer).</param> /// <remarks>The best performances are achieved when inserting a number of items that is a power of 2. The worst performances are when doubling the size of a store that is full. /// Warning: if <paramref name="ordered"/> is true but <paramref name="values"/> is not sorted, or is sorted using a different comparer, then the store will become corrupted ! /// </remarks> public void InsertItems(List <T> values, bool ordered = false) { if (values == null) { throw new ArgumentNullException("values"); } int count = values.Count; T[] segment, spare; if (count < 2) { if (count == 1) { Insert(values[0]); } return; } if (count == 2) { if (IsFree(1)) { segment = m_levels[1]; if (ordered) { segment[0] = values[0]; segment[1] = values[1]; } else { ColaStore.MergeSimple <T>(segment, values[0], values[1], m_comparer); } } else { spare = GetSpare(1); spare[0] = values[0]; spare[1] = values[1]; segment = m_levels[1]; MergeCascade(2, segment, spare); segment[0] = default(T); segment[1] = default(T); PutSpare(1, spare); } } else { // Inserting a size that is a power of 2 is very simple: // * either the corresponding level is empty, in that case we just copy the items and do a quicksort // * or it is full, then we just need to do a cascade merge // For non-power of 2s, we can split decompose them into a suite of power of 2s and insert them one by one int min = ColaStore.LowestBit(count); int max = ColaStore.HighestBit(count); if (max >= m_levels.Length) { // we need to allocate new levels Grow(max); } int p = 0; for (int i = min; i <= max; i++) { if (ColaStore.IsFree(i, count)) { continue; } segment = m_levels[i]; if (IsFree(i)) { // the target level is free, we can copy and sort in place values.CopyTo(p, segment, 0, segment.Length); if (!ordered) { Array.Sort(segment, 0, segment.Length, m_comparer); } p += segment.Length; m_count += segment.Length; } else { // the target level is used, we will have to do a cascade merge, using a spare spare = GetSpare(i); values.CopyTo(p, spare, 0, spare.Length); if (!ordered) { Array.Sort(spare, 0, spare.Length, m_comparer); } p += segment.Length; MergeCascade(i + 1, segment, spare); Array.Clear(segment, 0, segment.Length); PutSpare(i, spare); m_count += segment.Length; } } Contract.Assert(p == count); } CheckInvariants(); }