如何在IList <t>上执行二进制搜索?</t>

时间:2009-06-08 21:09:09

标签: .net generics list interface binary-search

简单的问题 - 给定IList<T>如何在不自己编写方法的情况下执行二进制搜索,而不将数据复制到具有内置二进制搜索支持的类型。我目前的状况如下。

  • List<T>.BinarySearch()不是IList<T>
  • 的成员
  • List<T>
  • 没有等效的ArrayList.Adapter()方法
  • IList<T>不会继承IList,因此无法使用ArrayList.Adapter()

我倾向于认为使用内置方法是不可能的,但我无法相信BCL / FCL中缺少这样的基本方法。

如果不可能,谁可以为IList<T>提供最短,最快,最智能或最美丽的二元搜索实现?

更新

我们都知道在使用二进制搜索之前必须对列表进行排序,因此您可以假设它是。但我认为(但没有验证)排序是同样的问题 - 你如何排序IList<T>

结论

似乎没有IList<T>的内置二进制搜索。可以使用First()OrderBy() LINQ方法进行搜索和排序,但它可能会受到性能影响。自己实现(作为扩展方法)似乎是你能做到的最好的。

11 个答案:

答案 0 :(得分:31)

我怀疑在.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;
}

这当然假设有问题的列表已经按照比较器将使用的相同规则进行了排序。

答案 1 :(得分:31)

我喜欢使用扩展方法的解决方案。但是,有一点警告是有道理的。

这实际上是Jon Bentley在他的“珍珠编程”(Programming Pearls)一书中的实施,它从一个未被发现的数字溢出错误中谦虚地受到了20年左右的影响。如果IList中有大量项目,则(上部+下部)可以溢出Int32。对此的解决方案是使用减法来稍微改变中间计算;中=低+(上 - 下)/ 2;

Bentley还在Programming Pearls中警告说,虽然二进制搜索算法于1946年发布,但第一个正确的实现直到1962年才发布。

http://en.wikipedia.org/wiki/Binary_search#Numerical_difficulties

答案 2 :(得分:28)

这是我的Lasse代码版本。我发现能够使用lambda表达式来执行搜索很有用。在对象列表中搜索时,它只允许传递用于排序的键。使用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);
}

答案 3 :(得分:4)

IList<T>进行二进制搜索会遇到一些问题,首先,正如您所提到的,BinarySearch上的List<T>方法不是IList<T>的成员1}}接口。其次,你无法在搜索之前对列表进行排序(你必须为二进制搜索工作)。

我认为您最好的选择是创建一个新的List<T>,对其进行排序,然后进行搜索。它并不完美,但如果您有IList<T>,则不需要很多选项。

答案 4 :(得分:4)

我一直在努力争取这个问题。特别是MSDN中指定的边缘个案的返回值:http://msdn.microsoft.com/en-us/library/w4e7fxsh.aspx

我现在已经从.NET 4.0复制了ArraySortHelper.InternalBinarySearch()并出于各种原因制作了自己的风格。

<强>用法:

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 :(得分:3)

请注意,以下Antoine提供的实施中存在一个错误:在列表中搜索大于任何项目时。返回值应该是〜更低而不是〜中间。反编译方法ArraySortHelper.InternalBinarySearch(mscorlib)来查看框架实现。

答案 6 :(得分:2)

如果您需要在IList<T>Wintellect's Power Collections has oneAlgorithms.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's implementation of IComparable&lt;T&gt;).
/// </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>
{
    // ...
}

答案 7 :(得分:2)

您可以使用List<T>.BinarySearch(T item)。如果要使用自定义比较器,请使用List<T>.BinarySearch(T item, IComparer<T> comparer)。有关详细信息,请查看此MSDN link

答案 8 :(得分:-1)

如果您可以使用.NET 3.5,则可以使用Linq扩展方法中的构建:

using System.Linq;

IList<string> ls = ...;
var orderedList = ls.OrderBy(x => x).ToList();
orderedList.BinarySearch(...);

然而,对于Andrew Hare的解决方案而言,这实际上只是一种略微不同的方式,并且只有在您从同一个有序列表中多次搜索时才真正有用。

答案 9 :(得分:-1)

请记住,对于某些列表实现,二进制搜索可能效率很低。例如,对于链接列表,如果正确实现它,则为O(n);如果您实现它,则为O(n log n)。

答案 10 :(得分:-2)

请注意,虽然List和IList没有BinarySearch方法,但SortedList却没有。