/// <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>Allocates a new store</summary> /// <param name="capacity">Initial capacity, or 0 for the default capacity</param> /// <param name="comparer">Comparer used to order the elements</param> public ColaStore(int capacity, IComparer <T> comparer) { if (capacity < 0) { throw new ArgumentOutOfRangeException("capacity", "Capacity cannot be less than zero."); } if (comparer == null) { throw new ArgumentNullException("comparer"); } Contract.EndContractBlock(); int levels; if (capacity == 0) { // use the default capacity levels = INITIAL_LEVELS; } else { // L levels will only store (2^L - 1) // note: there is no real penalty if the capacity was not correctly estimated, appart from the fact that all levels will not be contiguous in memory // 1 => 1 // 2..3 => 2 // 4..7 => 3 levels = ColaStore.HighestBit(capacity) + 1; } // allocating more than 31 levels would mean having an array of length 2^31, which is not possible if (levels >= 31) { throw new ArgumentOutOfRangeException("capacity", "Cannot allocate more than 30 levels"); } // pre-allocate the segments and spares at the same time, so that they are always at the same memory location var segments = new T[levels][]; var spares = new T[MAX_SPARE_ORDER][]; for (int i = 0; i < segments.Length; i++) { segments[i] = new T[1 << i]; if (i < spares.Length) { spares[i] = new T[1 << i]; } } m_levels = segments; m_root = segments[0]; m_spares = spares; #if ENFORCE_INVARIANTS m_spareUsed = new bool[spares.Length]; #endif m_comparer = comparer; }
/// <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>Pre-allocate memory in the store so that it can store a specified amount of items</summary> /// <param name="minimumRequired">Number of items that will be inserted in the store</param> public void EnsureCapacity(int minimumRequired) { int level = ColaStore.HighestBit(minimumRequired); if ((1 << level) < minimumRequired) { ++level; } if (level >= m_levels.Length) { Grow(level); } }
/// <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>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(); }