/// <summary>Search for the smallest element that is larger than a reference element</summary> /// <param name="value">Reference element</param> /// <param name="orEqual">If true, return the position of the value itself if it is found. If false, return the position of the closest value that is smaller.</param> /// <param name="offset">Receive the offset within the level of the next element, or 0 if not found</param> /// <param name="result">Receive the value of the next element, or default(T) if not found</param> /// <returns>Level of the next element, or -1 if <param name="result"/> was already the largest</returns> public static int FindNext <T>(T[][] levels, int count, T value, bool orEqual, IComparer <T> comparer, out int offset, out T result) { int level = NOT_FOUND; T min = default(T); int minOffset = 0; // scan each segment for a value that would be larger, keep track of the smallest found for (int i = 0; i < levels.Length; i++) { if (ColaStore.IsFree(i, count)) { continue; } var segment = levels[i]; int pos = ColaStore.BinarySearch <T>(segment, 0, segment.Length, value, comparer); if (pos >= 0) { // we found an exact match in this segment if (orEqual) { offset = pos; result = segment[pos]; return(i); } // the next item in this segment should be larger ++pos; } else { // we found where it would be stored in this segment pos = ~pos; } if (pos < segment.Length) { if (level == NOT_FOUND || comparer.Compare(segment[pos], min) < 0) { // we found a better candidate min = segment[pos]; level = i; minOffset = pos; } } } offset = minOffset; result = min; return(level); }
private void Debug_Dump(string label = null) { Trace.WriteLine("* Cursor State: " + label); for (int i = m_min; i < m_cursors.Length; i++) { if (ColaStore.IsFree(i, m_count)) { Trace.WriteLine(" - L" + i + ": unallocated"); continue; } int p = m_cursors[i]; Trace.WriteLine(" - L" + i + ": " + p + " [" + (1 << i) + "] = " + (p < 0 ? "<BEFORE>" : (p >= (1 << i)) ? "<AFTER>" : ("" + m_levels[i][p]))); } Trace.WriteLine(" > Current at " + m_currentLevel + " : " + m_current); }
/// <summary>Search for the largest element that is smaller than a reference element</summary> /// <param name="value">Reference element</param> /// <param name="orEqual">If true, return the position of the value itself if it is found. If false, return the position of the closest value that is smaller.</param> /// <param name="offset">Receive the offset within the level of the previous element, or 0 if not found</param> /// <param name="result">Receive the value of the previous element, or default(T) if not found</param> /// <returns>Level of the previous element, or -1 if <param name="result"/> was already the smallest</returns> public static int FindPrevious <T>(T[][] levels, int count, T value, bool orEqual, IComparer <T> comparer, out int offset, out T result) { int level = NOT_FOUND; T max = default(T); int maxOffset = 0; // scan each segment for a value that would be smaller, keep track of the smallest found for (int i = 0; i < levels.Length; i++) { if (ColaStore.IsFree(i, count)) { continue; } var segment = levels[i]; int pos = ColaStore.BinarySearch <T>(segment, 0, segment.Length, value, comparer); // the previous item in this segment should be smaller if (pos < 0) { // it is not pos = ~pos; } else if (orEqual) { // we found an exact match in this segment offset = pos; result = segment[pos]; return(i); } --pos; if (pos >= 0) { if (level == NOT_FOUND || comparer.Compare(segment[pos], max) > 0) { // we found a better candidate max = segment[pos]; level = i; maxOffset = pos; } } } offset = maxOffset; result = max; return(level); }
/// <summary>Iterate over all the values in the set, without any order guarantee</summary> internal static IEnumerable <T> IterateUnordered <T>(int count, T[][] inputs) { Contract.Requires(count >= 0 && inputs != null && count < (1 << inputs.Length)); for (int i = 0; i < inputs.Length; i++) { if (ColaStore.IsFree(i, count)) { continue; } var segment = inputs[i]; Contract.Assert(segment != null && segment.Length == 1 << i); for (int j = 0; j < segment.Length; j++) { yield return(segment[j]); } } }
/// <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; } } } } }
public static IEnumerable <T> FindBetween <T>(T[][] levels, int count, T begin, bool beginOrEqual, T end, bool endOrEqual, int limit, IComparer <T> comparer) { if (limit > 0) { for (int i = 0; i < levels.Length; i++) { if (ColaStore.IsFree(i, count)) { continue; } var segment = levels[i]; int to = ColaStore.BinarySearch <T>(segment, 0, segment.Length, end, comparer); if (to >= 0) { if (!endOrEqual) { to--; } } else { to = ~to; } if (to < 0 || to >= segment.Length) { continue; } int from = ColaStore.BinarySearch <T>(segment, 0, segment.Length, begin, comparer); if (from >= 0) { if (!beginOrEqual) { ++from; } } else { from = ~from; } if (from >= segment.Length) { continue; } if (from > to) { continue; } for (int j = from; j <= to && limit > 0; j++) { yield return(segment[j]); --limit; } if (limit <= 0) { break; } } } }
/// <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(); }