الوصول إلى مفتاح Dictionary.Keys من خلال فهرس رقمي

StackOverflow https://stackoverflow.com/questions/4227

  •  08-06-2019
  •  | 
  •  

سؤال

أنا أستخدم أ Dictionary<string, int> أين ال int هو عدد من المفتاح.

الآن، أحتاج إلى الوصول إلى آخر مفتاح تم إدراجه داخل القاموس، لكني لا أعرف اسمه.المحاولة الواضحة:

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

لا يعمل، لأن Dictionary.Keys لا ينفذ []-indexer.

أنا فقط أتساءل عما إذا كان هناك أي فئة مماثلة؟فكرت في استخدام المكدس، لكنه يخزن سلسلة فقط.يمكنني الآن إنشاء البنية الخاصة بي ثم استخدام ملف Stack<MyStruct>, ، ولكني أتساءل عما إذا كان هناك بديل آخر، وهو في الأساس قاموس يقوم بتنفيذ مفهرس [] على المفاتيح؟

هل كانت مفيدة؟

المحلول

كما يشير @Falanwe في أحد التعليقات، فإن القيام بشيء كهذا هو أمر جيد غير صحيح:

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

أنت لا يجب تعتمد على ترتيب المفاتيح في القاموس.إذا كنت بحاجة إلى الطلب، يجب عليك استخدام القاموس المطلوب, ، كما هو مقترح في هذا إجابة.الإجابات الأخرى في هذه الصفحة مثيرة للاهتمام أيضًا.

نصائح أخرى

يمكنك استخدام القاموس المطلوب.

يمثل مجموعة من أزواج المفاتيح/القيمة التي يمكن الوصول إليها بواسطة المفتاح أو الفهرس.

القاموس عبارة عن جدول تجزئة، لذا ليس لديك أي فكرة عن ترتيب الإدراج!

إذا كنت تريد معرفة آخر مفتاح تم إدراجه، فإنني أقترح توسيع القاموس ليشمل قيمة 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() لذا للتغلب على ذلك، سيتعين عليك الاحتفاظ بقائمة مرتبة من المفاتيح المدرجة.

لماذا لا تقوم فقط بتوسيع فئة القاموس لإضافتها في خاصية المفتاح الأخير المدرجة.شيء مثل ما يلي ربما؟

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

يمكنك دائمًا القيام بذلك:

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

لكنني لا أوصي به.ليس هناك ما يضمن أن آخر مفتاح تم إدراجه سيكون في نهاية المصفوفة.ترتيب المفاتيح على MSDN غير محدد، وقابل للتغيير.في اختباري الموجز للغاية، يبدو أنه يتم ترتيبه بالإدراج، ولكن من الأفضل أن تقوم بالبناء في مسك الدفاتر المناسب مثل المكدس - كما تقترح (على الرغم من أنني لا أرى حاجة إلى بنية تعتمد على عبارات أخرى)--أو ذاكرة تخزين مؤقت متغيرة واحدة إذا كنت بحاجة فقط إلى معرفة أحدث مفتاح.

أعتقد أنه يمكنك فعل شيء من هذا القبيل ، قد يكون بناء الجملة خاطئًا ، ولم يستخدم C# في فترة واحدة للحصول على العنصر الأخير

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

أو استخدم Max بدلاً من Last للحصول على القيمة القصوى، لا أعرف أيهما يناسب الكود الخاص بك بشكل أفضل.

أحد البدائل سيكون أ 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];

وأنا أتفق مع الجزء الثاني من إجابة باتريك.حتى لو بدا في بعض الاختبارات أنه يحتفظ بترتيب الإدراج، فإن الوثائق (والسلوك الطبيعي للقواميس والتجزئة) تنص صراحةً على أن الترتيب غير محدد.

أنت فقط تطلب مشكلة اعتمادًا على ترتيب المفاتيح.أضف مسك الدفاتر الخاص بك (كما قال باتريك، مجرد متغير واحد لآخر مفتاح تمت إضافته) للتأكد.أيضًا، لا تنجذب إلى جميع الأساليب مثل Last وMax في القاموس، حيث من المحتمل أن تكون تلك الأساليب مرتبطة بمقارنة المفاتيح (لست متأكدًا من ذلك).

في حال قررت استخدام تعليمات برمجية خطيرة تكون عرضة للكسر، فإن وظيفة الامتداد هذه ستقوم بجلب المفتاح من ملف 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);
    }
};

الطريقة التي صاغت بها السؤال تقودني إلى الاعتقاد بأن int الموجود في القاموس يحتوي على "موضع" العنصر في القاموس.انطلاقًا من التأكيد على أن المفاتيح لا يتم تخزينها بالترتيب الذي تمت إضافتها به، إذا كان هذا صحيحًا، فهذا يعني أن المفاتيح. Count (أو .Count - 1، إذا كنت تستخدم الصفر) يجب أن تظل كذلك هل يكون دائمًا رقم المفتاح الذي تم إدخاله مؤخرًا؟

إذا كان هذا صحيحًا، فهل هناك أي سبب يمنعك من استخدام Dictionary<int, string> بدلاً من ذلك حتى تتمكن من استخدام mydict[ mydict.Keys.Count]؟

لا أعرف ما إذا كان هذا سينجح لأنني متأكد تمامًا من عدم تخزين المفاتيح بالترتيب الذي تمت إضافتها به، ولكن يمكنك إرسال KeysCollection إلى قائمة ثم الحصول على المفتاح الأخير في القائمة...ولكن الأمر يستحق إلقاء نظرة.

الشيء الآخر الوحيد الذي يمكنني التفكير فيه هو تخزين المفاتيح في قائمة البحث وإضافة المفاتيح إلى القائمة قبل إضافتها إلى القاموس...انها ليست جميلة مع ذلك.

للتوسع في منشور دانيلز وتعليقاته بخصوص المفتاح، نظرًا لأن المفتاح مضمن ضمن القيمة على أي حال، يمكنك اللجوء إلى استخدام 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;

يمكنك أيضًا استخدام SortedList ونظيرتها العامة.هاتان الفئتان وفي إجابة أندرو بيترز المذكورة OrderedDictionary عبارة عن فئات قاموس يمكن من خلالها الوصول إلى العناصر عن طريق الفهرس (الموضع) وكذلك عن طريق المفتاح.كيفية استخدام هذه الفئات يمكنك أن تجد: فئة القائمة المصنفة , SortedList فئة عامة .

قد لا يكون القاموس بديهيًا جدًا لاستخدام الفهرس كمرجع، ولكن يمكنك إجراء عمليات مماثلة مع مجموعة من KeyValuePair:

السابق.KeyValuePair<string, string>[] filters;

الاستوديو المرئي صوت المستخدم يعطي رابطا ل تنفيذ OrderedDictionary عام بواسطة دوتمور.

ولكن إذا كنت تحتاج فقط إلى الحصول على أزواج المفاتيح/القيمة عن طريق الفهرس ولا تحتاج إلى الحصول على القيم عن طريق المفاتيح، فيمكنك استخدام خدعة واحدة بسيطة.قم بتعريف بعض الفئات العامة (أطلق عليها اسم 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 الخاص بك.قد تكون صفائف العنصر بأي طول، كما هو الحال في المصفوفة المتعرجة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top