Zugriff auf einen Dictionary.Keys-Schlüssel über einen numerischen Index

160

Ich benutze ein Dictionary<string, int>wo das inteine Zählung des Schlüssels ist.

Jetzt muss ich auf den zuletzt eingefügten Schlüssel im Wörterbuch zugreifen, aber ich kenne den Namen nicht. Der offensichtliche Versuch:

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

funktioniert nicht, da Dictionary.Keyskein [] -Indexer implementiert ist.

Ich frage mich nur, ob es eine ähnliche Klasse gibt. Ich habe über die Verwendung eines Stacks nachgedacht, aber darin wird nur eine Zeichenfolge gespeichert. Ich könnte jetzt meine eigene Struktur erstellen und dann eine verwenden Stack<MyStruct>, aber ich frage mich, ob es eine andere Alternative gibt, im Wesentlichen ein Wörterbuch, das einen [] -Indexer auf den Schlüsseln implementiert?

Michael Stum
quelle
1
Was passiert, wenn Sie diese Variable boxen?
Paul Prewett

Antworten:

222

Wie @Falanwe in einem Kommentar betont, ist es falsch , so etwas zu tun :

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

Sie sollten nicht von der Reihenfolge der Schlüssel in einem Wörterbuch abhängig sein. Wenn Sie eine Bestellung benötigen, sollten Sie ein OrderedDictionary verwenden , wie in dieser Antwort vorgeschlagen . Die anderen Antworten auf dieser Seite sind ebenfalls interessant.

Vitor Hugo
quelle
1
scheint nicht mit HashTableSystem.Collections.ICollection zu funktionieren 'enthält keine Definition für' ElementAt 'und es konnte keine Erweiterungsmethode' ElementAt 'gefunden werden, die ein erstes Argument vom Typ' System.Collections.ICollection 'akzeptiert
v.oddou
Sie können die ElementAtOrDefaultVersion verwenden, um mit einer außergewöhnlichen Version zu arbeiten.
Tarık Özgün Güner
22
Es ist beängstigend zu sehen, dass eine so offensichtlich falsche Antwort so akzeptiert und positiv bewertet wird. Es ist falsch, weil, wie in der Dictionary<TKey,TValue>Dokumentation angegeben , "Die Reihenfolge der Schlüssel in der Dictionary<TKey, TValue>.KeyCollectionist nicht angegeben." mydict.Count -1
Da
Das ist beängstigend ... aber hilfreich für mich, da ich nach einer Bestätigung meines Verdachts gesucht habe, dass Sie sich nicht auf die Bestellung verlassen können !!! Danke @Falanwe
Charlie
3
Für einige ist die Reihenfolge nicht relevant - nur die Tatsache, dass Sie alle Schlüssel durchgesehen haben.
Royi Mindel
59

Sie können ein OrderedDictionary verwenden .

Stellt eine Sammlung von Schlüssel / Wert-Paaren dar, auf die der Schlüssel oder Index zugreifen kann.

Andrew Peters
quelle
42
Erhm, nach 19 Upvotes hat niemand erwähnt, dass OrderedDictionary es immer noch nicht erlaubt, den Schlüssel per Index zu erhalten?
Lazlo
1
Sie können mit einem OrderedDictionary auf einen Wert mit einem Integer-Index zugreifen , jedoch nicht mit einem System.Collections.Generic.SortedDictionary <TKey, TValue>, bei dem der Index ein TKey
Maxence sein muss.
Der Name des bestellten Wörterbuchs bezieht sich auf diese Auflistungsfunktion, um Elemente in derselben Reihenfolge zu verwalten, in der sie hinzugefügt wurden. In einigen Fällen hat eine Bestellung dieselbe Bedeutung wie eine Sortierung, jedoch nicht in dieser Sammlung.
Sharunas Bielskis
18

Ein Wörterbuch ist eine Hash-Tabelle, Sie haben also keine Ahnung, in welcher Reihenfolge sie eingefügt werden sollen!

Wenn Sie den zuletzt eingefügten Schlüssel wissen möchten, würde ich vorschlagen, das Wörterbuch um einen LastKeyInserted-Wert zu erweitern.

Z.B:

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

}

Sie werden jedoch auf Probleme stoßen, wenn Sie dies verwenden .Remove(), um dies zu überwinden, müssen Sie eine geordnete Liste der eingefügten Schlüssel führen.

Sam
quelle
8

Warum erweitern Sie nicht einfach die Wörterbuchklasse, um eine zuletzt eingefügte Eigenschaft hinzuzufügen? So etwas wie das folgende vielleicht?

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
quelle
2
Sie setzen lastKeyInserted auf den zuletzt eingefügten Wert. Entweder wollten Sie es auf den zuletzt eingefügten Schlüssel setzen, oder Sie benötigen bessere Namen für die Variable und die Eigenschaft.
Fantius
6

Sie können dies immer tun:

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

Aber ich würde es nicht empfehlen. Es gibt keine Garantie dafür, dass sich der zuletzt eingefügte Schlüssel am Ende des Arrays befindet. Die Bestellung von Schlüsseln auf MSDN ist nicht spezifiziert und kann sich ändern. In meinem sehr kurzen Test scheint es in der Reihenfolge des Einfügens zu sein, aber Sie sollten besser wie ein Stapel eine ordnungsgemäße Buchhaltung aufbauen - wie Sie vorschlagen (obwohl ich nicht sehe, dass eine Struktur auf Ihrer Grundlage erforderlich ist andere Anweisungen) - oder einzelner Variablen-Cache, wenn Sie nur den neuesten Schlüssel kennen müssen.

Patrick
quelle
5

Ich denke, Sie können so etwas tun, die Syntax könnte falsch sein, seit einiger Zeit kein C # mehr verwendet, um das letzte Element zu erhalten

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

oder verwenden Sie Max anstelle von Last, um den Maximalwert zu erhalten. Ich weiß nicht, welcher besser zu Ihrem Code passt.

Juan
quelle
2
Ich würde hinzufügen, dass Sie, da "Last ()" eine Erweiterungsmethode ist, .NET Framework 3.5 benötigen und "using System.Linq" oben in Ihrer .cs-Datei hinzufügen müssen.
SuperOli
Versuchen Sie dies zum letzten Mal (wenn Sie offensichtlich einen Dist <string, string> verwenden :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor
4

Ich stimme dem zweiten Teil von Patricks Antwort zu. Auch wenn in einigen Tests die Einfügereihenfolge beibehalten zu werden scheint, wird in der Dokumentation (und im normalen Verhalten für Wörterbücher und Hashes) ausdrücklich angegeben, dass die Reihenfolge nicht angegeben ist.

Sie fragen nur nach Ärger, abhängig von der Reihenfolge der Schlüssel. Fügen Sie Ihre eigene Buchhaltung hinzu (wie Patrick sagte, nur eine einzige Variable für den zuletzt hinzugefügten Schlüssel), um sicherzugehen. Lassen Sie sich auch nicht von allen Methoden wie Last und Max im Wörterbuch verführen, da diese wahrscheinlich in Bezug auf den Schlüsselkomparator stehen (da bin ich mir nicht sicher).

Stephen Pellicer
quelle
4

Wenn Sie sich für die Verwendung von gefährlichem Code entscheiden, der beschädigt werden kann, ruft diese Erweiterungsfunktion einen Schlüssel von a Dictionary<K,V>gemäß seiner internen Indizierung ab (die für Mono und .NET derzeit in derselben Reihenfolge zu sein scheint, wie Sie sie durch Auflisten der KeysEigenschaft erhalten ).

Es ist sehr vorzuziehen, Linq: zu verwenden dict.Keys.ElementAt(i), aber diese Funktion iteriert O (N); Das Folgende ist O (1), jedoch mit einer Beeinträchtigung der Reflexionsleistung.

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
quelle
Hmm, die Bearbeitung zur Verbesserung der Antwort hat eine negative Bewertung erhalten. Habe ich nicht klargestellt, dass der Code (offensichtlich) abscheulich ist und entsprechend berücksichtigt werden sollte?
Glenn Slayden
4

Eine Alternative wäre eine KeyedCollection, wenn der Schlüssel in den Wert eingebettet ist.

Erstellen Sie einfach eine grundlegende Implementierung in einer versiegelten Klasse, die Sie verwenden möchten.

Also zu ersetzen Dictionary<string, int>(was kein sehr gutes Beispiel ist, da es keinen eindeutigen Schlüssel für ein int gibt).

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
quelle
Informationen zu Ihren Kommentaren zum Schlüssel finden Sie in meiner Antwort auf diese Frage.
Takrl
3

Die Art und Weise, wie Sie die Frage formuliert haben, lässt mich glauben, dass das int im Wörterbuch die "Position" des Elements im Wörterbuch enthält. Nach der Behauptung zu urteilen, dass die Schlüssel nicht in der Reihenfolge gespeichert sind, in der sie hinzugefügt wurden, würde dies bedeuten, dass die Schlüssel.Count (oder .Count - 1, wenn Sie nullbasiert verwenden) weiterhin vorhanden sein sollten immer die Nummer des zuletzt eingegebenen Schlüssels sein?

Wenn dies korrekt ist, gibt es einen Grund, warum Sie nicht stattdessen Dictionary <int, string> verwenden können, um mydict [mydict.Keys.Count] zu verwenden?

Jeremy Privett
quelle
2

Ich weiß nicht, ob dies funktionieren würde, da ich ziemlich sicher bin, dass die Schlüssel nicht in der Reihenfolge gespeichert sind, in der sie hinzugefügt wurden, aber Sie könnten die KeysCollection in eine Liste umwandeln und dann den letzten Schlüssel in der Liste erhalten ... aber es wäre einen Blick wert.

Das einzige andere, woran ich denken kann, ist, die Schlüssel in einer Suchliste zu speichern und die Schlüssel zur Liste hinzuzufügen, bevor Sie sie zum Wörterbuch hinzufügen ... es ist nicht schön.

Lomaxx
quelle
@ Juan: Es gibt keine .Last () -Methode auf der KeyCollection
lomaxx
Ich habe den Code nicht getestet, aber die Methode ist auf [MSDN] [1] dokumentiert. Vielleicht ist es eine andere Version des Frameworks? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan
2 Jahre zu spät, aber es könnte jemandem helfen ... siehe meine Antwort auf Juans Beitrag unten. Last () ist eine Erweiterungsmethode.
SuperOli
2

Um Daniels Beitrag und seine Kommentare zum Schlüssel zu erweitern, können Sie auf a KeyValuePair<TKey, TValue>als Wert zurückgreifen, da der Schlüssel ohnehin in den Wert eingebettet ist . Der Hauptgrund dafür ist, dass der Schlüssel im Allgemeinen nicht unbedingt direkt vom Wert abgeleitet werden kann.

Dann würde es so aussehen:

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

Um dies wie im vorherigen Beispiel zu verwenden, gehen Sie wie folgt vor:

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
quelle
2

Ein Wörterbuch ist möglicherweise nicht sehr intuitiv, um den Index als Referenz zu verwenden. Sie können jedoch ähnliche Vorgänge mit einem Array von KeyValuePair ausführen :

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

espaciomore
quelle
2

Sie können auch SortedList und sein generisches Gegenstück verwenden. Diese beiden Klassen und die in Andrew Peters Antwort erwähnte OrderedDictionary sind Wörterbuchklassen, in denen auf Elemente sowohl nach Index (Position) als auch nach Schlüssel zugegriffen werden kann. Verwendung dieser Klassen finden Sie unter: SortedList Class , SortedList Generic Class .

Sharunas Bielskis
quelle
1

UserVoice von Visual Studio enthält einen Link zur generischen OrderedDictionary-Implementierung von dotmore.

Wenn Sie jedoch nur Schlüssel / Wert-Paare nach Index und keine Werte nach Schlüsseln abrufen müssen, können Sie einen einfachen Trick verwenden. Deklarieren Sie eine generische Klasse (ich habe sie ListArray genannt) wie folgt:

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

Sie können es auch mit Konstruktoren deklarieren:

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

Sie lesen beispielsweise einige Schlüssel / Wert-Paare aus einer Datei und möchten sie nur in der Reihenfolge speichern, in der sie gelesen wurden, um sie später per Index abzurufen:

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

Wie Sie vielleicht bemerkt haben, können Sie nicht unbedingt nur Schlüssel / Wert-Paare in Ihrem ListArray haben. Die Elementarrays können beliebig lang sein, wie in einem gezackten Array.

Quicktrick
quelle