การเข้าถึง Dictionary.Keys Key ผ่านดัชนีตัวเลข


160

ฉันใช้Dictionary<string, int>ที่ซึ่งintเป็นกุญแจสำคัญ

ตอนนี้ฉันต้องเข้าถึงคีย์ที่แทรกล่าสุดภายในพจนานุกรม แต่ฉันไม่รู้ชื่อของมัน ความพยายามที่ชัดเจน:

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

ไม่ทำงานเพราะDictionary.Keysไม่ได้ใช้ [] -indexer

ฉันแค่สงสัยว่ามีคลาสที่คล้ายกันหรือไม่? ฉันคิดถึงการใช้สแต็ค แต่นั่นเก็บเฉพาะสตริง ตอนนี้ฉันสามารถสร้าง struct ของตัวเองแล้วใช้ a Stack<MyStruct>แต่ฉันสงสัยว่ามีทางเลือกอื่นหรือไม่โดยพื้นฐานแล้วพจนานุกรมที่ใช้ [] -indexer on the Keys?


1
จะเกิดอะไรขึ้นถ้าคุณเลือกตัวแปรนั้น
Paul Prewett

คำตอบ:


222

@Falanwe ชี้ให้เห็นในความคิดเห็นการทำสิ่งนี้ไม่ถูกต้อง :

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

คุณไม่ควรขึ้นอยู่กับลำดับของคีย์ในพจนานุกรม หากคุณต้องการสั่งซื้อคุณควรใช้พจนานุกรมที่มีคำสั่งตามที่แนะนำในคำตอบนี้ คำตอบอื่น ๆ ในหน้านี้ก็น่าสนใจเช่นกัน


1
ดูเหมือนว่าจะไม่ทำงานกับHashTableSystem.Collections.ICollection 'ไม่มีคำจำกัดความสำหรับ' ElementAt 'และไม่มีวิธีการขยาย' ElementAt 'ที่ยอมรับอาร์กิวเมนต์แรกของประเภท' System.Collections.ICollection '
v.oddou

คุณสามารถใช้ElementAtOrDefaultเวอร์ชันเพื่อทำงานกับรุ่นที่ไม่มีข้อยกเว้น
TarıkÖzgünGüner

22
มันน่ากลัวที่จะเห็นคำตอบที่ผิดอย่างโจ๋งครึ่มและได้รับการสนับสนุนอย่างมาก มันผิดเพราะDictionary<TKey,TValue>เอกสารระบุว่า "ลำดับของคีย์ในDictionary<TKey, TValue>.KeyCollectionนั้นไม่ได้ระบุไว้" คำสั่งไม่ถูกกำหนดคุณไม่มีทางรู้แน่ชัดว่าอยู่ที่ตำแหน่งสุดท้าย ( mydict.Count -1)
Falanwe

มันน่ากลัว ... แต่เป็นประโยชน์กับฉันเพราะฉันกำลังมองหาการยืนยันความสงสัยของฉันที่คุณไม่สามารถนับได้ในการสั่งซื้อ !!! ขอบคุณ @Falanwe
Charlie

3
สำหรับบางคำสั่งไม่เกี่ยวข้อง - เพียงแค่ความจริงที่ว่าคุณผ่านทุกปุ่ม
Royi Mindel

59

คุณสามารถใช้OrderedDictionary

แสดงชุดของคู่คีย์ / ค่าที่เข้าถึงได้โดยคีย์หรือดัชนี


42
Erhm หลังจาก 19 upvotes ไม่มีใครพูดถึงว่า OrderedDictionary ยังไม่อนุญาตให้รับคีย์ด้วยดัชนี?
Lazlo

1
คุณสามารถเข้าถึงค่าที่มีดัชนีจำนวนเต็มด้วยOrderedDictionaryแต่ไม่สามารถใช้กับSystem.Collections.Generic.SortedDictionary <TKey, TValue>โดยที่ดัชนีต้องเป็น TKey
Maxence

ชื่อ OrderedDictionary เกี่ยวข้องกับฟังก์ชันคอลเลกชันนี้เพื่อรักษาองค์ประกอบในลำดับเดียวกันกับที่เพิ่มเข้ามา ในบางกรณีคำสั่งซื้อมีความหมายเหมือนกับการเรียงลำดับ แต่ไม่ได้อยู่ในชุดสะสมนี้
Sharunas Bielskis

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 เป็นค่าสุดท้ายที่แทรก ไม่ว่าคุณจะตั้งให้เป็นคีย์สุดท้ายที่แทรกไว้หรือคุณต้องการชื่อที่ดีขึ้นสำหรับตัวแปรและคุณสมบัติ
Fantius

6

คุณสามารถทำสิ่งนี้ได้เสมอ:

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

แต่ฉันจะไม่แนะนำที่นี่ ไม่มีการรับประกันว่าคีย์ที่แทรกล่าสุดจะอยู่ที่ส่วนท้ายของอาร์เรย์ การสั่งซื้อ Keys บน MSDNนั้นไม่ได้ระบุและอาจมีการเปลี่ยนแปลง ในการทดสอบสั้น ๆ ของฉันดูเหมือนว่าจะเป็นการเรียงลำดับ แต่คุณควรสร้างสิ่งที่ดีกว่าในการทำบัญชีที่เหมาะสมเช่นสแต็ค - ตามที่คุณแนะนำ (แม้ว่าฉันไม่เห็นความต้องการโครงสร้างตาม ข้อความอื่น ๆ ) - หรือแคชตัวแปรเดียวหากคุณต้องการทราบรหัสล่าสุด


5

ฉันคิดว่าคุณสามารถทำอะไรเช่นนี้ไวยากรณ์อาจผิด havent ใช้ C # ในขณะที่รับรายการสุดท้าย

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

หรือใช้ Max แทน Last เพื่อรับค่าสูงสุดฉันไม่รู้ว่าอันไหนเหมาะกับโค้ดของคุณดีกว่า


2
ฉันจะเพิ่มที่ตั้งแต่ "Last ()" เป็นวิธีการขยายคุณจะต้อง. NET Framework 3.5 และเพื่อเพิ่ม "ใช้ System.Linq" ที่ด้านบนของไฟล์. cs ของคุณ
SuperOli

ลองใช้วิธีนี้เป็นครั้งสุดท้าย (เมื่อใช้ Dist <string, string> ชัด ๆ :-) KeyValuePair <string, string> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor

4

ฉันเห็นด้วยกับส่วนที่สองของคำตอบของแพทริค แม้ว่าในการทดสอบบางอย่างดูเหมือนว่าจะยังคงคำสั่งแทรกเอกสาร (และพฤติกรรมปกติสำหรับพจนานุกรมและแฮช) ระบุการสั่งซื้ออย่างชัดเจนโดยไม่ระบุ

คุณแค่ถามถึงปัญหาตามการสั่งซื้อกุญแจ เพิ่มการทำบัญชีของคุณเอง (ดังที่ Patrick กล่าวไว้ซึ่งเป็นเพียงตัวแปรเดียวสำหรับคีย์เพิ่มล่าสุด) เพื่อให้แน่ใจ นอกจากนี้อย่าถูกล่อลวงโดยวิธีการทั้งหมดเช่น Last และ Max ในพจนานุกรมเนื่องจากวิธีการเหล่านี้อาจเกี่ยวข้องกับเครื่องมือเปรียบเทียบหลัก (ฉันไม่แน่ใจเกี่ยวกับสิ่งนั้น)


4

ในกรณีที่คุณตัดสินใจใช้รหัสอันตรายที่อาจมีการแตกฟังก์ชันส่วนขยายนี้จะดึงคีย์จากการDictionary<K,V>จัดทำดัชนีภายใน (ซึ่งสำหรับโมโนและ. 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);
    }
};

อืมการแก้ไขเพื่อปรับปรุงคำตอบทำให้ได้รับการโหวต ฉันไม่ได้อธิบายอย่างชัดเจนว่าโค้ดนั้นน่าเกลียดน่ากลัวและควรได้รับการพิจารณาอย่างเหมาะสมหรือไม่
Glenn Slayden

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

เกี่ยวกับความคิดเห็นของคุณเกี่ยวกับคีย์โปรดดูคำตอบติดตามของฉัน
takrl

3

วิธีที่คุณพูดคำถามทำให้ฉันเชื่อว่า int ในพจนานุกรมบรรจุ "ตำแหน่ง" ของรายการในพจนานุกรม ตัดสินจากการยืนยันว่าคีย์ไม่ได้ถูกจัดเก็บตามลำดับที่ถูกเพิ่มหากถูกต้องนั่นหมายความว่าคีย์นั้นนับจำนวน (หรือ. จำนวน - 1 หากคุณใช้เลขศูนย์) ควรจะยังคงอยู่ เป็นหมายเลขของคีย์ที่ป้อนล่าสุดเสมอหรือไม่

หากถูกต้องมีเหตุผลใดบ้างที่คุณไม่สามารถใช้พจนานุกรม <int, string> แทนเพื่อให้คุณสามารถใช้ mydict [mydict.Keys.Count] ได้หรือไม่


2

ฉันไม่รู้ว่าจะใช้งานได้หรือไม่เพราะฉันค่อนข้างมั่นใจว่าคีย์ไม่ได้ถูกจัดเก็บตามลำดับที่เพิ่มเข้ามา แต่คุณสามารถส่ง KeysCollection ไปยังรายการแล้วรับคีย์สุดท้ายในรายการ ... แต่มันจะคุ้มค่าที่จะดู

สิ่งเดียวที่ฉันคิดได้ก็คือเก็บกุญแจไว้ในรายการค้นหาและเพิ่มปุ่มลงในรายการก่อนที่คุณจะเพิ่มลงในพจนานุกรม ... มันไม่ได้สวยมากนัก


@ Juan: ไม่มี .Last () วิธีการใน KeyCollection
lomaxx

ฉันไม่ได้ทดสอบโค้ด แต่มีการบันทึกวิธีการใน [MSDN] [1] อาจเป็นรุ่นอื่นที่เป็นกรอบงานหรือไม่ [1]: msdn.microsoft.com/en-us/library/bb908406.aspx
Juan

2 ปีที่ผ่านมา แต่อาจช่วยใครบางคน ... ดูคำตอบของฉันที่โพสต์ของ Juan ด้านล่าง 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;

2

พจนานุกรมอาจไม่ง่ายนักสำหรับการใช้ดัชนีสำหรับการอ้างอิง แต่คุณสามารถมีการดำเนินการที่คล้ายกันกับอาเรย์ของKeyValuePair :

อดีต KeyValuePair<string, string>[] filters;


2

คุณยังสามารถใช้ SortedList และ Generic ทั่วไปได้ ทั้งสองคลาสและใน Andrew Peters คำตอบที่กล่าวถึง OrderedDictionary เป็นคลาสพจนานุกรมที่รายการสามารถเข้าถึงได้โดยดัชนี (ตำแหน่ง) และคีย์ วิธีใช้คลาสเหล่านี้คุณสามารถค้นหาได้: SortedList Class , SortedList ทั่วไปชั้น


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 ของคุณ อาร์เรย์ไอเท็มอาจมีความยาวเท่าใดก็ได้เช่นเดียวกับในอาร์เรย์แบบขรุขระ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.