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?


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.


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.


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.


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

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.


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.


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).


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

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

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


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.


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

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;


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 .


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.