代码之家  ›  专栏  ›  技术社区  ›  Daniel Brückner Pradip

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

  •  37
  • Daniel Brückner Pradip  · 技术社区  · 15 年前

    简单问题-给出一个 IList<T> 如何执行二进制搜索,而无需自己编写方法,也无需将数据复制到具有内置二进制搜索支持的类型。我目前的状态如下。

    • List<T>.BinarySearch() IList<T>
    • 没有与之相当的 ArrayList.Adapter() 方法 List<T>
    • IList<T> IList ArrayList.Adapter() 不可能

    我倾向于相信内置方法不可能做到这一点,但我不能相信BCL/FCL中缺少这样一种基本方法。

    如果这是不可能的,谁能给最短的,最快的,最聪明的,或最美好的二进制搜索的实现 IList<T> ?

    使现代化

    ?

    结论

    似乎没有内置的二进制搜索 IList<T> First() OrderBy() LINQ方法用于搜索和排序,但它很可能会影响性能。自己实现它(作为一种扩展方法)似乎是最好的选择。

    11 回复  |  直到 4 年前
        1
  •  39
  •   Dai    6 年前

    我怀疑在.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
  •  35
  •   Theodor Zoulias    5 年前

    这是我的Lasse代码版本。我发现能够使用lambda表达式来执行搜索非常有用。在对象列表中搜索时,它只允许传递用于排序的键。使用IComparer的实现基本上就是从这个实现派生出来的。

    我也喜欢在找不到匹配项时返回~lower。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
  •  34
  •   devgeezer    15 年前

    这实际上是Jon Bentley在其著作《编程珍珠》(Programming Pearls)中的实现,它受到了一个20年左右未被发现的数字溢出错误的影响。如果IList中有大量项,则(上+下)会溢出Int32。解决这个问题的一个办法是,用减法来做中间的计算,方法稍有不同;中间=下部+(上部-下部)/2;

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

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

        4
  •  5
  •   Theodor Zoulias    5 年前

    一段时间以来,我一直在努力把这件事做好。特别是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
  •  4
  •   Andrew Hare    15 年前

    在搜索二进制文件时会遇到一些问题 IList<T> ,首先,正如你提到的 BinarySearch 关于 List<T> 界面其次,您无法在搜索之前对列表进行排序(这是二进制搜索必须执行的操作)。

    我认为你最好的选择是创造一个新的 列表<T> ,对其进行排序,然后进行搜索。它并不完美,但如果你有一个 IList<T> .

        6
  •  3
  •   mdi    14 年前

    请注意,下面Antoine提供的实现中有一个bug:搜索大于列表中任何项的项时。返回值应为~lower而不是~middle。反编译方法ArraySortHelper.InternalBinarySearch(mscorlib)以查看框架实现。

        7
  •  2
  •   Christian    14 年前

    如果您需要一个现成的二进制搜索实现 IList<T> s Wintellect's Power Collections has one 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'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>
    {
        // ...
    }
    
        8
  •  2
  •   Pratik Bhattacharya    7 年前

    你可以用 List<T>.BinarySearch(T item) . 如果要使用自定义比较器,请使用 List<T>.BinarySearch(T item, IComparer<T> comparer) . 看看这个MSDN link 更多细节。

        9
  •  -1
  •   Anonymous    15 年前

        10
  •  -1
  •   Nathan    10 年前

    如果可以使用.NET 3.5,则可以使用内置Linq扩展方法:

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

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

        11
  •  -3
  •   Joel Coehoorn    15 年前