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

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

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

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

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

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

Ответов (15)

Решение

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

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

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

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

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

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

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

бывший. KeyValuePair<string, string>[] filters;

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

Я думаю, вы можете сделать что-то подобное, синтаксис может быть неправильным, давно не использовал C# Чтобы получить последний элемент

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

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

Вы всегда могли сделать это:

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

Но я бы не рекомендовал это. Нет гарантии, что последний вставленный ключ будет в конце массива. Порядок ключей в MSDN не указан и может быть изменен. В моем очень кратком тесте кажется, что это действительно в порядке вставки, но вам лучше построить надлежащую бухгалтерию, например, стек - как вы предлагаете (хотя я не вижу необходимости в структуре, основанной на вашем другие операторы) или кеш с одной переменной, если вам просто нужно знать последний ключ.

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

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

Я согласен со второй частью ответа Патрика. Даже если в некоторых тестах кажется, что порядок вставки сохраняется, в документации (и нормальном поведении словарей и хэшей) явно указано, что порядок не указан.

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

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

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

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

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

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

Если вы хотите узнать последний вставленный ключ, я бы предложил расширить словарь, включив в него значение 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() чтобы преодолеть это, вам придется вести упорядоченный список вставленных ключей.

В случае, если вы решите использовать опасный код, который может быть поврежден, эта функция расширения будет извлекать ключ из 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);
    }
};

Альтернативой может быть 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];

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