Acessando uma chave Dictionary.Keys através de um índice numérico

160

Estou usando um Dictionary<string, int>onde o inté uma contagem da chave.

Agora, preciso acessar a última chave inserida dentro do dicionário, mas não sei o nome dela. A tentativa óbvia:

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

não funciona, porque Dictionary.Keysnão implementa um índice [].

Eu só me pergunto se existe alguma classe semelhante? Pensei em usar uma pilha, mas isso apenas armazena uma string. Agora eu poderia criar minha própria estrutura e, em seguida, usar a Stack<MyStruct>, mas gostaria de saber se existe outra alternativa, essencialmente um dicionário que implemente um indexador [] nas chaves?

Michael Stum
fonte
1
O que acontece se você encaixar essa variável?
Paul Prewett 29/07

Respostas:

222

Como o @Falanwe aponta em um comentário, fazer algo assim está incorreto :

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

Você não deve depender da ordem das chaves em um dicionário. Se você precisar fazer um pedido, use um OrderedDictionary , conforme sugerido nesta resposta . As outras respostas nesta página também são interessantes.

Vitor Hugo
fonte
1
parece não funcionar com HashTableSystem.Collections.ICollection 'não contém uma definição para' ElementAt 'e nenhum método de extensão' ElementAt 'que aceita um primeiro argumento do tipo' System.Collections.ICollection 'pode ser encontrado
v.oddou
Você pode usar a ElementAtOrDefaultversão para trabalhar com a versão sem exceção.
Tarık Özgün Güner
22
É assustador ver uma resposta tão flagrantemente errada aceita e votada tanto assim. Está errado porque, como a Dictionary<TKey,TValue>documentação declara "A ordem das chaves no Dictionary<TKey, TValue>.KeyCollectionnão é especificada". A fim de ser indefinido, você não tem nenhuma maneira de saber com certeza que está na última posição ( mydict.Count -1)
Falanwe
Isso é assustador ... mas útil para mim, porque eu estava procurando por confirmação da minha suspeita de que você não pode contar com o pedido !!! Obrigado @Falanwe
Charlie
3
Para alguns, o pedido não é relevante - apenas o fato de você ter passado por todas as chaves.
Royi Mindel 07/12/16
59

Você pode usar um OrderedDictionary .

Representa uma coleção de pares de chave / valor que são acessíveis pela chave ou pelo índice.

Andrew Peters
fonte
42
Erhm, depois de 19 votos positivos, ninguém mencionou que o OrderedDictionary ainda não permite obter a chave pelo índice?
Lazlo
1
Você pode acessar um valor com um índice inteiro com um OrderedDictionary , mas não com um System.Collections.Generic.SortedDictionary <TKey, TValue>, em que o índice precisa ser um TKey
Maxence
O nome OrderedDictionary está relacionado a esta função de coleção para manter os elementos na mesma ordem em que foram adicionados. Em alguns casos, um pedido tem o mesmo significado que uma classificação, mas não nesta coleção.
Sharunas Bielskis
18

Um dicionário é uma tabela de hash; portanto, você não tem idéia da ordem de inserção!

Se você quiser saber a última chave inserida, sugiro que estenda o Dicionário para incluir um valor LastKeyInserted.

Por exemplo:

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

}

Você terá problemas, no entanto, quando usar .Remove()isso para superar isso, precisará manter uma lista ordenada das chaves inseridas.

Sam
fonte
8

Por que você apenas não estende a classe do dicionário para adicionar uma última propriedade inserida na chave. Algo como o seguinte, talvez?

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
fonte
2
Você está configurando lastKeyInserted para o último valor inserido. Você pretendia configurá-lo para a última chave inserida ou precisa de nomes melhores para a variável e a propriedade.
Fantius
6

Você sempre pode fazer isso:

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

Mas eu não recomendaria. Não há garantia de que a última chave inserida esteja no final da matriz. A encomenda de chaves no MSDN não é especificada e está sujeita a alterações. Em meu teste muito breve, parece estar em ordem de inserção, mas é melhor você criar uma contabilidade apropriada como uma pilha - como você sugere (embora eu não veja a necessidade de uma estrutura com base no seu outras instruções) - ou cache de variável única, se você precisar apenas conhecer a chave mais recente.

Patrick
fonte
5

Eu acho que você pode fazer algo assim, a sintaxe pode estar errada, não usa C # há algum tempo Para obter o último item

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

ou use Max em vez de Last para obter o valor máximo, não sei qual deles se encaixa melhor no seu código.

Juan
fonte
2
Eu acrescentaria que, como "Last ()" é um método de extensão, você precisaria do .NET Framework 3.5 e adicionaria "using System.Linq" na parte superior do arquivo .cs.
SuperOli 5/11
Tente isso por último (ao usar um Dist <string, string> obviamente :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor
4

Eu concordo com a segunda parte da resposta de Patrick. Mesmo que em alguns testes pareça manter a ordem de inserção, a documentação (e o comportamento normal de dicionários e hashes) declara explicitamente que a ordem não é especificada.

Você está apenas pedindo problemas, dependendo da ordem das chaves. Adicione sua própria contabilidade (como Patrick disse, apenas uma única variável para a última chave adicionada) para ter certeza. Além disso, não fique tentado com todos os métodos como Last e Max no dicionário, pois provavelmente estão em relação ao comparador de chaves (não tenho certeza disso).

Stephen Pellicer
fonte
4

Caso você decida usar um código perigoso que esteja sujeito a quebra, essa função de extensão buscará uma chave de Dictionary<K,V>acordo com a indexação interna (que atualmente para Mono e .NET parece estar na mesma ordem que você, enumerando a Keyspropriedade )

É muito preferível usar o Linq:, dict.Keys.ElementAt(i)mas essa função irá iterar O (N); o seguinte é O (1), mas com uma penalidade no desempenho da reflexão.

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
fonte
Hmm, editar para melhorar a resposta ganhou um voto negativo. Não deixei claro que o código é (obviamente) hediondo e deve ser considerado em conformidade?
Glenn Slayden
4

Uma alternativa seria um KeyedCollection se a chave estiver incorporada no valor.

Basta criar uma implementação básica em uma classe selada para usar.

Portanto, para substituir Dictionary<string, int>(o que não é um exemplo muito bom, pois não há uma chave clara para um 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];
Daniel Ballinger
fonte
Em relação aos seus comentários sobre a chave, veja minha resposta a este.
22411 takrl
3

A maneira como você formulou a pergunta me leva a acreditar que o int no Dicionário contém a "posição" do item no Dicionário. A julgar pela afirmação de que as chaves não são armazenadas na ordem em que foram adicionadas, se isso estiver correto, isso significaria que as chaves. sempre ser o número da última chave digitada?

Se estiver correto, existe algum motivo para você não usar o Dictionary <int, string> para poder usar mydict [mydict.Keys.Count]?

Jeremy Privett
fonte
2

Não sei se isso funcionaria porque tenho certeza de que as chaves não estão armazenadas na ordem em que foram adicionadas, mas você pode converter o KeysCollection em uma Lista e obter a última chave na lista ... mas valeria a pena dar uma olhada.

A única outra coisa em que consigo pensar é armazenar as chaves em uma lista de pesquisa e adicioná-las à lista antes de adicioná-las ao dicionário ... não é bem assim.

lomaxx
fonte
@Juan: não existe um método .last () no KeyCollection
lomaxx
Eu não testei o código, mas o método está documentado no [MSDN] [1] talvez seja outra versão do framework? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan
2 anos atrasado, mas pode ajudar alguém ... veja minha resposta ao post de Juan abaixo. Last () é um método de extensão.
SuperOli 5/11/10
2

Para expandir a postagem de Daniels e seus comentários sobre a chave, já que a chave está incorporada ao valor de qualquer maneira, você pode usar a KeyValuePair<TKey, TValue>como valor. O principal raciocínio para isso é que, em geral, a Chave não é necessariamente derivada diretamente do valor.

Então ficaria assim:

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

Para usar isso como no exemplo anterior, você faria:

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

Um dicionário pode não ser muito intuitivo para usar o índice como referência, mas você pode ter operações semelhantes com uma matriz de KeyValuePair :

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

espaciomore
fonte
2

Você também pode usar SortedList e seu equivalente genérico. Essas duas classes e na resposta de Andrew Peters mencionada OrderedDictionary são classes de dicionário nas quais os itens podem ser acessados ​​por índice (posição) e por chave. Como usar essas classes, você pode encontrar: Classe SortedList , Classe Genérica SortedList .

Sharunas Bielskis
fonte
1

O UserVoice do Visual Studio fornece um link para a implementação genérica do OrderedDictionary por dotmore.

Mas se você precisar obter apenas pares de chave / valor por índice e não precisar obter valores por chave, use um truque simples. Declare alguma classe genérica (eu a chamei de ListArray) da seguinte maneira:

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

Você também pode declarar isso com construtores:

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

Por exemplo, você lê alguns pares de chave / valor de um arquivo e deseja armazená-los na ordem em que foram lidos, para obtê-los posteriormente pelo índice:

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

Como você deve ter notado, você pode não ter necessariamente apenas pares de chave / valor no seu ListArray. As matrizes de itens podem ter qualquer comprimento, como na matriz irregular.

quicktrick
fonte