问题
In WPF, I want to keep my ObservableCollection sorted. Items are added randomly and not in batch. I do not want to sort the entire IList each time. I want to insert at the proper place on each insert.
Is there a way to generically insert quickly (log(n)) an item into any IList of T.
Does anyone wrote the code for it?
Note: Yes, the list should already be sorted before calling this method.
Note2: Sometimes you have to use an IList and can't replace it with a SortedList and do not want to maintain 2 collections: For example: in WPF using an ObservableCollection.
Update, yes there is a way to do it. By using dichotomy. I ask the question to save me time. I'm writing it and will show it as soon as it is debugged.
回答1:
Here are some useful extension methods for sorted IList<T> collections (SortedBinarySearch, SortedIndexOf, SortedInsert and SortedRemove). The binary search algorithm is stolen from the source code of the ArraySortHelper<T>.InternalBinarySearch method.
/// <summary>Searches within the sorted <see cref="IList{T}"/> for the
/// specified item and returns the zero-based index of the item if found;
/// otherwise, a negative number that is the bitwise complement of the index of
/// the next item that is larger than item or, if there is no larger item,
/// the bitwise complement of <see cref="IList{T}.Count"/>.</summary>
public static int SortedBinarySearch<T>(this IList<T> list, T item,
IComparer<T> comparer = null)
{
if (list == null) throw new ArgumentNullException(nameof(list));
comparer = comparer ?? Comparer<T>.Default;
int lo = 0;
int hi = list.Count - 1;
while (lo <= hi)
{
int i = lo + ((hi - lo) >> 1);
int order = comparer.Compare(list[i], item);
if (order == 0) return i;
if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}
return ~lo;
}
/// <summary>Searches for the specified item within the sorted
/// <see cref="IList{T}"/> and returns the zero-based index of the item
/// if found; otherwise, -1.</summary>
public static int SortedIndexOf<T>(this IList<T> list, T item,
IComparer<T> comparer = null)
{
int index = SortedBinarySearch(list, item, comparer);
if (index < 0) return -1;
return index;
}
/// <summary>Inserts an item into the sorted <see cref="IList{T}"/>.</summary>
public static int SortedInsert<T>(this IList<T> list, T item,
IComparer<T> comparer = null)
{
int index = SortedBinarySearch(list, item, comparer);
if (index < 0) index = ~index;
list.Insert(index, item);
return index;
}
/// <summary>Removes an item from the sorted <see cref="IList{T}"/> and returns
/// true if the item is successfully removed; otherwise, false.</summary>
public static bool SortedRemove<T>(this IList<T> list, T item,
IComparer<T> comparer = null)
{
int index = SortedBinarySearch(list, item, comparer);
if (index < 0) return false;
list.RemoveAt(index);
return true;
}
回答2:
OK... I did it before someone would close my question (it almost happen with 2 votes to close)...
Although there was 2 persons who vote to close, I think it was a good question and I also think the code worth being kept.
It took me about 5 hours to do it and debug it. It seems to work fine. To save 5 hours... that's why I asked the question. To save time.
/// <summary>
/// Insert item in list in log(n). The list should already be sorted.
/// Item will be inserted and there will be duplicate if comparer return 0.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="item"></param>
/// <param name="comparer"></param>
/// <returns></returns>
public static int InsertInSortedList<T>(this IList<T> list, T item, Func<T, T, int> comparer = null)
{
if (comparer == null)
{
comparer = Comparer<T>.Default.Compare;
}
int first = 0;
int last = list.Count;
int middle = 0;
int compareResult = 0;
if (last > 0)
{
while (true)
{
middle = first + ((last - first) / 2);
compareResult = comparer(item, list[middle]);
if (compareResult > 0)
{
first = middle + 1;
if (first >= last)
{
middle++;
break;
}
continue;
}
if (compareResult < 0)
{
last = middle;
if (first == last)
{
break;
}
continue;
}
break;
}
}
if (middle == list.Count)
{
list.Add(item);
}
else
{
list.Insert(middle, item);
}
return middle;
}
来源:https://stackoverflow.com/questions/61893643/is-there-a-way-to-generically-quickly-insert-logn-an-item-into-any-ilistt