Acceder a una clave de Dictionary.Keys a través de un índice numérico

160

Estoy usando un Dictionary<string, int>donde el intes un recuento de la clave.

Ahora, necesito acceder a la última Clave insertada dentro del Diccionario, pero no sé su nombre. El intento obvio:

int LastCount = mydict[mydict.keys[mydict.keys.Count]];

no funciona porque Dictionary.Keysno implementa un indexador [].

Me pregunto si hay alguna clase similar? Pensé en usar una pila, pero eso solo almacena una cadena. Ahora podría crear mi propia estructura y luego usar a Stack<MyStruct>, pero me pregunto si hay otra alternativa, esencialmente un diccionario que implementa un índice [] en las teclas.

Michael Stum
fuente
1
¿Qué sucede si encuadras esa variable?
Paul Prewett

Respuestas:

222

Como @Falanwe señala en un comentario, hacer algo como esto es incorrecto :

int LastCount = mydict.Keys.ElementAt(mydict.Count -1);

No debe depender del orden de las teclas en un Diccionario. Si necesita ordenar, debe usar un OrderedDictionary , como se sugiere en esta respuesta . Las otras respuestas en esta página también son interesantes.

Vitor Hugo
fuente
1
parece que no funciona con HashTableSystem.Collections.ICollection 'no contiene una definición para' ElementAt 'y no se puede encontrar ningún método de extensión' ElementAt 'que acepte un primer argumento de tipo' System.Collections.ICollection '
v.oddou
Puede usar la ElementAtOrDefaultversión para trabajar con la versión sin excepción.
Tarık Özgün Güner
22
Da miedo ver una respuesta tan descaradamente incorrecta aceptada y votada tanto. Está mal porque, como dice la Dictionary<TKey,TValue>documentación , "el orden de las claves en el Dictionary<TKey, TValue>.KeyCollectionno está especificado". El orden no está definido, no tiene forma de saber con certeza cuál está en la última posición ( mydict.Count -1)
Falanwe
Esto es aterrador ... ¡pero útil para mí ya que estaba buscando confirmación de mi sospecha de que no puede contar con el pedido! Gracias @Falanwe
Charlie
3
Para algunos, el orden no es relevante, solo el hecho de que revisaste todas las claves.
Royi Mindel
59

Puedes usar un OrderedDictionary .

Representa una colección de pares clave / valor a los que puede acceder la clave o el índice.

Andrew Peters
fuente
42
Erhm, después de 19 votos a favor, ¿nadie mencionó que OrderedDictionary todavía no permite obtener la clave por índice?
Lazlo
1
Puede acceder a un valor con un índice entero con un OrderedDictionary , pero no con un System.Collections.Generic.SortedDictionary <TKey, TValue> donde el índice debe ser una TKey
Maxence
El nombre de OrderedDictionary está relacionado con esta función de colección para mantener los elementos en el mismo orden en que se agregaron. En algunos casos, un pedido tiene el mismo significado que una ordenación, pero no en esta colección.
Sharunas Bielskis
18

Un diccionario es una tabla hash, por lo que no tiene idea del orden de inserción.

Si desea conocer la última clave insertada, le sugiero extender el Diccionario para incluir un valor LastKeyInserted.

P.ej:

public MyDictionary<K, T> : IDictionary<K, T>
{
    private IDictionary<K, T> _InnerDictionary;

    public K LastInsertedKey { get; set; }

    public MyDictionary()
    {
        _InnerDictionary = new Dictionary<K, T>();
    }

    #region Implementation of IDictionary

    public void Add(KeyValuePair<K, T> item)
    {
        _InnerDictionary.Add(item);
        LastInsertedKey = item.Key;

    }

    public void Add(K key, T value)
    {
        _InnerDictionary.Add(key, value);
        LastInsertedKey = key;
    }

    .... rest of IDictionary methods

    #endregion

}

Tendrá problemas, sin embargo, cuando lo utilice .Remove()para superar esto, deberá mantener una lista ordenada de las claves insertadas.

Sam
fuente
8

¿Por qué no simplemente extiende la clase del diccionario para agregar una última propiedad insertada en la clave? Algo como lo siguiente tal vez?

public class ExtendedDictionary : Dictionary<string, int>
{
    private int lastKeyInserted = -1;

    public int LastKeyInserted
    {
        get { return lastKeyInserted; }
        set { lastKeyInserted = value; }
    }

    public void AddNew(string s, int i)
    {
        lastKeyInserted = i;

        base.Add(s, i);
    }
}
Calanus
fuente
2
Está configurando lastKeyInserted en el último valor insertado. O quería establecerlo en la última clave insertada o necesita mejores nombres para la variable y la propiedad.
Fantius
6

Siempre puedes hacer esto:

string[] temp = new string[mydict.count];
mydict.Keys.CopyTo(temp, 0)
int LastCount = mydict[temp[mydict.count - 1]]

Pero no lo recomendaría. No hay garantía de que la última clave insertada esté al final de la matriz. El pedido de claves en MSDN no está especificado y está sujeto a cambios. En mi breve prueba, parece estar en orden de inserción, pero sería mejor construir una contabilidad adecuada como una pila, como sugiere (aunque no veo la necesidad de una estructura basada en su otras declaraciones) - o caché de variable única si solo necesita conocer la última clave.

Patricio
fuente
5

Creo que puede hacer algo como esto, la sintaxis puede estar equivocada, no he usado C # en un tiempo para obtener el último elemento

Dictionary<string, int>.KeyCollection keys = mydict.keys;
string lastKey = keys.Last();

o use Max en lugar de Last para obtener el valor máximo, no sé cuál se ajusta mejor a su código.

Juan
fuente
2
Agregaría que dado que "Last ()" es un método de extensión, necesitaría .NET Framework 3.5 y agregar "using System.Linq" en la parte superior de su archivo .cs.
SuperOli
Pruebe esto para el final (cuando use un Dist <string, string> obviamente :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor
4

Estoy de acuerdo con la segunda parte de la respuesta de Patrick. Incluso si en algunas pruebas parece mantener el orden de inserción, la documentación (y el comportamiento normal de los diccionarios y hashes) declara explícitamente que el orden no está especificado.

Solo está pidiendo problemas dependiendo del orden de las llaves. Agregue su propia contabilidad (como dijo Patrick, solo una variable para la última clave agregada) para estar seguro. Además, no se deje tentar por todos los métodos, como Last y Max en el diccionario, ya que probablemente estén relacionados con el comparador de claves (no estoy seguro de eso).

Stephen Pellicer
fuente
4

En caso de que decida usar un código peligroso que esté sujeto a roturas, esta función de extensión obtendrá una clave de Dictionary<K,V>acuerdo con su indexación interna (que para Mono y .NET actualmente parece estar en el mismo orden que usted al enumerar la Keyspropiedad )

Es mucho preferible usar Linq:, dict.Keys.ElementAt(i)pero esa función repetirá O (N); el siguiente es O (1) pero con una penalización de rendimiento de reflexión.

using System;
using System.Collections.Generic;
using System.Reflection;

public static class Extensions
{
    public static TKey KeyByIndex<TKey,TValue>(this Dictionary<TKey, TValue> dict, int idx)
    {
        Type type = typeof(Dictionary<TKey, TValue>);
        FieldInfo info = type.GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance);
        if (info != null)
        {
            // .NET
            Object element = ((Array)info.GetValue(dict)).GetValue(idx);
            return (TKey)element.GetType().GetField("key", BindingFlags.Public | BindingFlags.Instance).GetValue(element);
        }
        // Mono:
        info = type.GetField("keySlots", BindingFlags.NonPublic | BindingFlags.Instance);
        return (TKey)((Array)info.GetValue(dict)).GetValue(idx);
    }
};
Glenn Slayden
fuente
Hmm, la edición para mejorar la respuesta obtuvo un voto negativo. ¿No dejé en claro que el código es (obviamente) horrible, y debería considerarse en consecuencia?
Glenn Slayden
4

Una alternativa sería KeyedCollection si la clave está incrustada en el valor.

Simplemente cree una implementación básica en una clase sellada para usar.

Entonces, para reemplazar Dictionary<string, int>(que no es un muy buen ejemplo, ya que no hay una clave clara para un int).

private sealed class IntDictionary : KeyedCollection<string, int>
{
    protected override string GetKeyForItem(int item)
    {
        // The example works better when the value contains the key. It falls down a bit for a dictionary of ints.
        return item.ToString();
    }
}

KeyedCollection<string, int> intCollection = new ClassThatContainsSealedImplementation.IntDictionary();

intCollection.Add(7);

int valueByIndex = intCollection[0];
Daniel Ballinger
fuente
Con respecto a sus comentarios sobre la clave, vea mi respuesta de seguimiento a esta.
takrl
3

La forma en que redactó la pregunta me lleva a creer que el int en el Diccionario contiene la "posición" del elemento en el Diccionario. A juzgar por la afirmación de que las claves no se almacenan en el orden en que se agregaron, si esto es correcto, eso significaría que las claves.Count (o .Count - 1, si está utilizando cero) aún deberían siempre será el número de la última clave ingresada?

Si eso es correcto, ¿hay alguna razón por la que no puedas usar Dictionary <int, string> para que puedas usar mydict [mydict.Keys.Count]?

Jeremy Privett
fuente
2

No sé si esto funcionaría porque estoy bastante seguro de que las claves no se almacenan en el orden en que se agregan, pero puede convertir KeysCollection en una lista y luego obtener la última clave en la lista ... pero valdría la pena echarle un vistazo.

La única otra cosa que se me ocurre es almacenar las claves en una lista de búsqueda y agregar las claves a la lista antes de agregarlas al diccionario ... aunque no es bastante bueno.

lomaxx
fuente
@Juan: no hay ningún método .Last () en KeyCollection
lomaxx
No probé el código, pero el método está documentado en [MSDN] [1] ¿tal vez es otra versión del marco? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan
2 años de retraso pero podría ayudar a alguien ... vea mi respuesta a la publicación de Juan a continuación. Last () es un método de extensión.
SuperOli
2

Para ampliar la publicación de Daniels y sus comentarios con respecto a la clave, ya que la clave está incrustada en el valor de todos modos, puede recurrir a usar a KeyValuePair<TKey, TValue>como valor. El razonamiento principal para esto es que, en general, la clave no necesariamente se deriva directamente del valor.

Entonces se vería así:

public sealed class CustomDictionary<TKey, TValue>
  : KeyedCollection<TKey, KeyValuePair<TKey, TValue>>
{
  protected override TKey GetKeyForItem(KeyValuePair<TKey, TValue> item)
  {
    return item.Key;
  }
}

Para usar esto como en el ejemplo anterior, haría:

CustomDictionary<string, int> custDict = new CustomDictionary<string, int>();

custDict.Add(new KeyValuePair<string, int>("key", 7));

int valueByIndex = custDict[0].Value;
int valueByKey = custDict["key"].Value;
string keyByIndex = custDict[0].Key;
takrl
fuente
2

Un diccionario puede no ser muy intuitivo para usar el índice como referencia, pero puede tener operaciones similares con una matriz de KeyValuePair :

ex. KeyValuePair<string, string>[] filters;

espaciomore
fuente
2

También puede usar SortedList y su contraparte genérica. Estas dos clases y en la respuesta de Andrew Peters mencionan que OrderedDictionary son clases de diccionario en las que se puede acceder a los elementos por índice (posición) y por clave. Cómo utilizar estas clases puede encontrar: Clase SortedList , Clase Genérica SortedList .

Sharunas Bielskis
fuente
1

UserVoice de Visual Studio ofrece un enlace a la implementación genérica de OrderedDictionary por dotmore.

Pero si solo necesita obtener pares clave / valor por índice y no necesita obtener valores por claves, puede usar un simple truco. Declare alguna clase genérica (la llamé ListArray) de la siguiente manera:

class ListArray<T> : List<T[]> { }

También puede declararlo con constructores:

class ListArray<T> : List<T[]>
{
    public ListArray() : base() { }
    public ListArray(int capacity) : base(capacity) { }
}

Por ejemplo, lee algunos pares clave / valor de un archivo y solo desea almacenarlos en el orden en que fueron leídos para obtenerlos luego por índice:

ListArray<string> settingsRead = new ListArray<string>();
using (var sr = new StreamReader(myFile))
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {
        string[] keyValueStrings = line.Split(separator);
        for (int i = 0; i < keyValueStrings.Length; i++)
            keyValueStrings[i] = keyValueStrings[i].Trim();
        settingsRead.Add(keyValueStrings);
    }
}
// Later you get your key/value strings simply by index
string[] myKeyValueStrings = settingsRead[index];

Como habrás notado, no necesariamente puedes tener solo pares de clave / valor en tu ListArray. Las matrices de elementos pueden ser de cualquier longitud, como en una matriz irregular.

truco rápido
fuente