Доступ до словника. Клавіші ключів через числовий індекс


160

Я використовую a, Dictionary<string, int>де intє підрахунок ключа.

Тепер мені потрібно отримати доступ до останнього вставленого ключа всередині Словника, але я не знаю його назви. Очевидна спроба:

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

не працює, тому Dictionary.Keysщо не реалізує [] -indexer.

Мені просто цікаво, чи є подібний клас? Я думав про використання стека, але це зберігає лише рядок. Тепер я міг би створити власну структуру, а потім використовувати a Stack<MyStruct>, але мені цікаво, чи є інша альтернатива, по суті, Словник, який реалізує [] -індексер на ключах?


1
Що станеться, якщо ви встановите цю змінну?
Пол Преуетт

Відповіді:


222

Як в коментарі вказує @Falanwe, робити щось подібне неправильно :

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

Ви не повинні залежати від порядку клавіш у словнику. Якщо вам потрібно замовити, вам слід скористатися OrdersDictionary , як пропонується у цій відповіді . Інші відповіді на цій сторінці також цікаві.


1
схоже, не працює з HashTableSystem.Collections.ICollection 'не містить визначення для' ElementAt 'і не може бути знайдено метод розширення' ElementAt ', який приймає перший аргумент типу' System.Collections.ICollection '
v.oddou

Ви можете використовувати ElementAtOrDefaultверсію для роботи з виключною версією.
Tarık Özgün Güner

22
Страшно бачити таку неправдиву неправильну відповідь, яку так сприйняли і сприйняли. Це неправильно, оскільки, як йдеться в Dictionary<TKey,TValue>документації, "Порядок ключів у" Dictionary<TKey, TValue>.KeyCollectionне визначений ". Якщо замовлення не визначене, ви не можете точно знати, що знаходиться на останній позиції ( mydict.Count -1)
Фаланве

Це страшно ... але корисно для мене, оскільки я шукав підтвердження моєї підозри, що ви не можете розраховувати на замовлення !!! Спасибо @Falanwe
Чарлі

3
Для деяких замовлення не має значення - лише той факт, що ви пройшли всі ключі.
Royi Mindel

59

Ви можете використовувати OrdersDictionary .

Представляє сукупність пар ключів / значень, які доступні ключем або індексом.


42
Erhm, після 19 оновлень ніхто не згадував, що OrdersDictionary все ще не дозволяє отримати ключ за індексом?
Lazlo

1
Ви можете отримати доступ до значення з цілим індексом за допомогою OrdersDictionary , але не за допомогою System.Collections.Generic.SortedDictionary <TKey, TValue>, де індексом повинен бути TKey
Maxence

Ім'я OrdersDictionary пов'язане з цією функцією колекції для підтримки елементів у тому ж порядку, що вони були додані. У деяких випадках замовлення має те саме значення, що і сорт, але не в цій колекції.
Шарунас Більскіс

18

Словник - це хеш-таблиця, тому ви не маєте уявлення про порядок вставки!

Якщо ви хочете знати останню вставлену клавішу, я б запропонував розширити Словник на значення LastKeyInserted.

Наприклад:

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

}

Однак у вас .Remove()виникнуть проблеми, коли ви будете використовувати це для подолання цього, вам доведеться зберігати впорядкований список вставлених ключів.


8

Чому б вам просто не розширити клас словника, щоб додати його до властивості останнього ключа. Можливо, щось подібне до наступного?

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
Ви встановлюєте lastKeyInserted для останнього вставленого значення. Або ви мали намір встановити його до останнього вставленого ключа, або вам потрібні кращі імена змінної та властивості.
Fantius

6

Ви завжди можете це зробити:

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

Але я б не рекомендував це. Немає гарантії, що останній вставлений ключ буде в кінці масиву. Замовлення ключів на MSDN не визначене та може змінюватися. У моєму дуже короткому тесті це здається в порядку вставки, але вам краще побудувати належну бухгалтерію, як стек - як ви пропонуєте (хоча я не бачу потреби в структурі на основі вашої інші висловлювання) - або кеш єдиної змінної, якщо вам просто потрібно знати останній ключ.


5

Я думаю, що ти можеш зробити щось подібне, синтаксис може бути неправильним, час від часу використовуючи C #, щоб отримати останній елемент

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

або використовувати Макс замість Останнього, щоб отримати максимальне значення, я не знаю, який з них краще відповідає вашому коду.


2
Я додам, що оскільки "Last ()" є методом розширення, вам знадобиться .NET Framework 3.5 і додати "using System.Linq" у верхній частині вашого файлу .cs.
SuperOli

Спробуйте це для останнього (при використанні Dist <string, string> очевидно :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Тім Віндзор

4

Я згоден з другою частиною відповіді Патріка. Навіть якщо в деяких тестах здається, що дотримується порядок вставки, документація (і нормальна поведінка для словників та хесів) прямо заявляє, що впорядкування не визначено.

Ви просто просите неприємностей залежно від замовлення ключів. Додайте власну бухгалтерію (як сказав Патрік, лише одну змінну для останнього доданого ключа), щоб бути впевненим. Крім того, не спокушайтесь на всі методи, такі як Last і Max у словнику, оскільки вони, мабуть, стосуються порівняння ключів (я не впевнений у цьому).


4

У випадку, якщо ви вирішили використовувати небезпечний код, який підлягає злому, ця функція розширення отримає ключ із Dictionary<K,V>відповідно до його внутрішньої індексації (що для Mono та .NET в даний час здається в тому ж порядку, що ви отримаєте, перераховуючи Keysвластивість ).

Набагато краще використовувати Linq:, dict.Keys.ElementAt(i)але ця функція буде повторювати O (N); далі - O (1), але з покаранням ефективності відображення.

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

Гм, редагування для поліпшення відповіді заробило зворотну оцінку. Чи не я зрозумів, що код (очевидно) огидний, і його слід враховувати відповідно?
Гленн Слейден

4

Однією з альтернатив була б KeyedCollection, якщо ключ вбудований у значення.

Просто створіть базову реалізацію у закритому класі для використання.

Тож замінити Dictionary<string, int>(що не дуже вдалий приклад, оскільки не існує чіткого ключа для 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];

Щодо ваших коментарів щодо ключа, дивіться мою відповідь на цю відповідь.
takrl

3

Те, як ви сформулювали запитання, приводить мене до думки, що int у словнику містить "позицію" елемента у словнику. Судячи з твердження, що ключі не зберігаються в тому порядку, який вони додали, якщо це правильно, це означатиме, що ключі. Кількість (або .Count - 1, якщо ви використовуєте нульову основу) все одно повинна завжди буде номер останнього введеного ключа?

Якщо це правильно, чи є якась причина, що ви не можете замість цього використовувати Словник <int, string>, щоб ви могли використовувати mydict [mydict.Keys.Count]?


2

Я не знаю, чи спрацювало б це, бо я впевнений, що ключі не зберігаються у тому порядку, коли вони додані, але ви можете кинути KeysCollection до списку, а потім отримати останній ключ у списку ... але варто було б подивитися.

Єдине, що я можу придумати, - це зберігати ключі у списку пошуку та додавати ключі до списку перед тим, як додати їх до словника ... це не дуже тхо.


@Juan: немає методу .Last () на KeyCollection
lomaxx

Я не перевіряв код, але метод задокументований на [MSDN] [1], можливо, його інша версія рамки? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Хуан

На 2 роки пізніше, але це може комусь допомогти ... дивіться мою відповідь на пост Хуана нижче. Last () - метод розширення.
SuperOli

2

Щоб розгорнути публікацію Daniels та його коментарі стосовно ключа, оскільки ключ все одно вбудований у значення, ви можете вдатися до використання а KeyValuePair<TKey, TValue>. Основне міркування цього полягає в тому, що в цілому ключ не обов'язково безпосередньо виводиться із значення.

Тоді це виглядатиме так:

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

Щоб використовувати це, як у попередньому прикладі, слід зробити:

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

Словник може бути не дуже інтуїтивним для використання індексу для довідки, але ви можете мати подібні операції з масивом KeyValuePair :

колишній KeyValuePair<string, string>[] filters;


2

Ви також можете використовувати SortedList та його загальний аналог. Ці два класи та в згаданій Ендрю Петерс відповіді OrriedDictionary - це словникові класи, в яких елементи можуть бути доступні як за індексом (позицією), так і за ключем. Як користуватися цими класами ви можете знайти: SortedList Class , SortedList Generic Class .


1

UserVoice Visual Studio дає посилання на загальну реалізацію OrdersDictionary від dotmore.

Але якщо вам потрібно отримати пари ключ / значення за індексом і не потрібно отримувати значення за ключами, ви можете скористатися одним простим трюком. Заявіть про якийсь загальний клас (я назвав його ListArray) наступним чином:

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

Ви також можете оголосити це конструкторами:

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

Наприклад, ви читаєте кілька пар ключів / значень з файлу і просто хочете зберегти їх у тому порядку, який вони прочитали, щоб отримати їх пізніше за індексом:

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

Як ви, можливо, помітили, у вашому ListArray не обов'язково можуть бути лише пари ключа / значення. Масиви елементів можуть бути будь-якої довжини, як у нерівному масиві.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.