44

簡単な質問-IList<T>メソッドを自分で記述せずに、また組み込みのバイナリ検索サポートを備えたタイプにデータをコピーせずに、どのようにバイナリ検索を実行するかを考えます。私の現在の状況は次のとおりです。

  • List<T>.BinarySearch()のメンバーではありませんIList<T>
  • に相当するArrayList.Adapter()方法はありませんList<T>
  • IList<T>から継承しないためIList、使用ArrayList.Adapter()できません

ビルトインメソッドでは不可能だと思う傾向がありますが、そのような基本的なメソッドがBCL/FCLに欠けているとは信じられません。

それが不可能な場合、誰が最短、最速、最も賢い、または最も美しい二分探索の実装を提供できIList<T>ますか?

アップデート

二分探索を使用する前にリストをソートする必要があることは誰もが知っているので、そうだと推測できます。しかし、私はそれがソートと同じ問題であると思います(しかし検証しませんでした)-どのようにソートしますIList<T>か?

結論

の組み込みの二分探索はないようですIList<T>。LINQメソッドを使用First()して検索と並べ替えを行うことができますが、パフォーマンスが低下する可能性があります。OrderBy()(拡張メソッドとして)自分で実装するのが最善のようです。

4

11 に答える 11

39

一部の基本クラスに存在するもの(ただし、明らかにインターフェイスには存在しない)を除いて、.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;
}

もちろん、これは、比較者が使用するのと同じルールに従って、問題のリストがすでにソートされていることを前提としています。

于 2009-06-08T21:21:29.097 に答える
35

これが私のバージョンのLasseのコードです。ラムダ式を使用して検索を実行できると便利だと思います。オブジェクトのリストを検索する場合、ソートに使用されたキーのみを渡すことができます。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);
}
于 2010-06-01T10:13:17.730 に答える
34

拡張メソッドを使用したソリューションが好きです。ただし、少し警告があります。

これは事実上、彼の著書 『Programming Pearls』からのJon Bentleyの実装であり、20年ほどの間発見されなかった数値オーバーフローのバグに適度に苦しんでいます。IListに多数のアイテムがある場合、(上+下)はInt32をオーバーフローする可能性があります。これに対する解決策は、代わりに減算を使用して、中間の計算を少し異なる方法で実行することです。中=下+(上-下)/ 2;

Bentleyはまた、Programming Pearlsで、バイナリ検索アルゴリズムが1946年に公開され、最初の正しい実装は1962年まで公開されなかったと警告しました。

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

于 2009-06-09T03:02:51.013 に答える
5

私はこれを正しくするのにしばらく苦労してきました。特に、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の仕様によれば、これで実装が機能することで問題が解決することを願っています。

于 2012-01-01T15:21:22.977 に答える
4

バイナリ検索でいくつかの問題が発生しIList<T>ます。まず、前述のように、のBinarySearchメソッドはインターフェイスList<T>のメンバーではありません。IList<T>次に、検索する前にリストを並べ替える方法がありません(バイナリ検索を機能させるには、これを行う必要があります)。

私はあなたの最善の策は、新しいものを作成し、List<T>それをソートしてから検索することだと思います。完璧ではありませんが、があれば多くのオプションを選択する必要はありませんIList<T>

于 2009-06-08T21:16:02.320 に答える
3

以下のAntoineによって提供される実装には、バグがあることに注意してください。リスト内のどの項目よりも大きい項目を検索する場合。戻り値は、〜middleではなく〜lowerである必要があります。メソッドArraySortHelper.InternalBinarySearch(mscorlib)を逆コンパイルして、フレームワークの実装を確認します。

于 2010-08-04T14:17:10.077 に答える
2

のバイナリ検索用の既製の実装が必要な場合IList<T>WintellectのPowerCollectionsには次の ものがあります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>
{
    // ...
}
于 2009-09-28T12:29:32.657 に答える
2

を使用できますList<T>.BinarySearch(T item)。カスタム比較機能を使用する場合は、を使用しますList<T>.BinarySearch(T item, IComparer<T> comparer)。詳細については、このMSDNリンクを参照してください。

于 2016-11-18T19:03:22.840 に答える
-1

.NET 3.5を使用できる場合は、Linq拡張メソッドのビルドを使用できます。

using System.Linq;

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

ただし、これは実際にはAndrew Hareのソリューションを実行するためのわずかに異なる方法であり、同じ順序のリストから複数回検索する場合にのみ非常に役立ちます。

于 2009-06-08T21:43:47.587 に答える
-1

一部のリストの実装では、バイナリ検索が非常に非効率になる可能性があることに注意してください。たとえば、リンクリストの場合、正しく実装するとO(n)になり、単純に実装するとO(n log n)になります。

于 2009-09-28T12:36:47.137 に答える
-3

ListとIListにはBinarySearchメソッドがありませんが、SortedListにはあることに注意してください。

于 2009-06-08T21:34:15.920 に答える