Accesso a una chiave Dictionary.Keys tramite un indice numerico


160

Sto usando un Dictionary<string, int>dove intè il conteggio della chiave.

Ora, devo accedere all'ultima chiave inserita all'interno del dizionario, ma non ne conosco il nome. L'ovvio tentativo:

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

non funziona, perché Dictionary.Keysnon implementa un [] -indexer.

Mi chiedo solo se esiste una classe simile? Ho pensato di utilizzare uno Stack, ma che memorizza solo una stringa. Ora potrei creare la mia struttura e quindi utilizzare un Stack<MyStruct>, ma mi chiedo se ci sia un'altra alternativa, essenzialmente un dizionario che implementa un [] indice sulle chiavi?


1
Cosa succede se inscatoli quella variabile?
Paul Prewett,

Risposte:


222

Come sottolinea @Falanwe in un commento, fare qualcosa del genere non è corretto :

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

Non dovresti dipendere dall'ordine delle chiavi in ​​un dizionario. Se hai bisogno di ordinare, dovresti usare OrderedDictionary , come suggerito in questa risposta . Anche le altre risposte in questa pagina sono interessanti.


1
sembra non funzionare con HashTableSystem.Collections.ICollection 'non contiene una definizione per' ElementAt 'e non è stato trovato alcun metodo di estensione' ElementAt 'che accetta un primo argomento di tipo' System.Collections.ICollection '
v.oddou

Puoi usare la ElementAtOrDefaultversione per lavorare con la versione senza eccezioni.
Tarık Özgün Güner,

22
È spaventoso vedere una risposta così palesemente sbagliata accettata e votata così tanto. È sbagliato perché, come indicato nella Dictionary<TKey,TValue>documentazione "L'ordine delle chiavi in Dictionary<TKey, TValue>.KeyCollectionnon è specificato." mydict.Count -1
Dato che

Questo è spaventoso ... ma utile per me poiché stavo cercando la conferma del mio sospetto che non puoi contare sull'ordine !!! Grazie @Falanwe
Charlie,

3
Per alcuni l'ordine non è rilevante - solo il fatto che hai esaminato tutte le chiavi.
Royi Mindel,

59

È possibile utilizzare un OrderedDictionary .

Rappresenta una raccolta di coppie chiave / valore accessibili dalla chiave o dall'indice.


42
Erhm, dopo 19 voti, nessuno ha detto che OrderedDictionary non consente ancora di ottenere la chiave per indice?
Lazlo,

1
Puoi accedere a un valore con un indice intero con un OrderedDictionary , ma non con un System.Collections.Generic.SortedDictionary <TKey, TValue> dove l'indice deve essere un TKey
Maxence

Il nome OrderedDictionary è correlato a questa funzione di raccolta per mantenere gli elementi nello stesso ordine in cui sono stati aggiunti. In alcuni casi, un ordine ha lo stesso significato di un ordinamento, ma non in questa raccolta.
Sharunas Bielskis il

18

Un dizionario è una tabella hash, quindi non hai idea dell'ordine di inserimento!

Se vuoi conoscere l'ultima chiave inserita, suggerirei di estendere il dizionario per includere un valore LastKeyInserted.

Per esempio:

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

}

Incontrerai comunque dei problemi quando lo usi .Remove(), per ovviare a questo problema dovrai tenere un elenco ordinato delle chiavi inserite.


8

Perché non estendi semplicemente la classe del dizionario per aggiungere un'ultima proprietà inserita nella chiave. Qualcosa come il seguente forse?

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);
    }
}

2
Stai impostando lastKeyInserted sull'ultimo valore inserito. O intendevi impostarlo sull'ultima chiave inserita o hai bisogno di nomi migliori per la variabile e la proprietà.
Fantius

6

Puoi sempre farlo:

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

Ma non lo consiglierei. Non c'è garanzia che l'ultima chiave inserita sarà alla fine dell'array. L'ordinamento per Chiavi su MSDN non è specificato e può essere modificato. Nel mio brevissimo test, sembra che sia in ordine di inserimento, ma faresti meglio a costruire una contabilità adeguata come una pila - come suggerisci (anche se non vedo la necessità di una struttura basata sul tuo altre istruzioni) - o cache a variabile singola se hai solo bisogno di conoscere la chiave più recente.


5

Penso che tu possa fare qualcosa del genere, la sintassi potrebbe essere sbagliata, non usare C # da un po 'di tempo per ottenere l'ultimo elemento

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

o usa Max invece di Last per ottenere il valore massimo, non so quale si adatta meglio al tuo codice.


2
Vorrei aggiungere che poiché "Last ()" è un metodo di estensione, è necessario .NET Framework 3.5 e aggiungere "using System.Linq" nella parte superiore del file .cs.
SuperOli,

Prova questo per ultimo (quando usi Dist <stringa, stringa> ovviamente :-) KeyValuePair <stringa, stringa> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor,

4

Concordo con la seconda parte della risposta di Patrick. Anche se in alcuni test sembra mantenere l'ordine di inserimento, la documentazione (e il normale comportamento di dizionari e hash) afferma esplicitamente che l'ordinamento non è specificato.

Stai solo chiedendo problemi a seconda dell'ordinamento delle chiavi. Aggiungi la tua contabilità (come ha detto Patrick, solo una singola variabile per l'ultima chiave aggiunta) per essere sicuro. Inoltre, non essere tentato da tutti i metodi come Last e Max sul dizionario in quanto quelli sono probabilmente in relazione al comparatore chiave (non ne sono sicuro).


4

Nel caso in cui si decida di utilizzare un codice pericoloso soggetto a rottura, questa funzione di estensione prenderà una chiave da a in Dictionary<K,V>base alla sua indicizzazione interna (che per Mono e .NET attualmente sembra essere nello stesso ordine che si ottiene elencando ilKeys proprietà ).

È molto preferibile usare Linq:, dict.Keys.ElementAt(i)ma quella funzione ripeterà O (N); il seguente è O (1) ma con una penalità di prestazione di riflessione.

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);
    }
};

Hmm, la modifica per migliorare la risposta ha ottenuto un voto negativo. Non ho chiarito che il codice è (ovviamente) orribile e dovrebbe essere considerato di conseguenza?
Glenn Slayden,

4

Un'alternativa sarebbe un KeyedCollection se la chiave è incorporata nel valore.

Basta creare un'implementazione di base in una classe sigillata da usare.

Quindi, per sostituire Dictionary<string, int>(che non è un ottimo esempio in quanto non esiste una chiave chiara per 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];

Per quanto riguarda i tuoi commenti sulla chiave, vedi la mia risposta di follow-up a questo.
Takrl,

3

Il modo in cui hai formulato la domanda mi porta a credere che int nel dizionario contenga la "posizione" dell'articolo sul dizionario. A giudicare dall'affermazione che le chiavi non sono memorizzate nell'ordine in cui sono state aggiunte, se questo è corretto, ciò significherebbe che keys.Count (o .Count - 1, se si utilizza in base zero) dovrebbe comunque sempre il numero dell'ultima chiave inserita?

Se è corretto, c'è qualche motivo per cui non puoi invece usare Dictionary <int, string> in modo da poter usare mydict [mydict.Keys.Count]?


2

Non so se questo funzionerebbe perché sono abbastanza sicuro che le chiavi non siano memorizzate nell'ordine in cui sono state aggiunte, ma potresti lanciare KeysCollection in un elenco e quindi ottenere l'ultima chiave nell'elenco ... ma varrebbe la pena dare un'occhiata.

L'unica altra cosa che mi viene in mente è quella di memorizzare le chiavi in ​​un elenco di ricerca e aggiungere le chiavi all'elenco prima di aggiungerle al dizionario ... non è abbastanza.


@Juan: non esiste alcun metodo .Last () sul KeyCollection
lomaxx

Non ho testato il codice, ma il metodo è documentato su [MSDN] [1] forse è un'altra versione del framework? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan

2 anni di ritardo, ma potrebbe aiutare qualcuno ... vedi la mia risposta al post di Juan qui sotto. Last () è un metodo di estensione.
SuperOli,

2

Per espandere il post di Daniels e i suoi commenti sulla chiave, poiché la chiave è comunque incorporata nel valore, è possibile ricorrere all'uso di un KeyValuePair<TKey, TValue> come valore. Il ragionamento principale per questo è che, in generale, la chiave non è necessariamente direttamente derivabile dal valore.

Quindi sarebbe simile a questo:

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

Per usarlo come nell'esempio precedente, devi fare:

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;

2

Un dizionario potrebbe non essere molto intuitivo per l'utilizzo dell'indice come riferimento, ma è possibile eseguire operazioni simili con un array di KeyValuePair :

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


2

Puoi anche utilizzare SortedList e la sua controparte generica. Queste due classi e nella risposta di Andrew Peters menzionate OrderedDictionary sono classi di dizionario in cui è possibile accedere agli elementi per indice (posizione) e per chiave. Come utilizzare queste classi è possibile trovare: Classe SortedList , Classe generica SortedList .


1

UserVoice di Visual Studio fornisce un collegamento all'implementazione OrderedDictionary generica di dotmore.

Ma se hai solo bisogno di ottenere coppie chiave / valore per indice e non hai bisogno di ottenere valori per chiavi, puoi usare un semplice trucco. Dichiarare una classe generica (l'ho chiamata ListArray) come segue:

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

Puoi anche dichiararlo con i costruttori:

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

Ad esempio, leggi alcune coppie chiave / valore da un file e vuoi solo memorizzarle nell'ordine in cui sono state lette in modo da ottenerle in seguito per indice:

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

Come avrai notato, non puoi necessariamente avere solo coppie di chiave / valore in ListArray. Le matrici di elementi possono essere di qualsiasi lunghezza, come nella matrice frastagliata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.