اذهب إلى المحتوى

جداول التقطيع Hash tables في روبي


سارة طه

جدول التّقطيع Hash هي بنية بيانات Data Structure مشابهة إلى حد كبير للمصفوفات التي تحدّثنا عنها في الدّرس السّابق (حتّى أنّ جداول التّقطيع تسمّى في بعض اللُّغات الأخرى Associative Arrays). يندرج الاثنان تحت ما يسمّى بالمجموعات Collections. يتيح لنا النوعان حفظ بيانات مختلفة مرّة واحدة دون الحاجة إلى استخدام الكثير من المتغيّرات لحفظها. إلا أنّ جداول التّقطيع تختلف عن المصفوفات بعض الشيء. فمثلاً يمكننا الإشارة إلى عنصر في المصفوفة عن طريق مكان هذا العنصر أو ما يسمّى بالدليل Index، أمّا بالنسبة لجدول التّقطيع فنستخدم معرّفًا Identifier للإشارة إلى العنصر الذي نريد الوصول إليه.

ruby-hash-tables.png

يمكن اعتبار جداول التّقطيع على أنّها قوائم مكوّنة من أزواج مفاتيح Key وقيمة Value. يمكن للمعرّف أو المفتاح أن يكون أي نوع من أنواع بيانات روبي التي تعرّفنا عليها في الدروس السّابقة. ولكن أكثر الأنواع المستخدمة شيوعًا هو السلاسل، الأرقام و الرموز Symbols (هذا الأخير هو أكثرها شيوعًا على الإطلاق). سنتعرّف في هذا الدّرس على كيفيّة إنشاء والتّعامل مع جداول التّقطيع.

إنشاء جدول التقطيع

لنبدأ العمل مع جداول التّقطيع الآن، ابدأ جلسة روبي جديدة في سطر أوامر روبي التفاعليّ عن طريق كتابة أمر irb في الطرفيّة.

عمليّة إنشاء جدول التّقطيع تشبه نوعًا ما عمليّة إنشاء مصفوفة. هناك طرق مختلفة لإنشاء جدول تقطيع. يمكن تهيئة Instantiate جدول تقطيع باستخدام الحاضنات Curly Braces أو تهيئة نسخة من صنف Hash. 

لنتعرّف أوّلاً على الطريقة الأولى. وهي عبارة عن إنشاء جدول تقطيع فارغ ومن ثمّ إضافة عناصر إليه فيما بعد. فمثلاً إذا أردنا إنشاء جدول تقطيع فارغ وتعيينه إلى متغيّر اسمه myHash نفعل ذلك كالتّالي:

myHash = Hash.new

تأكّد من كتابة Hash بحرف كبير. 

هناك طريقة أقصر من ذلك أيضًا وذلك بتعيين حاضنات فارغة للمتغيّر هكذا:

myHash = {}

بعد الضغط على Enter في الحالتين ستجد أن روبي قد قامت بإرجاع حاضنات فارغة معلنةً عن إنشاء جدول تقطيع فارغ. ولكن ماذا سنفعل بجدول تقطيع فارغ على كل حال؟ جدول تقطيع فارغ ليس بالشيء المفيد على الإطلاق. 

الآن لو أردنا إنشاء جدول تقطيع باستخدام الحاضنات وتعيينه إلى متغيّر اسمه marge ثم بداخل جدول التّقطيع قمنا بإضافة العناصر المتكوّنة من مفتاح وقيمة. نفعل ذلك هكذا:

marge = { :name => "Marge Simpson" }

ماذا فعلنا هنا؟ أنشأنا متغيّرًا باسم marge وقمنا بتعيين قيمة جدول التّقطيع الذي أنشأناها في الطرف الأيمن لذلك المتغيّر. وضعنا بجدول التّقطيع عنصرًا واحدًا مكوّنًا من المفتاح name: والذي هو من نوع Symbol (عرّفنا النّوع عن طريق النقطتين Colon الموجودتان قبله) والقيمة التي هي عبارة عن سلسلة، قمنا بتعيين القيمة إلى المفتاح عن طريقة ما يسمّى بسهم جدول التّقطيع Hash Rocket (<=). يعدّ استخدام الرموز (ٍSymbol) كمفاتيح في جداول التّقطيع مثلما فعلنا في المثال السّابق أمرًا شائعًا جدًا في روبي. قد تتساءل عن السبب؟ السبب الرئيسي هو أنّ الرموز تستخدم الذّاكرة بطريقة أفضل وأكثر إفادة بكثير من السلاسل مثلاً. 

هناك طريقة أخرى مختصرة لإنشاء جداول التّقطيع ولتوضيح الطريقة سنقوم بإنشاء جدول تقطيع جديد ونعيّنه إلى المتغيّر homer كالتّالي:

homer = { name: "Homer Simpson" }

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

{:name => "Homer Simpson"}

يمكننا إنشاء جدول تقطيع يحتوي على أكثر من زوج مفتاح/قيمة. مثلاً لنعد إنشاء جدول التّقطيع homer من جديد هكذا:

homer = { name: "Homer Simpson", job: "Nuclear Safety Inspector", children: ["Bart", "Lisa", "Maggie"] }

كما تلاحظ يمكننا استخدام أنواع مختلفة من الكائنات كعناصر في جدول التّقطيع . لسنا مضطرّين لاستخدام السلاسل فقط. فدلينا في المثال السّابق قيم العنصرين name و job من نوع سلسلة والعنصر الثالث الذي يحتوي على مفتاح children قيمته من نوع مصفوفة مكوّنة من ثلاثة عناصر.

التعامل مع جداول التقطيع

إذا أردنا التحقّق إذا كان هناك مفتاح معيّن موجود في جدول التّقطيع أم لا يمكننا فعل ذلك باستخدام دالّة include. فمثلاً إذا أردنا معرفة إذا كان لدى homer وظيفة يمكننا التحقّق كالتّالي:

homer.include?(:job)

إذًا فقد استخدمنا اسم جدول التّقطيع أو المتغيّر الذي قمنا بتعيين جدول تقطيع له، ثم لدينا استدعاء دالّة include، وحيث كما ذكرنا في الدروس السّابقة أنّ include دالّة منطقيّة Boolean Method والدوال المنطقيّة تنتهي بعلامة استفهام لأن ناتج استدعائها true أو false (باختصار هي كأن تسأل مثلاً، هل لهومر وظيفة؟ لن تجد إطلاقًا إجابة غير نعم أو لا). إذًا ناتج هذا الأمر السّابق سيكون true إذا وجدت روبي قيمة للمفتاح job أو false إذا لم تجد. وبالنّظر إلى جدول التّقطيع التي لدينا فنتوقّع إجابة روبي بـ true حيث أنّه موجود وظيفة (المفتاح job) لـ homer بالفعل في جدول التّقطيع . 

إذًا نحن نعرف أنّ العنصر موجود، الآن نريد معرفة كيف يمكننا استخدام هذه القيمة (سواء لطباعتها على الشّاشة أو لو استخدامها في عمليّات أخرى). للوصول إلى القيمة الفعليّة بدلاً من التحقّق من وجودها فقط ولمعرفة أن هذه القيمة خاصّة بالمفتاح المحدّد كل ما نفعله هو استخدام المفتاح بطريقة مشابهة جدًا لتلك التي استخدمناها مع الدليل Index في المصفوفات. إذًا إذا أردنا معرفة وظيفة homer يمكننا فعل ذلك كالتّالي:

homer[:job]

إذًا كلّ ما فعلناه أعلاه هو كتابة اسم جدول التّقطيع واسم المفتاح بين قوسين مربّعين. بعد تنفيذ الأمر سترجع لك روبي الوظيفة. 

لكن ماذا لو استدعينا مفتاحًا ليس موجودًا في جدول التّقطيع أصلاً؟ مثلاً لو أردنا معرفة عمر homer باستخدام مفتاح age:. لم نقم بحفظ قيمة كهذه في جدول التّقطيع لذلك إذا استدعيناها فلن ترجع روبي شيئًا غير nil. ولكن ماذا تعني nil؟ nil هو كائن في روبي يمثّل اللاشيء إذا أردت تعريفه كذلك. متى أخفقت دالّة بإرجاع قيمة مفيدة أو ذات معنى فإنّها تقوم بإرجاع nil. 

إذا أردنا معرفة عدد القيم الموجودة في جدول تقطيع يمكننا استخدام دالّة length. جرّب تنفيذ الأمر التالي وستحصل على طول جدول التّقطيع الخاصّة بك:

homer.length

قد تحتاج إلى إضافة قيم جديدة إلى جدول التّقطيع (فارغة كانت أو تحتوي على عناصر) بعد إنشائها، يمكنك فعل ذلك عن طريقة كتابة اسم المفتاح الجديد وجعله مساويًا للقيمة التي تريد تعيينها إلى ذلك المفتاح. فمثلاً إذا أردنا إضافة مفتاح age إلى homer والذي أخبرتنا روبي أعلاه بأنّه ليس موجودًا في جدول التّقطيع عن طريقة إرجاعها nil. فيمكننا فعل ذلك كالتّالي:

homer[:age] = 38

الآن للتأكّد من أن العنصر قد تمّت إضافته فإنّ كلّ ما علينا فعله هو كتابة اسم جدول التّقطيع (homer في هذه الحالة) والضغط على Enter. ستجد أنّ المفتاح age وقيمته موجودين بنهاية جدول التّقطيع . والآن أيضًا يمكنك سؤال روبي عن قيمة المفتاح age وتوقّع ظهور قيمة بدلاً من اللاقيمة nil. 

يمكن أيضًا إضافة جدول تقطيع ليكون أحد القيم في جدول تقطيع آخر. مثلاً إذا أردنا إضافة بيانات زوجة homer إلى جدول التّقطيع الخاصّ به ولدينا فعلاً جدول تقطيع باسم زوجته marge فيمكننا فعل ذلك كالتّالي:

homer[:wife] = marge

ماذا حدث؟ قمنا هنا بتعيين جدول التّقطيع الموجودة بالمتغيّر marge إلى المفتاح الجديد wife المضاف إلى جدول التّقطيع homer. بعد كتابة هذا والضّغط على Enter ستجد أنّ روبي قد أعادت القيمة الموجودة في marge وهي جدول تقطيع مكوّنة من عنصر واحد.

{:name => "Marge Simpson"}

هل توقّعت أن يكون الأمر أكثر صعوبة؟ لا تتوقع أن تواجه صعوبة في روبي لحسن الحظّ. 

الآن إذا أردنا النظر في جدول تقطيع homer ستجد أنّ جدول التّقطيع قد أضيف. ما فعلناه هنا هو أنّنا قمنا بإنشاء جدول تقطيع متداخل Nested Hash، بمعنى أنّ لدينا جدول تقطيع داخل جدول تقطيع آخر. 

الآن إذا أردنا الوصول إلى قيمة جدول تقطيع المتداخل نفعل ذلك عن طريق استدعاء كل مفتاح بالترتيب بدءًا من جدول التّقطيع المحتوي على الآخر. فمثلاً إذا أردت معرفة اسم زوجة هومر فنستدعي المفتاح wife كما تعوّدنا ولكن نضيف إلى ذلك المفتاح أيضًا مفتاح name الموجود في جدول تقطيع marge كالتّالي:

homer[:wife][:name]

إذًا فالمفتاح الأوّل هو الموجود بجدول تقطيع homer والمفتاح الثّاني هو الموجود في جدول تقطيع marge. بالضغط على Enter ستقوم روبي بإرجاع القيمة الموجودة في المفتاح name الموجود في marge وليس homer، لا تنس ذلك.

"Marge Simpson"

دوال جداول التقطيع

الكثير من الدوال الخاصّة بجداول التّقطيع هي نفسها الخاصّة بالمصفوفات تعرّفنا على اثنين منها (include و length). 

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

homer.delete(:age)  # 38

لحذف أوّل عنصر في جدول تقطيع نستخدم دالّة shift والتي تحذف أوّل زوج مفتاح/قيمة بجدول تقطيع وتقوم بإرجاعهما في مصفوفة.

homer.shift # [:name, "Homer Simpson"]

الدوال الخاصة بجداول التقطيع فقط

رغم ذكرنا سابقًا أنّ دوال جداول التّقطيع تشبه دوال المصفوفات إلا أن جداول التّقطيع تختصّ ببعض الدوال عن المصفوفات نظرًا لاحتوائها على مفاتيح وقيم. فمثلاً دالّة keys تقوم بإرجاع مصفوفة بها جميع المفاتيح الموجودة بجدول تقطيع .

homer.keys  # [:job, :children, :wife]

دالّة values تقوم بإرجاع مصفوفة تحتوي على جميع القيم الموجودة في جدول تقطيع .

homer.values    # ["Nuclear Safety Inspector", ["Bart", "Lisa", "Maggie"], { :name => "Marge Simpson" }]

لمعرفة إذا كانت قيمة معيّنة موجودة في جدول تقطيع يمكننا استخدام دالّة ?has_value أو اختصارها ?value.

homer.value?("Homer Simpson")   # false

قد تتساءل لماذا قامت روبي بإرجاع false رغم إنشائنا للمفتاح في البداية؟ دعني أُذكّرك أنّه قد تمّ حذف العنصر باستخدام دالّة shift أعلاه. 

لمعرفة إذا كان مفتاح معيّن موجود في جدول تقطيع أم لا يمكننا استخدام دالّة ?has_key أو اختصارها ?key.

homer.key?(:job)    # true

خاتمة

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


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...