2010-09-08 9 views
21

¿Por qué hay solo un SortedList<TKey, TValue> que se parece más a un diccionario, pero no SortedList<T> que en realidad es solo una lista que siempre está ordenada?¿Por qué no hay SortedList <T> en .NET?

De acuerdo con the MSDN documentation on SortedList, en realidad se implementa internamente como una matriz de tamaño dinámico de KeyValuePair<TKey, TValue> que siempre está ordenada por la clave. ¿No sería la misma clase más útil como una lista de cualquier tipo T? ¿No encajaría eso mejor el nombre también?

+1

hmm, una idea interesante. Pero si tuviera una SortedList ¿cómo realizaría la clasificación (¿dónde está la 'clave')? ¿Tendría que hacer que Foo implemente IComparable? – RPM1984

+2

Tengo que estar de acuerdo contigo; dado el nombre, realmente no esperarías que esta clase contenga pares clave-valor. No es un diccionario, por supuesto, ya que puede tener la misma clave presente en la lista varias veces. –

+0

@ RPM1984 - Sí, podría hacer Foo IComparable o podría suministrar un comparador cuando construya la lista. –

Respuesta

9

Aunque nadie puede decirle realmente por qué no hay SortedList<T>, es posible analizar por qué SortedList toma una clave y un valor. Un diccionario mapea las claves de los valores. Las formas típicas de hacerlo son con un árbol binario, una tabla hash y una lista (matriz), aunque las tablas hash son más comunes porque son O (1) para la mayoría de las operaciones.

La operación principal que no admite en O (1) es obtener la siguiente clave en orden. Si desea poder hacer eso, normalmente usa un árbol binario, que le proporciona un diccionario ordenado.

Si decide implementar el mapa como una lista, mantendrá los elementos ordenados por clave para que la búsqueda sea O (lg n), proporcionándole otro diccionario ordenado, en forma de una lista ordenada. Por supuesto, el nombre SortedDictionary ya estaba tomado, pero SortedList no. Podría haberlo llamado SortedListDictionary o SortedDictionaryList, pero no pude nombrarlo.

1

Es una lista con la clasificación realizada por la clave. Solo estoy especulando, pero al proporcionar la capacidad de especificar la clave separada del elemento, su elemento no tiene que ser comparable, solo la clave debe ser. Me imagino que, en el caso general, esto ahorra una buena cantidad de código que se está desarrollando para implementar IComparable, ya que la clave es probablemente un tipo que ya es comparable.

+3

Esta respuesta no tiene ningún sentido. Si desea ordenar por algo, necesita un comparador, independientemente de si llama al algo una "clave" o un "valor". Si la clave es de un tipo que ya es comparable, entonces obviamente también podría ordenar una lista de solo las claves, y si no es así, entonces claramente 'SortedList 'tampoco proporciona la funcionalidad deseada. – Timwi

+0

Mi punto es que, para la mayoría de los casos, con 'SortedList ', terminaría teniendo que implementar IComparable para una clase y que IComparable simplemente volvería a implementar (o delegaría la funcionalidad) la comparabilidad de un tipo de valor. Siendo ese el caso, hacer explícita la clave de clasificación simplifica la implementación para el usuario de la clase a expensas de algunos gastos generales de almacenamiento, suponiendo que la clave se extrae de una propiedad de clase. Obviamente, puede mantener una lista de claves ordenadas y una lista de valores asociados en el mismo orden. Creo que así es como se implementa SortedList. No muy eficiente. – tvanfosson

+1

@tvanfosson: No es correcto. La clase en sí no tiene que ser comparable.De hecho, la razón principal para tener una SortedList es situaciones en las que el programador proporcionará una función personalizada como comparador al construir la lista. Por ejemplo, si quiero ordenar puntos 2D en y y luego en x, proporcionaría un IComparer que así lo haga. Tal Comparer no es inherente a la clase de punto, ni debería serlo. Más bien es parte de mi uso personalizado de esos puntos – ToolmakerSteve

0

Los comentarios RPM son bastante válidos. Además, con las extensiones Linq, puede ordenar por cualquier propiedad de T utilizando el método de extensión Ordenar. Creo que ese podría ser el principal razonamiento detrás de esto.

+3

No hay método de extensión 'Ordenar'. Si quisiste decir 'List .Sort' (que no es ni un método de extensión ni una parte de LINQ), entonces usar esto después de cada llamada a' Add' y 'Insert' sería mucho más lento de lo necesario. Si se refería a 'OrderBy', es * incluso * más lento. – Timwi

+0

La capacidad de ordenar no es una respuesta a la pregunta. RAZÓN: El punto de una clase 'Sorted..' es que MANTENDE la clasificación a medida que agrega/elimina elementos. Esto es bastante diferente de llamar a List.Sort cada vez que lo necesite para ordenarlo; si esa es la única solución, entonces en algunos casos de uso el programador debería llamar a Sort cada vez que agreguen un nuevo elemento: extremadamente lento. Una buena clase Sorted ... funcionaría mucho mejor que llamar Sort en cada inserción (a través de la estructura interna apropiada). – ToolmakerSteve

2

Creo que la razón probablemente sea que List<T> ya tiene BinarySearch y Insert, lo que significa que implementar su propia lista siempre ordenada es trivial.

No es que esto signifique que una clase SortedList<T> no pertenece al marco, solo que probablemente no era una prioridad muy alta, ya que cualquier desarrollador que la necesitara podría escribirla rápidamente.

Creo que lo mismo ocurrió con HashSet<T>, que originalmente no existía porque podría usar fácilmente un Dictionary<T, byte> (por ejemplo) para simular uno antes de .NET 3.5.

sé que es lo que hice en los dos casos: que tenía una clase UniqueSet<T> y una clase AlwaysSortedList<T>, que acaba de terminar un Dictionary<T, byte> y una List<T> (y utiliza BinarySearch y Insert), respectivamente.

+1

Si una 'SortedList ' tiene demasiada prioridad, entonces ciertamente 'SortedList ', que proporciona un subconjunto estricto de la funcionalidad, sería incluso de menor prioridad. – Timwi

+0

@Timwi: No creo que sea * bastante * preciso sobre 'SortedList '. Quiero decir, en términos de su implementación, sí, parece ser el caso. Pero esa clase está destinada a ser utilizada como un diccionario, de ahí todas las comparaciones entre "SortedList ' y 'SortedDictionary ' en la documentación de MSDN (y el hecho de que 'SortedList 'implementa' IDictionary '). No sé * por qué * habrían priorizado esto por encima de una lista ordenada "real"; el hecho es que la implementación de uno no requiere prácticamente ningún esfuerzo. –

+1

Pensé que el objetivo de un marco era no tener que hacer cosas triviales –

4

Hay ahora :)

public class SortedList<T> : ICollection<T> 
{ 
    private List<T> m_innerList; 
    private Comparer<T> m_comparer; 

    public SortedList() : this(Comparer<T>.Default) 
    { 
    } 

    public SortedList(Comparer<T> comparer) 
    { 
     m_innerList = new List<T>(); 
     m_comparer = comparer; 
    } 

    public void Add(T item) 
    { 
     int insertIndex = FindIndexForSortedInsert(m_innerList, m_comparer, item); 
     m_innerList.Insert(insertIndex, item); 
    } 

    public bool Contains(T item) 
    { 
     return IndexOf(item) != -1; 
    } 

    /// <summary> 
    /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire SortedList<T> 
    /// </summary> 
    public int IndexOf(T item) 
    { 
     int insertIndex = FindIndexForSortedInsert(m_innerList, m_comparer, item); 
     if (insertIndex == m_innerList.Count) 
     { 
      return -1; 
     } 
     if (m_comparer.Compare(item, m_innerList[insertIndex]) == 0) 
     { 
      int index = insertIndex; 
      while (index > 0 && m_comparer.Compare(item, m_innerList[index - 1]) == 0) 
      { 
       index--; 
      } 
      return index; 
     } 
     return -1; 
    } 

    public bool Remove(T item) 
    { 
     int index = IndexOf(item); 
     if (index >= 0) 
     { 
      m_innerList.RemoveAt(index); 
      return true; 
     } 
     return false; 
    } 

    public void RemoveAt(int index) 
    { 
     m_innerList.RemoveAt(index); 
    } 

    public void CopyTo(T[] array) 
    { 
     m_innerList.CopyTo(array); 
    } 

    public void CopyTo(T[] array, int arrayIndex) 
    { 
     m_innerList.CopyTo(array, arrayIndex); 
    } 

    public void Clear() 
    { 
     m_innerList.Clear(); 
    } 

    public T this[int index] 
    { 
     get 
     { 
      return m_innerList[index]; 
     } 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return m_innerList.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return m_innerList.GetEnumerator(); 
    } 

    public int Count 
    { 
     get 
     { 
      return m_innerList.Count; 
     } 
    } 

    public bool IsReadOnly 
    { 
     get 
     { 
      return false; 
     } 
    } 

    public static int FindIndexForSortedInsert(List<T> list, Comparer<T> comparer, T item) 
    { 
     if (list.Count == 0) 
     { 
      return 0; 
     } 

     int lowerIndex = 0; 
     int upperIndex = list.Count - 1; 
     int comparisonResult; 
     while (lowerIndex < upperIndex) 
     { 
      int middleIndex = (lowerIndex + upperIndex)/2; 
      T middle = list[middleIndex]; 
      comparisonResult = comparer.Compare(middle, item); 
      if (comparisonResult == 0) 
      { 
       return middleIndex; 
      } 
      else if (comparisonResult > 0) // middle > item 
      { 
       upperIndex = middleIndex - 1; 
      } 
      else // middle < item 
      { 
       lowerIndex = middleIndex + 1; 
      } 
     } 

     // At this point any entry following 'middle' is greater than 'item', 
     // and any entry preceding 'middle' is lesser than 'item'. 
     // So we either put 'item' before or after 'middle'. 
     comparisonResult = comparer.Compare(list[lowerIndex], item); 
     if (comparisonResult < 0) // middle < item 
     { 
      return lowerIndex + 1; 
     } 
     else 
     { 
      return lowerIndex; 
     } 
    } 
} 
+0

Lista que no implementa IList. Awesome :) –

+3

@MariuszJamro, en realidad 'SortedList ' no se puede implementar 'IList ' porque algunas operaciones no tienen ningún significado en el contexto de una colección ordenada, como indexador, 'InsertAt' y' RemoveAt' – ironstone13

+1

@Tal se podría use BinarySearch en lugar de FindIndexForSortedInsert – yacc

2

creo que el camino a seguir con este problema es poner en práctica un método de extensión que se suma a List<T> de una manera ordenada (sólo 2 líneas de código;), y luego List<T> se puede utilizar como una lista ordenada (suponiendo que evite el uso de List.Add(...)):

public static void AddSorted<T>(this List<T> list, T value) 
    { 
     int x = list.BinarySearch(value); 
     list.Insert((x >= 0) ? x : ~x, value); 
    } 
Cuestiones relacionadas