Как выполнить бинарный поиск в IList <T>?
Простой вопрос - задан IList<T>
, как вы выполняете двоичный поиск, не записывая метод самостоятельно и не копируя данные в тип с встроенной поддержкой двоичного поиска. Мое текущее состояние следующее.
-
List<T>.BinarySearch()
не является членом IList<T>
- Нет эквивалента метода
ArrayList.Adapter()
для List<T>
-
IList<T>
не наследуется от IList
, поэтому использование ArrayList.Adapter()
невозможно
Я склонен полагать, что это невозможно с помощью встроенных методов, но я не могу поверить, что такой базовый метод отсутствует в BCL/FCL.
Если это невозможно, кто может дать самую короткую, быструю, умную или самую красивую реализацию бинарного поиска для IList<T>
?
UPDATE
Мы все знаем, что список должен быть отсортирован перед использованием двоичного поиска, поэтому вы можете предположить, что он есть. Но я предполагаю (но не проверял) это та же проблема с сортировкой - как вы сортируете IList<T>
?
Заключение
Кажется, нет встроенного двоичного поиска для IList<T>
. Для поиска и сортировки можно использовать методы First()
и OrderBy()
LINQ, но это будет иметь худший результат. Реализация его самостоятельно (как метод расширения) кажется лучшим, что вы можете сделать.
Ответы
Ответ 1
Я сомневаюсь, что в .NET существует такой метод двоичного поиска общего назначения, за исключением того, что он присутствует в некоторых базовых классах (но, видимо, не в интерфейсах), поэтому здесь моя общая цель.
public static Int32 BinarySearchIndexOf<T>(this IList<T> list, T value, IComparer<T> comparer = null)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
comparer = comparer ?? Comparer<T>.Default;
Int32 lower = 0;
Int32 upper = list.Count - 1;
while (lower <= upper)
{
Int32 middle = lower + (upper - lower) / 2;
Int32 comparisonResult = comparer.Compare(value, list[middle]);
if (comparisonResult == 0)
return middle;
else if (comparisonResult < 0)
upper = middle - 1;
else
lower = middle + 1;
}
return ~lower;
}
Это, конечно, предполагает, что список, о котором идет речь, уже отсортирован по тем же правилам, которые использует компаратор.
Ответ 2
Мне нравится решение с методом расширения. Тем не менее, немного предупреждения в порядке.
Это эффективная реализация Джона Бентли из его книги "Программирование жемчуга", и она умеренно связана с ошибкой с числовым переполнением, которое не было обнаружено в течение 20 лет или около того. (Верхний + нижний) может переполнять Int32, если у вас есть большое количество элементов в IList. Решением к этому является сделать средний расчет немного по-другому, используя вычитание; Средний = Нижний + (Верхний - Нижний)/2;
Bentley также предупредил в Programming Pearls, что, хотя алгоритм бинарного поиска был опубликован в 1946 году, и первая правильная реализация не была опубликована до 1962 года.
http://en.wikipedia.org/wiki/Binary_search#Numerical_difficulties
Ответ 3
Вот моя версия кода Лассе. Я считаю полезным иметь возможность использовать лямбда-выражение для выполнения поиска. При поиске в списке объектов разрешается передавать только тот ключ, который использовался для сортировки. Реализации, использующие IComparer, тривиально получены из этого.
Я также хотел бы вернуться ~ ниже, когда совпадение не найдено. Array.BinarySearch делает это и позволяет вам знать, куда должен быть вставлен искомый элемент, чтобы сохранить порядок.
/// <summary>
/// Performs a binary search on the specified collection.
/// </summary>
/// <typeparam name="TItem">The type of the item.</typeparam>
/// <typeparam name="TSearch">The type of the searched item.</typeparam>
/// <param name="list">The list to be searched.</param>
/// <param name="value">The value to search for.</param>
/// <param name="comparer">The comparer that is used to compare the value
/// with the list items.</param>
/// <returns></returns>
public static int BinarySearch<TItem, TSearch>(this IList<TItem> list,
TSearch value, Func<TSearch, TItem, int> comparer)
{
if (list == null)
{
throw new ArgumentNullException("list");
}
if (comparer == null)
{
throw new ArgumentNullException("comparer");
}
int lower = 0;
int upper = list.Count - 1;
while (lower <= upper)
{
int middle = lower + (upper - lower) / 2;
int comparisonResult = comparer(value, list[middle]);
if (comparisonResult < 0)
{
upper = middle - 1;
}
else if (comparisonResult > 0)
{
lower = middle + 1;
}
else
{
return middle;
}
}
return ~lower;
}
/// <summary>
/// Performs a binary search on the specified collection.
/// </summary>
/// <typeparam name="TItem">The type of the item.</typeparam>
/// <param name="list">The list to be searched.</param>
/// <param name="value">The value to search for.</param>
/// <returns></returns>
public static int BinarySearch<TItem>(this IList<TItem> list, TItem value)
{
return BinarySearch(list, value, Comparer<TItem>.Default);
}
/// <summary>
/// Performs a binary search on the specified collection.
/// </summary>
/// <typeparam name="TItem">The type of the item.</typeparam>
/// <param name="list">The list to be searched.</param>
/// <param name="value">The value to search for.</param>
/// <param name="comparer">The comparer that is used to compare the value
/// with the list items.</param>
/// <returns></returns>
public static int BinarySearch<TItem>(this IList<TItem> list, TItem value,
IComparer<TItem> comparer)
{
return list.BinarySearch(value, comparer.Compare);
}
Ответ 4
Я боролся с получением этого права в течение некоторого времени. В частности, возвращаемые значения для крайних случаев, как указано в MSDN: http://msdn.microsoft.com/en-us/library/w4e7fxsh.aspx
Теперь я скопировал ArraySortHelper.InternalBinarySearch() из .NET 4.0 и сделал свой собственный аромат по разным причинам.
Использование:
var numbers = new List<int>() { ... };
var items = new List<FooInt>() { ... };
int result1 = numbers.BinarySearchIndexOf(5);
int result2 = items.BinarySearchIndexOfBy(foo => foo.bar, 5);
Это должно работать со всеми типами .NET. Я пробовал int, long и double до сих пор.
Реализация:
public static class BinarySearchUtils
{
public static int BinarySearchIndexOf<TItem>(this IList<TItem> list,
TItem targetValue, IComparer<TItem> comparer = null)
{
Func<TItem, TItem, int> compareFunc =
comparer != null ? comparer.Compare :
(Func<TItem, TItem, int>) Comparer<TItem>.Default.Compare;
int index = BinarySearchIndexOfBy(list, compareFunc, targetValue);
return index;
}
public static int BinarySearchIndexOfBy<TItem, TValue>(this IList<TItem> list,
Func<TItem, TValue, int> comparer, TValue value)
{
if (list == null)
throw new ArgumentNullException("list");
if (comparer == null)
throw new ArgumentNullException("comparer");
if (list.Count == 0)
return -1;
// Implementation below copied largely from .NET4
// ArraySortHelper.InternalBinarySearch()
int lo = 0;
int hi = list.Count - 1;
while (lo <= hi)
{
int i = lo + ((hi - lo) >> 1);
int order = comparer(list[i], value);
if (order == 0)
return i;
if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}
return ~lo;
}
}
Юнит-тесты:
[TestFixture]
public class BinarySearchUtilsTest
{
[Test]
public void BinarySearchReturnValueByMsdnSpecification()
{
var numbers = new List<int>() { 1, 3 };
// Following the MSDN documentation for List<T>.BinarySearch:
// http://msdn.microsoft.com/en-us/library/w4e7fxsh.aspx
// The zero-based index of item in the sorted List(Of T), if item is found;
int index = numbers.BinarySearchIndexOf(1);
Assert.AreEqual(0, index);
index = numbers.BinarySearchIndexOf(3);
Assert.AreEqual(1, index);
// otherwise, a negative number that is the bitwise complement of the
// index of the next element that is larger than item
index = numbers.BinarySearchIndexOf(0);
Assert.AreEqual(~0, index);
index = numbers.BinarySearchIndexOf(2);
Assert.AreEqual(~1, index);
// or, if there is no larger element, the bitwise complement of Count.
index = numbers.BinarySearchIndexOf(4);
Assert.AreEqual(~numbers.Count, index);
}
}
Я просто вычеркнул это из своего кода, поэтому, пожалуйста, прокомментируйте, если он не работает "из коробки".
Надеюсь, что это решит проблему с работающей реализацией раз и навсегда, по крайней мере, в соответствии со спецификациями MSDN.
Ответ 5
У вас будет несколько проблем с двоичным поиском IList<T>
. Во-первых, как вы уже упоминали, метод BinarySearch
на List<T>
не является членом интерфейса IList<T>
. Во-вторых, у вас нет способа сортировки списка перед поиском (что вы должны сделать для работы бинарного поиска).
Я думаю, что лучше всего создать новый List<T>
, отсортировать его, а затем выполнить поиск. Это не идеально, но вам не нужно много вариантов, если у вас есть IList<T>
.
Ответ 6
Обратите внимание, что в реализации, предоставленной Антуаном, есть ошибка: при поиске элемента, большего, чем все в списке. Возвращаемое значение должно быть ~ ниже не ~ среднее. Декомпилируйте метод ArraySortHelper.InternalBinarySearch(mscorlib), чтобы увидеть реализацию фреймворка.
Ответ 7
Если вам нужна готовая реализация для двоичного поиска на IList<T>
s, Wintellect Power Collections имеет один (в Algorithms.cs
):
/// <summary>
/// Searches a sorted list for an item via binary search. The list must be sorted
/// by the natural ordering of the type (it implementation of IComparable<T>).
/// </summary>
/// <param name="list">The sorted list to search.</param>
/// <param name="item">The item to search for.</param>
/// <param name="index">Returns the first index at which the item can be found. If the return
/// value is zero, indicating that <paramref name="item"/> was not present in the list, then this
/// returns the index at which <paramref name="item"/> could be inserted to maintain the sorted
/// order of the list.</param>
/// <returns>The number of items equal to <paramref name="item"/> that appear in the list.</returns>
public static int BinarySearch<T>(IList<T> list, T item, out int index)
where T: IComparable<T>
{
// ...
}
Ответ 8
Вы можете использовать List<T>.BinarySearch(T item)
. Если вы хотите использовать пользовательский сопоставитель, используйте List<T>.BinarySearch(T item, IComparer<T> comparer)
. Подробнее см. В этой MSDN ссылка.
Ответ 9
Если вы можете использовать .NET 3.5, вы можете использовать сборку в методах расширения Linq:
using System.Linq;
IList<string> ls = ...;
var orderedList = ls.OrderBy(x => x).ToList();
orderedList.BinarySearch(...);
Тем не менее, это действительно немного другой способ перехода к решению Andrew Hare, и это действительно полезно, если вы много раз просматриваете тот же упорядоченный список.
Ответ 10
Имейте в виду, что бинарный поиск может быть весьма неэффективным для некоторых реализаций списка. Например, для связанного списка это O (n), если вы его правильно реализуете и O (n log n), если вы реализуете его наивно.
Ответ 11
Обратите внимание, что хотя List и IList не имеют метода BinarySearch, SortedList делает.