Accès à une clé Dictionary.Keys via un index numérique

160

J'utilise un Dictionary<string, int>où leint est un compte de la clé.

Maintenant, j'ai besoin d'accéder à la dernière clé insérée dans le dictionnaire, mais je n'en connais pas le nom. La tentative évidente:

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

ne fonctionne pas, car Dictionary.Keysn'implémente pas un [] -indexer.

Je me demande simplement s'il existe une classe similaire? J'ai pensé à utiliser une pile, mais cela ne stocke qu'une chaîne. Je pourrais maintenant créer ma propre structure et ensuite utiliser a Stack<MyStruct>, mais je me demande s'il existe une autre alternative, essentiellement un dictionnaire qui implémente un [] -indexer sur les clés?

Michael Stum
la source
1
Que se passe-t-il si vous encadrez cette variable?
Paul Prewett

Réponses:

222

Comme @Falanwe le souligne dans un commentaire, faire quelque chose comme ça est incorrect :

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

Vous ne devez pas dépendre de l'ordre des clés dans un dictionnaire. Si vous avez besoin d'une commande, vous devez utiliser un OrderedDictionary , comme suggéré dans cette réponse . Les autres réponses sur cette page sont également intéressantes.

Vitor Hugo
la source
1
semble ne pas fonctionner avec HashTableSystem.Collections.ICollection 'ne contient pas de définition pour' ElementAt 'et aucune méthode d'extension' ElementAt 'acceptant un premier argument de type' System.Collections.ICollection 'n'a pu être trouvée
v.oddou
Vous pouvez utiliser la ElementAtOrDefaultversion pour travailler avec la version sans exception.
Tarık Özgün Güner
22
C'est effrayant de voir une réponse aussi manifestement fausse acceptée et votée autant. C'est faux car, comme l' indique la Dictionary<TKey,TValue>documentation , "l'ordre des clés dans le Dictionary<TKey, TValue>.KeyCollectionn'est pas spécifié." L'ordre étant indéfini, vous n'avez aucun moyen de savoir avec certitude qui est à la dernière position ( mydict.Count -1)
Falanwe
C'est effrayant ... mais utile pour moi car je cherchais une confirmation de mon soupçon que vous ne pouvez pas compter sur la commande !!! Merci @Falanwe
Charlie
3
Pour certains, l'ordre n'est pas pertinent - juste le fait que vous ayez passé toutes les clés.
Royi Mindel
59

Vous pouvez utiliser un OrderedDictionary .

Représente une collection de paires clé / valeur accessibles par la clé ou l'index.

Andrew Peters
la source
42
Erhm, après 19 votes positifs, personne n'a mentionné que OrderedDictionary ne permet toujours pas d'obtenir la clé par index?
Lazlo du
1
Vous pouvez accéder à une valeur avec un index entier avec un OrderedDictionary , mais pas avec un System.Collections.Generic.SortedDictionary <TKey, TValue> où l'index doit être un TKey
Maxence
Le nom OrderedDictionary est lié à cette fonction de collection pour conserver les éléments dans le même ordre dans lequel ils ont été ajoutés. Dans certains cas, un ordre a la même signification qu'un tri, mais pas dans cette collection.
Sharunas Bielskis le
18

Un dictionnaire est une table de hachage, vous n'avez donc aucune idée de l'ordre d'insertion!

Si vous voulez connaître la dernière clé insérée, je suggère d'étendre le dictionnaire pour inclure une valeur LastKeyInserted.

Par exemple:

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

}

Vous rencontrerez des problèmes, cependant lorsque vous l'utiliserez .Remove(), vous devrez donc conserver une liste ordonnée des clés insérées.

Sam
la source
8

Pourquoi ne pas étendre simplement la classe de dictionnaire pour ajouter une dernière propriété clé insérée. Quelque chose comme ce qui suit peut-être?

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
la source
2
Vous définissez lastKeyInserted sur la dernière valeur insérée. Soit vous vouliez le définir sur la dernière clé insérée, soit vous avez besoin de meilleurs noms pour la variable et la propriété.
Fantius
6

Vous pouvez toujours faire ceci:

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

Mais je ne le recommanderais pas. Il n'y a aucune garantie que la dernière clé insérée sera à la fin du tableau. La commande des clés sur MSDN n'est pas spécifiée et peut être modifiée. Dans mon très bref test, cela semble être dans l'ordre d'insertion, mais vous feriez mieux de créer une comptabilité appropriée comme une pile - comme vous le suggérez (bien que je ne vois pas la nécessité d'une structure basée sur votre autres instructions) - ou cache de variable unique si vous avez juste besoin de connaître la dernière clé.

Patrick
la source
5

Je pense que vous pouvez faire quelque chose comme ça, la syntaxe est peut-être erronée, vous n'avez pas utilisé C # depuis un moment Pour obtenir le dernier élément

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

ou utilisez Max au lieu de Last pour obtenir la valeur maximale, je ne sais pas lequel correspond le mieux à votre code.

Juan
la source
2
J'ajouterais que puisque "Last ()" est une méthode d'extension, vous auriez besoin du .NET Framework 3.5 et d'ajouter "using System.Linq" en haut de votre fichier .cs.
SuperOli
Essayez ceci pour la fin (lorsque vous utilisez un Dist <string, string> évidemment :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor
4

Je suis d'accord avec la deuxième partie de la réponse de Patrick. Même si dans certains tests, il semble conserver l'ordre d'insertion, la documentation (et le comportement normal des dictionnaires et des hachages) indique explicitement que l'ordre n'est pas spécifié.

Vous demandez simplement des problèmes en fonction de la commande des clés. Ajoutez votre propre comptabilité (comme Patrick l'a dit, juste une seule variable pour la dernière clé ajoutée) pour être sûr. Aussi, ne vous laissez pas tenter par toutes les méthodes telles que Last et Max sur le dictionnaire car elles sont probablement liées au comparateur clé (je ne suis pas sûr de cela).

Stephen Pellicer
la source
4

Dans le cas où vous décidez d'utiliser un code dangereux qui est sujet à rupture, cette fonction d'extension récupérera une clé de a en Dictionary<K,V>fonction de son indexation interne (qui pour Mono et .NET semble actuellement être dans le même ordre que vous obtenez en énumérant la Keyspropriété ).

Il est de loin préférable d'utiliser Linq:, dict.Keys.ElementAt(i)mais cette fonction itérera O (N); ce qui suit est O (1) mais avec une pénalité de performance de réflexion.

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
la source
Hmm, modifier pour améliorer la réponse a obtenu un vote défavorable. N'ai-je pas précisé que le code est (évidemment) hideux et doit être considéré en conséquence?
Glenn Slayden
4

Une alternative serait un KeyedCollection si la clé est incorporée dans la valeur.

Créez simplement une implémentation de base dans une classe scellée à utiliser.

Donc pour remplacer Dictionary<string, int>(ce qui n'est pas un très bon exemple car il n'y a pas de clé claire pour 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
la source
En ce qui concerne vos commentaires sur la clé, voir ma réponse de suivi à celle-ci.
takrl le
3

La façon dont vous avez formulé la question me porte à croire que l'int dans le dictionnaire contient la "position" de l'élément dans le dictionnaire. À en juger par l'affirmation selon laquelle les clés ne sont pas stockées dans l'ordre dans lequel elles ont été ajoutées, si cela est correct, cela signifierait que keys.Count (ou .Count - 1, si vous utilisez une base zéro) devrait toujours toujours être le numéro de la dernière clé saisie?

Si c'est correct, y a-t-il une raison pour laquelle vous ne pouvez pas utiliser à la place Dictionary <int, string> pour pouvoir utiliser mydict [mydict.Keys.Count]?

Jeremy Privett
la source
2

Je ne sais pas si cela fonctionnerait parce que je suis à peu près sûr que les clés ne sont pas stockées dans l'ordre dans lequel elles sont ajoutées, mais vous pouvez convertir KeysCollection dans une liste, puis obtenir la dernière clé de la liste ... mais ça vaudrait la peine d'y jeter un œil.

La seule autre chose à laquelle je peux penser est de stocker les clés dans une liste de recherche et d'ajouter les clés à la liste avant de les ajouter au dictionnaire ... ce n'est pas joli.

lomaxx
la source
@Juan: il n'y a pas de méthode .Last () sur KeyCollection
lomaxx
Je n'ai pas testé le code, mais la méthode est documentée sur [MSDN] [1] peut-être que c'est une autre version du framework? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan
2 ans de retard mais ça pourrait aider quelqu'un ... voir ma réponse au message de Juan ci-dessous. Last () est une méthode d'extension.
SuperOli
2

Pour développer l'article de Daniels et ses commentaires concernant la clé, puisque la clé est de toute façon intégrée dans la valeur, vous pouvez recourir à l'utilisation d'un KeyValuePair<TKey, TValue> comme valeur. Le raisonnement principal en est que, en général, la clé n'est pas nécessairement directement dérivable de la valeur.

Ensuite, cela ressemblerait à ceci:

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

Pour utiliser ceci comme dans l'exemple précédent, vous feriez:

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
la source
2

Un dictionnaire peut ne pas être très intuitif pour utiliser l'index comme référence, mais vous pouvez avoir des opérations similaires avec un tableau de KeyValuePair :

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

espaciomore
la source
2

Vous pouvez également utiliser SortedList et son équivalent générique. Ces deux classes et dans la réponse d'Andrew Peters mentionnées OrderedDictionary sont des classes de dictionnaire dans lesquelles les éléments sont accessibles par index (position) ainsi que par clé. Comment utiliser ces classes, vous pouvez trouver: Classe SortedList , Classe générique SortedList .

Sharunas Bielskis
la source
1

UserVoice de Visual Studio donne un lien vers l' implémentation générique OrderedDictionary par dotmore.

Mais si vous avez seulement besoin d'obtenir des paires clé / valeur par index et que vous n'avez pas besoin d'obtenir des valeurs par clés, vous pouvez utiliser une astuce simple. Déclarez une classe générique (je l'ai appelée ListArray) comme suit:

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

Vous pouvez également le déclarer avec des constructeurs:

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

Par exemple, vous lisez certaines paires clé / valeur d'un fichier et souhaitez simplement les stocker dans l'ordre dans lequel elles ont été lues afin de les obtenir plus tard par index:

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];

Comme vous l'avez peut-être remarqué, vous ne pouvez pas nécessairement avoir uniquement des paires clé / valeur dans votre ListArray. Les tableaux d'éléments peuvent être de n'importe quelle longueur, comme dans un tableau irrégulier.

quicktrick
la source