Mengakses Kamus. Kunci Tombol melalui indeks numerik

160

Saya menggunakan di Dictionary<string, int>mana inthitungan tombol.

Sekarang, saya perlu mengakses Kunci yang dimasukkan terakhir di dalam Kamus, tetapi saya tidak tahu namanya. Upaya nyata:

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

tidak berfungsi, karena Dictionary.Keystidak mengimplementasikan [] -indexer.

Saya hanya ingin tahu apakah ada kelas serupa? Saya berpikir tentang menggunakan Stack, tetapi itu hanya menyimpan string. Saya sekarang dapat membuat struct saya sendiri dan kemudian menggunakan Stack<MyStruct>, tapi saya ingin tahu apakah ada alternatif lain, pada dasarnya Kamus yang mengimplementasikan [] -indexer pada Tombol?

Michael Stum
sumber
1
Apa yang terjadi jika Anda mengotak variabel itu?
Paul Prewett

Jawaban:

222

Seperti @Falanwe tunjukkan dalam komentar, melakukan sesuatu seperti ini tidak benar :

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

Anda tidak harus bergantung pada urutan tombol dalam Kamus. Jika Anda perlu memesan, Anda harus menggunakan OrderedDictionary , seperti yang disarankan dalam jawaban ini . Jawaban lain di halaman ini juga menarik.

Vitor Hugo
sumber
1
tampaknya tidak berfungsi dengan HashTableSystem.Collections.ICollection 'tidak mengandung definisi untuk' ElementAt 'dan tidak ada metode ekstensi' ElementAt 'menerima argumen pertama dari tipe' System.Collections.ICollection 'dapat ditemukan
v.oddou
Anda dapat menggunakan ElementAtOrDefaultversi untuk bekerja dengan versi tanpa pengecualian.
Tarık Özgün Güner
22
Menakutkan melihat jawaban yang salah dan diterima begitu banyak. Ini salah karena, seperti yang dinyatakan dalam Dictionary<TKey,TValue>dokumentasi "Urutan kunci di Dictionary<TKey, TValue>.KeyCollectiontidak ditentukan." Pesanan tidak ditentukan, Anda tidak memiliki cara untuk mengetahui pasti yang ada di posisi terakhir ( mydict.Count -1)
Falanwe
Ini menakutkan ... tetapi membantu saya karena saya mencari konfirmasi kecurigaan saya bahwa Anda tidak dapat mengandalkan pesanan !!! Terima kasih @Falanwe
Charlie
3
Untuk beberapa pesanan tidak relevan - hanya fakta bahwa Anda telah melewati semua kunci.
Royi Mindel
59

Anda dapat menggunakan OrderedDictionary .

Merupakan kumpulan pasangan kunci / nilai yang dapat diakses oleh kunci atau indeks.

Andrew Peters
sumber
42
Erhm, setelah 19 upvotes, tidak ada yang menyebutkan bahwa OrderedDictionary masih tidak mengizinkan untuk mendapatkan kunci berdasarkan indeks?
Lazlo
1
Anda dapat mengakses nilai dengan indeks integer dengan Kamus Ordered , tetapi tidak dengan System.Collections.Generic.SortedDictionary <TKey, TValue> di mana indeks harus berupa TKey
Maks.
Nama OrderedDictionary terkait dengan fungsi koleksi ini untuk mempertahankan elemen dalam urutan yang sama bahwa mereka ditambahkan. Dalam beberapa kasus, pesanan memiliki arti yang sama dengan pengurutan, tetapi tidak dalam koleksi ini.
Sharunas Bielskis
18

Kamus adalah Tabel Hash, jadi Anda tidak tahu urutan penyisipan!

Jika Anda ingin mengetahui kunci yang disisipkan terakhir, saya sarankan memperluas Kamus untuk menyertakan nilai LastKeyInserted.

Misalnya:

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

}

Anda akan mengalami masalah namun ketika Anda menggunakannya .Remove()untuk mengatasinya Anda harus menyimpan daftar kunci yang dimasukkan.

Sam
sumber
8

Mengapa Anda tidak memperluas kelas kamus untuk menambahkan properti yang disisipkan kunci terakhir. Sesuatu seperti yang berikut ini mungkin?

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
sumber
2
Anda mengatur lastKeyInerterted ke nilai terakhir yang dimasukkan. Entah Anda bermaksud mengaturnya ke kunci terakhir yang dimasukkan atau Anda perlu nama yang lebih baik untuk variabel dan properti.
Fantius
6

Anda selalu bisa melakukan ini:

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

Tapi saya tidak akan merekomendasikannya. Tidak ada jaminan bahwa kunci yang dimasukkan terakhir akan berada di akhir array. Pemesanan untuk Kunci pada MSDN tidak ditentukan, dan dapat berubah. Dalam tes saya yang sangat singkat, tampaknya memang dalam urutan penyisipan, tetapi Anda akan lebih baik membangun pembukuan yang tepat seperti tumpukan - seperti yang Anda sarankan (meskipun saya tidak melihat kebutuhan struct berdasarkan pada Anda pernyataan lainnya) - atau cache variabel tunggal jika Anda hanya perlu mengetahui kunci terbaru.

Patrick
sumber
5

Saya pikir Anda dapat melakukan sesuatu seperti ini, sintaks mungkin salah, belum pernah menggunakan C # untuk sementara waktu untuk mendapatkan item terakhir

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

atau gunakan Max daripada Terakhir untuk mendapatkan nilai maksimal, saya tidak tahu mana yang lebih cocok dengan kode Anda.

Juan
sumber
2
Saya akan menambahkan bahwa sejak "Terakhir ()" adalah metode ekstensi, Anda akan memerlukan .NET Framework 3.5 dan menambahkan "menggunakan System.Linq" di bagian atas file .cs Anda.
SuperOli
Coba ini untuk yang terakhir (ketika menggunakan Dist <string, string> jelas :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor
4

Saya setuju dengan bagian kedua dari jawaban Patrick. Bahkan jika dalam beberapa tes tampaknya menjaga urutan penyisipan, dokumentasi (dan perilaku normal untuk kamus dan hash) secara eksplisit menyatakan bahwa pemesanan tidak ditentukan.

Anda hanya meminta masalah tergantung pada urutan kunci. Tambahkan pembukuan Anda sendiri (seperti kata Patrick, hanya satu variabel untuk kunci terakhir yang ditambahkan) untuk memastikan. Juga, jangan tergoda dengan semua metode seperti Last dan Max pada kamus karena itu mungkin terkait dengan komparator kunci (saya tidak yakin tentang itu).

Stephen Pellicer
sumber
4

Jika Anda memutuskan untuk menggunakan kode berbahaya yang dapat rusak, fungsi ekstensi ini akan mengambil kunci dari yang Dictionary<K,V>sesuai dengan pengindeksan internalnya (yang untuk Mono dan .NET saat ini tampaknya berada dalam urutan yang sama seperti yang Anda dapatkan dengan menyebutkan Keysproperti tersebut ).

Jauh lebih baik menggunakan Linq:, dict.Keys.ElementAt(i)tetapi fungsi itu akan beralih O (N); berikut ini adalah O (1) tetapi dengan penalti kinerja refleksi.

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
sumber
Hmm, mengedit untuk meningkatkan jawaban mendapat downvote. Apakah saya tidak menjelaskan bahwa kode itu (jelas) mengerikan, dan harus dipertimbangkan?
Glenn Slayden
4

Salah satu alternatif akan menjadi KeyedCollection jika kunci tersebut tertanam dalam nilai.

Cukup buat implementasi dasar di kelas tertutup untuk digunakan.

Jadi untuk mengganti Dictionary<string, int>(yang bukan contoh yang sangat baik karena tidak ada kunci yang jelas untuk 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
sumber
Mengenai komentar Anda pada kunci, lihat jawaban tindak lanjut saya untuk yang ini.
takrl
3

Cara Anda mengucapkan pertanyaan itu membuat saya percaya bahwa int dalam Kamus berisi "posisi" item pada Kamus. Menilai dari pernyataan bahwa kunci tidak disimpan dalam urutan yang ditambahkan, jika ini benar, itu berarti bahwa kunci.Count (atau .Count - 1, jika Anda menggunakan berbasis nol) harus tetap selalu menjadi nomor kunci yang dimasukkan terakhir?

Jika itu benar, apakah ada alasan mengapa Anda tidak bisa menggunakan Kamus <int, string> sehingga Anda dapat menggunakan mydict [mydict.Keys.Count]?

Jeremy Privett
sumber
2

Saya tidak tahu apakah ini akan berhasil karena saya cukup yakin bahwa kunci tidak disimpan dalam urutan yang ditambahkan, tetapi Anda bisa melemparkan KunciKoleksi ke Daftar dan kemudian mendapatkan kunci terakhir dalam daftar ... tetapi akan layak untuk dilihat.

Satu-satunya hal lain yang dapat saya pikirkan adalah untuk menyimpan kunci dalam daftar pencarian dan menambahkan kunci ke daftar sebelum Anda menambahkannya ke kamus ... itu tidak cantik.

lomaxx
sumber
@Juan: tidak ada metode .Last () pada KeyCollection
lomaxx
Saya tidak menguji kode, tetapi metode ini didokumentasikan pada [MSDN] [1] mungkin versi lain dari framework? [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan
2 tahun terlambat tetapi mungkin membantu seseorang ... lihat balasan saya untuk posting Juan di bawah ini. Terakhir () adalah metode ekstensi.
SuperOli
2

Untuk memperluas posting Daniels dan komentarnya mengenai kunci tersebut, karena kunci tersebut tetap tertanam dalam nilai, Anda dapat menggunakan menggunakan KeyValuePair<TKey, TValue>sebagai nilai. Alasan utama untuk ini adalah bahwa, secara umum, Kunci tidak selalu dapat diturunkan langsung dari nilai.

Maka akan terlihat seperti ini:

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

Untuk menggunakan ini seperti pada contoh sebelumnya, Anda harus:

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

Kamus mungkin tidak terlalu intuitif untuk menggunakan indeks untuk referensi tetapi, Anda dapat memiliki operasi serupa dengan array KeyValuePair :

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

espaciomore
sumber
2

Anda juga dapat menggunakan SortedList dan mitra Generiknya. Dua kelas ini dan dalam jawaban Andrew Peters disebutkan OrderedDictionary adalah kelas kamus di mana item dapat diakses dengan indeks (posisi) serta dengan kunci. Cara menggunakan kelas-kelas ini Anda dapat menemukan: Kelas SortedList , Kelas Generik SortedList .

Sharunas Bielskis
sumber
1

Visual Studio UserVoice memberikan link ke implementasi OrderedDictionary generik oleh dotmore.

Tetapi jika Anda hanya perlu mendapatkan pasangan kunci / nilai berdasarkan indeks dan tidak perlu mendapatkan nilai dengan kunci, Anda dapat menggunakan satu trik sederhana. Deklarasikan beberapa kelas generik (saya menyebutnya ListArray) sebagai berikut:

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

Anda juga dapat mendeklarasikannya dengan konstruktor:

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

Misalnya, Anda membaca beberapa pasangan kunci / nilai dari file dan hanya ingin menyimpannya sesuai urutan pembacaannya agar nanti dengan indeks:

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

Seperti yang mungkin Anda perhatikan, Anda tidak harus hanya memasangkan kunci / nilai di ListArray Anda. Array item mungkin memiliki panjang berapa pun, seperti dalam array bergerigi.

tipuan cepat
sumber