¿Cómo realizar una búsqueda binaria en IList ?

Pregunta simple: dado un IList ¿cómo se realiza una búsqueda binaria sin escribir el método usted mismo y sin copiar los datos a un tipo con soporte de búsqueda binario incorporado. Mi estado actual es el siguiente.

  • List.BinarySearch() no es miembro de IList
  • No hay un equivalente del método ArrayList.Adapter() para List
  • IList no hereda de IList , por lo tanto, no es posible usar ArrayList.Adapter()

Tiendo a creer que no es posible con los métodos integrados, pero no puedo creer que ese método básico no se encuentre en BCL / FCL.

Si no es posible, ¿quién puede dar la implementación de búsqueda binaria más corta, más rápida, más inteligente o más hermosa para IList ?

ACTUALIZAR

Todos sabemos que una lista debe ser ordenada antes de usar la búsqueda binaria, por lo tanto, puede suponer que sí lo es. Pero supongo (pero no lo verifiqué) que es el mismo problema con el género: ¿cómo clasifica IList ?

CONCLUSIÓN

Parece que no hay una búsqueda binaria IList para IList . Uno puede usar los métodos First() y OrderBy() LINQ para buscar y ordenar, pero seguramente tendrá un golpe de rendimiento. Implementarlo usted mismo (como un método de extensión) parece lo mejor que puede hacer.

Dudo que haya un método de búsqueda binaria de propósito general en .NET como ese, excepto el que está presente en algunas clases base (pero aparentemente no en las interfaces), así que aquí está mi propósito general.

 public static Int32 BinarySearchIndexOf(this IList list, T value, IComparer comparer = null) { if (list == null) throw new ArgumentNullException(nameof(list)); comparer = comparer ?? Comparer.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; } 

Por supuesto, esto supone que la lista en cuestión ya está ordenada, de acuerdo con las mismas reglas que usará el comparador.

Me gusta la solución con el método de extensión. Sin embargo, un poco de advertencia está en orden.

Esta es efectivamente la implementación de Jon Bentley de su libro Programming Pearls y sufre modestamente de un error con desbordamiento numérico que no se descubrió durante 20 años más o menos. El (superior + inferior) puede desbordar Int32 si tiene una gran cantidad de elementos en el IList. Una solución a esto es hacer el cálculo del medio de una manera un poco diferente usando una resta; Medio = Inferior + (Superior – Inferior) / 2;

Bentley también advirtió en Programming Pearls que mientras el algoritmo de búsqueda binaria se publicó en 1946 y la primera implementación correcta no se publicó hasta 1962.

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

Aquí está mi versión del código de Lasse. Me parece útil poder usar una expresión lambda para realizar la búsqueda. Al buscar en una lista de objetos, solo permite pasar la clave que se usó para ordenar. Las implementaciones que usan IComparer se derivan trivialmente de esta.

También me gusta regresar ~ menor cuando no se encuentra una coincidencia. Array.BinarySearch lo hace y le permite saber dónde debe insertarse el elemento que ha buscado para poder realizar el pedido.

 ///  /// Performs a binary search on the specified collection. ///  /// The type of the item. /// The type of the searched item. /// The list to be searched. /// The value to search for. /// The comparer that is used to compare the value with the list items. ///  public static int BinarySearch(this IList list, TSearch value, Func 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; } ///  /// Performs a binary search on the specified collection. ///  /// The type of the item. /// The list to be searched. /// The value to search for. ///  public static int BinarySearch(this IList list, TItem value) { return BinarySearch(list, value, Comparer.Default); } ///  /// Performs a binary search on the specified collection. ///  /// The type of the item. /// The list to be searched. /// The value to search for. /// The comparer that is used to compare the value with the list items. ///  public static int BinarySearch(this IList list, TItem value, IComparer comparer) { return list.BinarySearch(value, comparer.Compare); } 

Vas a tener un par de problemas al buscar binarios en un IList . Primero, como mencionaste, el método BinarySearch en la List no es miembro de la interfaz IList . En segundo lugar, no tiene forma de ordenar la lista antes de buscarla (lo cual debe hacer para que funcione una búsqueda binaria).

Creo que tu mejor opción es crear una nueva List , ordenarla y luego buscar. No es perfecto, pero no tienes muchas opciones si tienes un IList .

He estado luchando para hacer esto bien desde hace un tiempo. En particular, los valores de retorno para casos extremos tal como se especifica en MSDN: http://msdn.microsoft.com/en-us/library/w4e7fxsh.aspx

Ahora copié el ArraySortHelper.InternalBinarySearch () de .NET 4.0 e hice mi propio sabor por varias razones.

Uso:

 var numbers = new List() { ... }; var items = new List() { ... }; int result1 = numbers.BinarySearchIndexOf(5); int result2 = items.BinarySearchIndexOfBy(foo => foo.bar, 5); 

Esto debería funcionar con todos los tipos .NET. Intenté int, long y double hasta ahora.

Implementación:

 public static class BinarySearchUtils { public static int BinarySearchIndexOf(this IList list, TItem targetValue, IComparer comparer = null) { Func compareFunc = comparer != null ? comparer.Compare : (Func) Comparer.Default.Compare; int index = BinarySearchIndexOfBy(list, compareFunc, targetValue); return index; } public static int BinarySearchIndexOfBy(this IList list, Func 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; } } 

Pruebas unitarias:

 [TestFixture] public class BinarySearchUtilsTest { [Test] public void BinarySearchReturnValueByMsdnSpecification() { var numbers = new List() { 1, 3 }; // Following the MSDN documentation for List.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); } } 

Acabo de sacar esto de mi propio código, así que comente si no funciona de la caja.

Espero que esto solucione el problema con una implementación que funcione de una vez por todas, al menos según las especificaciones de MSDN.

Tenga en cuenta que hay un error en la implementación proporcionada por Antoine a continuación: cuando busca un elemento más grande que cualquiera en la lista. El valor de retorno debe ser ~ menor que ~ medio. Descompile el método ArraySortHelper.InternalBinarySearch (mscorlib) para ver la implementación del marco.

Si necesita una implementación lista para la búsqueda binaria en IList s, las Colecciones de energía de Wintellect tienen una (en Algorithms.cs ):

 ///  /// 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<T>). ///  /// The sorted list to search. /// The item to search for. /// Returns the first index at which the item can be found. If the return /// value is zero, indicating that  was not present in the list, then this /// returns the index at which  could be inserted to maintain the sorted /// order of the list. /// The number of items equal to  that appear in the list. public static int BinarySearch(IList list, T item, out int index) where T: IComparable { // ... } 

Puede usar List.BinarySearch(T item) . Si desea utilizar un comparador personalizado, utilice List.BinarySearch(T item, IComparer comparer) . Eche un vistazo a este enlace de MSDN para más detalles.

Tenga en cuenta que la búsqueda binaria puede ser bastante ineficiente para algunas implementaciones de listas. Por ejemplo, para una lista vinculada, es O (n) si la implementa correctamente, y O (n log n) si la implementa ingenuamente.

Si puede usar .NET 3.5, puede usar la comstackción en los métodos de extensión Linq:

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

Sin embargo, esta es solo una forma ligeramente diferente de abordar la solución de Andrew Hare, y solo es realmente útil si buscas varias veces fuera de la misma lista ordenada.

Tenga en cuenta que mientras List e IList no tienen un método BinarySearch, SortedList sí lo tiene.