Доступ к ключу Dictionary.Keys через числовой индекс

160

Я использую Dictionary<string, int>где intэто количество ключей.

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

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

не работает, потому Dictionary.Keysчто не реализует [] -индексер.

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

Майкл Стум
источник
1
Что произойдет, если вы поместите эту переменную в бокс?
Пол Prewett

Ответы:

222

Как отмечает @Falanwe в комментарии, делать что-то подобное неправильно :

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

Вы не должны зависеть от порядка ключей в словаре. Если вам нужно упорядочить, вы должны использовать OrderedDictionary , как предлагается в этом ответе . Другие ответы на этой странице также интересны.

Витор Хьюго
источник
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
Для некоторых порядок не актуален - только то, что вы прошли все ключи.
Рой Миндел
59

Вы можете использовать OrderedDictionary .

Представляет коллекцию пар ключ / значение, которые доступны по ключу или индексу.

Эндрю Питерс
источник
42
Эмм, после 19 голосов никто не упомянул, что OrderedDictionary до сих пор не позволяет получить ключ по индексу?
Лазло
1
Вы можете получить доступ к значению с целочисленным индексом с помощью OrderedDictionary , но не с помощью System.Collections.Generic.SortedDictionary <TKey, TValue>, где индекс должен быть TKey
Maxence
Имя OrderedDictionary связано с этой функцией коллекции, чтобы элементы были сохранены в том же порядке, в котором они были добавлены. В некоторых случаях заказ имеет то же значение, что и сортировка, но не в этой коллекции.
Шарунас Бельскис
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 для последнего вставленного значения. Либо вы хотите установить последний ключ, либо вам нужны более подходящие имена для переменной и свойства.
Фантиус
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();

или используйте Max вместо Last, чтобы получить максимальное значение, я не знаю, какой из них лучше подходит вашему коду.

Juan
источник
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

В случае, если вы решите использовать опасный код, который может быть поврежден, эта функция расширения будет извлекать ключ из a 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];
Дэниел Баллинджер
источник
Что касается ваших комментариев к ключу, см. Мой ответ на этот вопрос.
Такл
3

То, как вы сформулировали вопрос, заставляет меня поверить, что int в Словаре содержит «позицию» элемента в Словаре. Судя по утверждению о том, что ключи хранятся не в том порядке, в котором они были добавлены, если это правильно, это будет означать, что keys.Count (или .Count - 1, если вы используете нули) должны по-прежнему всегда будет номер последнего введенного ключа?

Если это правильно, есть ли причина, по которой вы не можете вместо этого использовать Dictionary <int, string>, чтобы вы могли использовать mydict [mydict.Keys.Count]?

Джереми Приветт
источник
2

Я не знаю, сработает ли это, потому что я почти уверен, что ключи хранятся не в том порядке, в котором они были добавлены, но вы можете привести KeysCollection к списку, а затем получить последний ключ в списке ... но стоило бы посмотреть.

Единственное, о чем я могу подумать, это сохранить ключи в списке поиска и добавить ключи в список, прежде чем добавлять их в словарь ... это не совсем так.

lomaxx
источник
@Juan: в KeyCollection нет метода .Last ()
lomaxx
Я не тестировал код, но метод описан в [MSDN] [1], может быть, это еще одна версия фреймворка? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Хуан,
На 2 года позже, но это может кому-то помочь ... см. Мой ответ на пост Хуана ниже. Last () - это метод расширения.
SuperOli
2

Чтобы подробнее остановиться на публикации Дэниелса и его комментариях относительно ключа, поскольку ключ в любом случае встроен в значение, вы можете прибегнуть к использованию в 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;
takrl
источник
2

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

ех. KeyValuePair<string, string>[] filters;

espaciomore
источник
2

Вы также можете использовать SortedList и его общий аналог. Эти два класса и упомянутый в ответе Эндрю Питерса OrderedDictionary являются словарными классами, в которых элементы могут быть доступны по индексу (позиции), а также по ключу. Как использовать эти классы вы можете найти: SortedList Class , SortedList Generic Class .

Шарунас Бельскис
источник
1

UserVoice Visual Studio дает ссылку на универсальную реализацию OrderedDictionary от 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. Массивы элементов могут быть любой длины, например, в зубчатом массиве.

quicktrick
источник