تعرف إلى الصف Hashtable

يوجد ضمن فضاء الأسماء System.Collections صفوف القواميس التي تدل من اسمها أنها مثل القاموس تقوم بتخزين قيمة تسمى “مفتاح” وقيمة تسمى “قيمة المفتاح”، وبالتالي تكون الافادة في عملية البحث عن القيمة المقابلة لمفتاح ما، من هذه الصفوف الصف Hashtable الذي يمكن استخدامه كما يلي:

Hashtable emailLookup = new Hashtable();
emailLookup.Add(“Abdulkarim2006@hotmail.com”, “Kanaan, Abdulkarim”);
emailLookup[“Abdulkarim.Kanaan@gmail.com”] = “Kanaan, Abdulkarim”;

طبعا كما لاحظنا هناك طريقتين لإدخال البيانات إما باستخدام التابع Add أو باستخدام الفهرسة طبعا ولكن يجدر القول إلى أن هذه الأخيرة ستكون عملية إضافة في حال أن المفتاح الذي تمت إضافته غير موجود أما في حال وجوده مسبقا فإنه سيؤدي إلى عملية تعديل، أي في حال الايميل Abdulkarim.Kanaan@gmail.com موجود مسبقا فإن السطر:

emailLookup[“Abdulkarim.Kanaan@gmail.com”] = “Kanaan, Abdulkarim”;

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

Console.WriteLine(emailLookup[“Abdulkarim.Kanaan@gmail.com”]);

ولكن في حال أننا لا نعرف المفتاح للقيمة التي نبحث عنها على المعلومات المخزنة داخل الكائن emailLookup من الصف Hashtable دعنا نكتب:

foreach (object obj in emailLookup)
{
    Console.WriteLine(obj);
}

ولكن ياللمفاجأة ما هذه النواتج؟:

System.Collections.DictionaryEntry
System.Collections.DictionaryEntry

 ما الذي حدث؟ الذي حدث أنك حصلت على كائن من الصف DictionaryEntry وليس مفتاح أو قيمة للمفتاح، حتى يتم العمل بشكل سليم عليك أن تنشئ كائن من الصف DictioaryEntry كما في المثال التالي:

foreach (DictionaryEntry entry in emailLookup)
{
    Console.WriteLine(entry.Value);
}

 

جميع صفوف القواميس تدعم الواجهة IDictionary المشتقة من الواجهة ICollection، أهم الخصائص والاجراءات للواجهة IDictionary مبينة في الجدوليت التاليين:
اسم الخاصية               الوصف
IsFixedSize           هذه القيمة تشير فيما إذا كان بالامكان تغير سعة المجمع.
IsReadOnly           تشير هذه القيمة إلى أنه إذا كان بالإمكان تغير قيم المجمع.
Keys                     يعيد كائن من الواجهة ICollection يحتوي على قائمة المفاتيح المخزنة داخل المجمع.
Values                  يعيد كائن من الواجهة ICollection يحتوي على قائمة قيم المفاتيح المخزنة داخل المجمع.

اسم الاجراء                الوصف
Add                      إضافة زوج (مفتاح\قيمة) إلى المجمع.
Clear                    حذف كافة عناصر المجمع.
Contains              التحقق فبما إذا كان مفتاح محدد موجود داخل المجمع.
GetEnumerator     يعيعد كائن من الواجهة IDictionaryEnumerator، هذا الاجراء مختلف عن ذلك الذي يعيد كائن من الواجهة IEnumerable
Remove                يحذف عنصر من المجمع وذلك من خلال مفتاح محدد.

أو يمكن الحصول على القيم المخزنة داخل مجمع كما يلي:

 

foreach (object obj in emailLookup.Values)
{
    Console.WriteLine(obj);
}

بالإضافة إلى ذلك فإن الصف Hashtable يحوي عل تابعين من أجل معرفة فيما إذا كان فتاح أو قيمة مفتاح موجودة داخل المجمع، الجول التالي يوضحهما:
اسم الاجراء                      الوصف
ContainsKey               يتحقق فيما إذا كان المجمع يحتوي على مفتاح محدد.
ContainsValue           يتحقق فيما إذا كان المجمع يحتوي على قيمة مفتاح محدد.

فهم عملية المساواة (التساوي) Equality:
يستخدم الصف Hashtable قيمة من نمط العدد الصحيح تقابل المفتاح، يستخدم هذه القيمة من أجل تسريع عمليات البحث عن مفتاح محدد، حيث أن كل صف في .NET مشتق من الصف Object الذي يدعم الاجراء GetHash الذي يعيد بدوره قيمة عددية صحيحة فريدة تعرف الكائن، في الصف Hashtable يتم تخزين قيمة فريدة للمفاتيح المخزنة في نفس الكائن، فعندما نقوم بمحاولة تخزين مفتاح مرتين (هذه الكلمة من ذهب: كيف يعرف أن هذا المفتاح مكرر؟؟ تابع لتعرف السبب) فإن المرة الثانية سوف تعتبر استدعاء لمفتاح الأول من أجل تعديل القيمة المقابلة له، على سبيل المثال:

 

Hashtable duplicates = new Hashtable();

duplicates[“First”] = “1st”;
duplicates[“First”] = “the first”;

Console.WriteLine(duplicates.Count); \\1

بما أن كلا المفتاحين متساويين في القيمة فإنه يعتبر تعديل للقيمة المقابلة للمفتاح الأول، في الحقيقة قد تم ذلك باستدعاء الاجراء GetHashCode الموجود في الصف String وبالرغم من أن كلا منهما (المفتاحين) كائن مستقل إلا أنه تم التعرف عليها على أنهما كائن واحد.
حتى نفهم الأمر الأكثر دعنا ننشىء صف جديد ونسميه Fish كما يلي:

public class Fish
{
    string name;

    public Fish(string theName)
    {
        name = theName;
    }
}

والان إذا قمنا بإنشاء كائنين من هذا الصف الجديد وجعلنا الخاصية name تأخذ نفس القيمة، دعنا نرى ماذا سوف يعتبرهما الكائن من الصف Hashtabel هل هما متساويين أم لا؟

 

Hashtable duplicates = new Hashtable();

Fish key1 = new Fish(“Herring”);
Fish key2 = new Fish(“Herring”);

duplicates[key1] = “Hello”;
duplicates[key2] = “Hello”;

Console.WriteLine(duplicates.Count); // 2

 

لقد اعتبر الكائن duplicates كلا من الكائنين key1, key2 كائنين غير متساويين لأن التابع GetHashCode قد أنشىء قيمتين مختلفتين لكل منهما، حتى نعالج هذا الأمر يجب أن نتجاوز المنهج GetHashCode من الصف Object ونعرفه نحن بأنفسنا كما يلي:

public override int GetHashCode()
{
    return name.GetHashCode();

والان عندما يتم استدعاء المنهح GetHashCode فإنه سيعيد نفس القيمة وذلك في حال قيمة name متساوية، ولكن مع ذلك لن نكون قد حللنا المشكلة والسبب في ذلك أنه يوجد تابع آخر في الصف Object يدعى Equals فهو يعيد القيمة false إذا كان كلا الكائنين من نسختين مختلفتين (أي لا يحملان نفس العنوان اللذان يشيران له في الذاكرة) ولحل هذه المشكلة نقوم بتجاوز المنهج Equals كما يلي:

public override bool Equals(object obj)
{
    Fish otherFish = obj as Fish;

    if (otherFish == null)
        return false;

    return otherFish.name == this.name;
}

الان أعد تشغيل البرنامج ولا حظ الفرق.

استخدام الواجهة IEqualityComparer: تحتوي هذه الواجهة على تابعين اثنين الأول: GetHashCode والثاني Equals سوف نقوم بالاستفادة من هذه الواجهة فيما يلي لنعد إلى المثال:

Hashtable duplicates = new Hashtable();

duplicates[“First”] = “1st”;
duplicates[“First”] = “the first”;

Console.WriteLine(duplicates.Count); \\1

ولنقم بتغير قيمة أحد المفاتيح من First إلى first وأعد التشغيل ستلاحظ أن الكائن من الصف Hashtable قد إعتبرهما كائنين مختلفين، والمطلوب الآن إلغاء التحسس لحالة الأحرف، كيف سنقوم بعمل ذلك؟؟ لاحظ إن الباني للصف Hashtable يقبل كائن من نوع IEqualityComparer سوف تستفيد منها بإنشاء صف يرث من هذه الواجهة ثم تجهيز عملية المقارنة كما نريد:

public class InsensitiveComparer : IEqualityComparer
{
CaseInsensitiveComparer _comparer = new CaseInsensitiveComparer();

    #region IEqualityComparer Members

    public int GetHashCode(object obj)
    {
        return obj.ToString().ToLowerInvariant().GetHashCode();
    }

    public new bool Equals(object x, object y)
    {
        if (_comparer.Compare(x, y) == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    #endregion
}

أعد كتابة الكود السابق كما يلي:

Hashtable duplicates = new Hashtable(new InsensitiveComparer());

duplicates[“First”] = “1st”;
duplicates[“first”] = “the first”;

Console.WriteLine(duplicates.Count); // 1

ولاحظ الناتج!!!.

بقي ذكر شيء واحد أن الصف Hashtable يقوم بترتيب العناصر المدخلة تلقائيا تبعا لقيمة Hash للمفاتيح المدخلة، المثال التالي يوضح ذلك:

Hashtable ht = new Hashtable();
ht.Add(0, “Zero”);
ht.Add(2, “Two”);
ht.Add(1, “One”);

foreach (DictionaryEntry de in ht)
{
    Console.WriteLine(“Key:{0}, Value:{1}”, de.Key, de.Value);
}

الناتج سيكون:

Key:2, Value:Two
Key:1, Value:One
Key:0, Value:Zero

يمكن تحميل مثال الدرس من الرابط التالي:

 

 

2 thoughts on “تعرف إلى الصف Hashtable

  1. بارك الله فيك على هذه المقالة الرائعه

    هذا النوع من الفئات Classes سهل العمل كثيرا وجعل العمل اسهل جدا .. ولا يعي معنى هذا الكلام كثيرا الا من يبرمج على اسس كائنيه Object Oriented Programming .

    اتحفنا

أضف تعليقاً

إملأ الحقول أدناه بالمعلومات المناسبة أو إضغط على إحدى الأيقونات لتسجيل الدخول:

WordPress.com Logo

أنت تعلق بإستخدام حساب WordPress.com. تسجيل خروج   / تغيير )

صورة تويتر

أنت تعلق بإستخدام حساب Twitter. تسجيل خروج   / تغيير )

Facebook photo

أنت تعلق بإستخدام حساب Facebook. تسجيل خروج   / تغيير )

Google+ photo

أنت تعلق بإستخدام حساب Google+. تسجيل خروج   / تغيير )

Connecting to %s