Dostęp do Dictionary.Keys Key poprzez indeks numeryczny


160

Używam Dictionary<string, int>gdzie intjest liczba klucza.

Teraz muszę uzyskać dostęp do ostatnio włożonego klucza w słowniku, ale nie znam jego nazwy. Oczywista próba:

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

nie działa, ponieważ Dictionary.Keysnie implementuje [] -indexer.

Zastanawiam się tylko, czy istnieje podobna klasa? Myślałem o użyciu stosu, ale przechowuje on tylko ciąg. Mógłbym teraz stworzyć własną strukturę, a następnie użyć a Stack<MyStruct>, ale zastanawiam się, czy istnieje inna alternatywa, zasadniczo Słownik, który implementuje [] -indexer w Keys?


1
Co się stanie, jeśli zapakujesz tę zmienną?
Paul Prewett

Odpowiedzi:


222

Jak @Falanwe wskazuje w komentarzu, zrobienie czegoś takiego jest niepoprawne :

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

Nie powinieneś polegać na kolejności kluczy w słowniku. Jeśli potrzebujesz złożyć zamówienie, powinieneś użyć OrderedDictionary , jak sugeruje ta odpowiedź . Inne odpowiedzi na tej stronie również są interesujące.


1
wydaje się, że nie działa z HashTableSystem.Collections.ICollection ”nie zawiera definicji elementu„ ElementAt ”i nie można znaleźć metody rozszerzającej„ ElementAt ”akceptującej pierwszy argument typu„ System.Collections.ICollection ”
v.oddou

Możesz użyć ElementAtOrDefaultwersji do pracy z wersją bez wyjątków.
Tarık Özgün Güner

22
Przerażające jest widzieć tak rażąco błędną odpowiedź zaakceptowaną i tak bardzo popartą. Jest to błędne, ponieważ zgodnie z Dictionary<TKey,TValue>dokumentacją stwierdza się, że „Kolejność kluczy w nie Dictionary<TKey, TValue>.KeyCollectionjest określona”. Zlecenie jest niezdefiniowane, nie masz pewności, która pozycja jest na ostatniej pozycji ( mydict.Count -1)
Falanwe

To przerażające ... ale pomocne, ponieważ szukałem potwierdzenia mojego podejrzenia, że ​​nie możesz liczyć na zamówienie !!! Dzięki @Falanwe
Charlie

3
Dla niektórych kolejność nie ma znaczenia - wystarczy, że przeszedłeś przez wszystkie klucze.
Royi Mindel

59

Możesz użyć OrderedDictionary .

Reprezentuje kolekcję par klucz / wartość, które są dostępne za pomocą klucza lub indeksu.


42
Eee, po 19 pozytywnych opiniach nikt nie wspomniał, że OrderedDictionary nadal nie pozwala na uzyskanie klucza według indeksu?
Lazlo,

1
Możesz uzyskać dostęp do wartości z indeksem całkowitoliczbowym za pomocą OrderedDictionary , ale nie za pomocą System.Collections.Generic.SortedDictionary <TKey, TValue>, gdzie indeks musi być TKey
Maxence

Nazwa OrderedDictionary jest powiązana z tą funkcją kolekcji, aby zachować elementy w tej samej kolejności, w jakiej zostały dodane. W niektórych przypadkach zamówienie ma takie samo znaczenie jak sortowanie, ale nie w tej kolekcji.
Sharunas Bielskis

18

Słownik to tablica z haszowaniem, więc nie masz pojęcia, w jakiej kolejności wstawiać!

Jeśli chcesz poznać ostatnio wstawiony klucz, sugerowałbym rozszerzenie Słownika o wartość LastKeyInserted.

Na przykład:

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

}

Napotkasz jednak problemy, gdy użyjesz, .Remove()więc aby temu zaradzić, będziesz musiał zachować uporządkowaną listę wstawionych kluczy.


8

Dlaczego po prostu nie rozszerzysz klasy słownika, aby dodać ostatnią wstawioną właściwość klucza. Może coś takiego jak poniższe?

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
Ustawiasz lastKeyInserted na ostatnią wstawioną wartość. Albo chciałeś ustawić go na ostatni wstawiony klucz, albo potrzebujesz lepszych nazw dla zmiennej i właściwości.
Fantius,

6

Zawsze możesz to zrobić:

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

Ale nie polecałbym tego. Nie ma gwarancji, że ostatni wstawiony klucz będzie na końcu tablicy. Sposób zamawiania kluczy w witrynie MSDN nie jest określony i może ulec zmianie. W moim bardzo krótkim teście wydaje się, że jest w kolejności wstawiania, ale lepiej byłoby budować w prawidłowej księgowości jak stos - jak sugerujesz (chociaż nie widzę potrzeby struktury opartej na inne instrukcje) - lub pamięć podręczną pojedynczej zmiennej, jeśli potrzebujesz tylko znać najnowszy klucz.


5

Myślę, że możesz zrobić coś takiego, składnia może być zła, od jakiegoś czasu nie używałeś C # Aby uzyskać ostatni element

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

lub użyj Max zamiast Last, aby uzyskać maksymalną wartość, nie wiem, który z nich lepiej pasuje do twojego kodu.


2
Dodałbym, że skoro „Last ()” jest metodą rozszerzenia, potrzebowałbyś .NET Framework 3.5 i dodania „using System.Linq” na początku pliku .cs.
SuperOli

Spróbuj tego dla last (oczywiście jeśli używasz Dist <string, string> :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor

4

Zgadzam się z drugą częścią odpowiedzi Patryka. Nawet jeśli w niektórych testach wydaje się, że zachowuje kolejność wstawiania, dokumentacja (i normalne zachowanie słowników i skrótów) wyraźnie stwierdza, że ​​kolejność jest nieokreślona.

Po prostu prosisz o kłopoty w zależności od kolejności kluczy. Dodaj własną księgowość (jak powiedział Patrick, tylko pojedyncza zmienna dla ostatniego dodanego klucza), aby mieć pewność. Nie daj się też skusić na wszystkie metody, takie jak Last i Max w słowniku, ponieważ są one prawdopodobnie związane z kluczowym komparatorem (nie jestem tego pewien).


4

W przypadku, gdy zdecydujesz się na użycie niebezpiecznego kodu, który może ulec uszkodzeniu, ta funkcja rozszerzenia pobierze klucz z a Dictionary<K,V>zgodnie z jego wewnętrznym indeksowaniem (które dla Mono i .NET wydaje się obecnie być w tej samej kolejności, jaką otrzymujesz przez wyliczenie Keyswłaściwości ).

O wiele lepiej jest używać Linq:, dict.Keys.ElementAt(i)ale ta funkcja będzie iterować O (N); Poniżej znajduje się O (1), ale z utratą odbicia.

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, edycja poprawiająca odpowiedź przyniosła głos przeciw. Czy nie wyjaśniłem, że kod jest (oczywiście) ohydny i należy go odpowiednio rozważyć?
Glenn Slayden

4

Jedną alternatywą byłby KeyedCollection, jeśli klucz jest osadzony w wartości.

Po prostu utwórz podstawową implementację w zapieczętowanej klasie do użycia.

Więc do zamiany Dictionary<string, int>(co nie jest dobrym przykładem, ponieważ nie ma jasnego klucza dla 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];

Jeśli chodzi o twoje komentarze na temat klucza, zobacz moją dalszą odpowiedź na to.
takrl,

3

Sposób sformułowania pytania prowadzi mnie do wniosku, że int w słowniku zawiera „pozycję” elementu w słowniku. Sądząc po stwierdzeniu, że klucze nie są przechowywane w kolejności, w jakiej zostały dodane, jeśli jest to poprawne, oznaczałoby to, że klucze.Count (lub .Count - 1, jeśli używasz od zera) powinny nadal zawsze jest numerem ostatnio wprowadzonego klucza?

Jeśli to prawda, czy jest jakiś powód, dla którego nie możesz zamiast tego użyć Dictionary <int, string>, aby użyć mydict [mydict.Keys.Count]?


2

Nie wiem, czy to zadziała, ponieważ jestem prawie pewien, że klucze nie są przechowywane w kolejności, w jakiej są dodawane, ale możesz rzucić KeysCollection na Listę, a następnie pobrać ostatni klucz na liście ... ale warto byłoby rzucić okiem.

Jedyną rzeczą, o której przychodzi mi do głowy, jest przechowywanie kluczy na liście wyszukiwania i dodawanie kluczy do listy przed dodaniem ich do słownika ... to nie jest ładne.


@Juan: nie ma metody .Last () w KeyCollection
lomaxx

Nie testowałem kodu, ale metoda jest udokumentowana w [MSDN] [1] może jest to inna wersja frameworka? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan,

2 lata później, ale może to komuś pomóc ... Zobacz moją odpowiedź na post Juana poniżej. Last () jest metodą rozszerzającą.
SuperOli

2

Aby rozwinąć post Danielsa i jego komentarze dotyczące klucza, ponieważ klucz i tak jest osadzony w wartości, możesz skorzystać z KeyValuePair<TKey, TValue> jako wartości. Głównym powodem tego jest to, że generalnie klucz niekoniecznie musi być bezpośrednio wyprowadzony z wartości.

Wtedy wyglądałoby to tak:

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

Aby użyć tego jak w poprzednim przykładzie, wykonaj następujące czynności:

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

Słownik może nie być zbyt intuicyjny, jeśli chodzi o używanie indeksu jako odniesienia, ale podobne operacje można wykonywać z tablicą KeyValuePair :

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


2

Możesz także użyć SortedList i jego odpowiednika Generic. Te dwie klasy i wspomniana w odpowiedzi Andrew Petersa OrderedDictionary to klasy słownikowe, w których dostęp do elementów można uzyskać zarówno za pomocą indeksu (pozycji), jak i klucza. Jak korzystać z tych klas, możesz znaleźć: SortedList Class , SortedList Generic Class .


1

Usługa UserVoice programu Visual Studio udostępnia link do ogólnej implementacji OrderedDictionary przez dotmore.

Ale jeśli potrzebujesz tylko par klucz / wartość według indeksu i nie potrzebujesz pobierać wartości za pomocą kluczy, możesz użyć jednej prostej sztuczki. Zadeklaruj jakąś klasę ogólną (nazwałem ją ListArray) w następujący sposób:

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

Możesz również zadeklarować to za pomocą konstruktorów:

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

Na przykład, czytasz niektóre pary klucz / wartość z pliku i po prostu chcesz przechowywać je w kolejności, w jakiej zostały odczytane, aby później uzyskać je według indeksu:

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

Jak być może zauważyłeś, niekoniecznie możesz mieć tylko pary klucz / wartość w swojej ListArray. Tablice elementów mogą mieć dowolną długość, na przykład tablice postrzępione.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.