c# - index - Acceder a una clave de Dictionary.Keys a través de un índice numérico
get index of dictionary (15)
¿Por qué no simplemente extiende la clase de 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);
}
}
Estoy usando un Dictionary<string, int>
donde int
es 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.Keys
no 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 un Stack<MyStruct>
, pero me pregunto si hay otra alternativa, esencialmente un Diccionario que implemente un [] -indexer en las Teclas.
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 claves en un Diccionario. Si necesita ordenar, debe usar un OrderedDictionary , como se sugiere en esta answer . Las otras respuestas en esta página también son interesantes.
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.
En caso de que decida utilizar un código peligroso que esté sujeto a roturas, esta función de extensión obtendrá una clave de un Dictionary<K,V>
acuerdo con su indexación interna (que para Mono y .NET actualmente parece estar en el mismo orden que se obtiene enumerando la propiedad Keys
).
Es preferible utilizar 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);
}
};
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á buscando 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).
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]?
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.
Lo único 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.
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 al uso de un KeyValuePair<TKey, TValue>
como el 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;
Puede usar un OrderedDictionary .
Representa una colección de pares clave / valor a los que puede acceder la clave o el índice.
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.
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 .
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
}
Sin embargo, se encontrará con problemas cuando use .Remove()
por lo que para superar esto deberá mantener una lista ordenada de las claves insertadas.
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;
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];
UserVoice Visual Studio ofrece un enlace a la implementación genérica OrderedDictionary de 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.