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

مدخل إلى البيانات وأنواعها: التجميعات Collections


أسامة دمراني

أشرنا في المقال السابق إلى عدة أنواع أساسية من البيانات البرمجية، وفي هذا المقال سنتابع مع ذكر نوع هام جدًا منها، وهو البيانات التجميعية collections.

التجميعات Collections

لقد شكلت علوم الحاسوب نظامًا كاملًا يدرس التجميعات وسلوكياتها المختلفة، والتي تسمى بالحاويات أحيانًا أو التسلسلات، وسننظر أولًا في التجميعات المدعومة في بايثون وجافاسكربت وVBScript، ثم نختم بملخص موجز لبعض أنواع التجميعات الأخرى التي قد نراها في لغات أخرى.

القوائم Lists

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

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

تُستخدم الأقواس المربعة لإنشاء القوائم والوصول إليها في بايثون، ويمكن إنشاء قائمة فارغة باستخدام زوج من تلك الأقواس ليس بينهما شيء، أو إنشاء قائمة تحتوي على قيم تفصل بينها فاصلة إنجليزية ",":

>>> aList = []
>>> another = [1,2,3]
>>> print( another )
[1, 2, 3]

نستطيع الوصول إلى العناصر المنفردة باستخدام رقم الفهرس، حيث يحمل العنصر الأول رقم 0 داخل الأقواس المربعة، فإذا أردنا الوصول إلى العنصر الثالث مثلًا -والذي سيحمل الترتيب 2 داخل الأقواس بما أننا نبدأ من الصفر-، فإننا نصل إليه كما يلي:

>>> print( another[2] )
3

كما نستطيع تغيير قيم العناصر في القائمة بطريقة مماثلة:

>>> another[2] = 7
>>> print( another )
[1, 2, 7]

لاحظ كيف تغير العنصر الثالث -الذي فهرسه 2- من 3 إلى 7.

يمكن استخدام أرقام الفهرس السالبة للوصول إلى عناصر القائمة من نهايتها، فإذا أردنا العنصر الأخير من القائمة نستخدم ‎-1:

>>> print( another[-1] )
7

وتضاف العناصر الجديدة ملحقةً إلى نهاية القائمة باستخدام العملية append()‎:

>>> aList.append(42)
>>> print( aList )
[42]

كما يمكن الاحتفاظ بقائمة داخل قائمة أخرى، فإذا ألحقنا القائمة الثانية بالقائمة الأولى فستكون على النحو التالي:

>>> aList.append(another)
>>> print( aList )
[42, [1, 2, 7]]

لاحظ كيف تكون النتيجة قائمةً من عنصرين، لكن العنصر الثاني عبارة عن قائمة بحد ذاته كما نرى من القوسين المحيطين به، ونستطيع الآن الوصول إلى العنصر 7 باستخدام فهرس مزدوج:

>>> print( aList[1][2] )
7

حيث يستخرج الفهرس 1 -الفهرس الأول- العنصر الثاني من القائمة الأولى -الذي هو نفسه القائمة الثانية-، ثم يستخرج الفهرس 2 -الفهرس الثاني- العنصر الثالث في القائمة الثانية، وهو العنصر 7.

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

>>> row1 = [1,2,3]
>>> row2 = ['a','b','c']
>>> table = [row1, row2]
>>> print( table )
[ [1,2,3], ['a','b','c'] ]
>>> element2 = table[0][1]
>>> print( element2 )
2

يمكن استخدام هذه الطريقة في إنشاء دفتر عناوين يكون كل مدخل فيه عبارةً عن قائمة من الاسم والعنوان، ويوضح المثال التالي دفتر عناوين فيه مدخلان:

>>> addressBook = [
... ['Samy', '9 Some St',' Anytown', '0123456789'],
... ['Warda', '11 Nother St', 'SomePlace', '0987654321']
... ]
>>>

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

اقتباس

إذا كنت تستخدم أداة IDLE فلن ترى محث ...، وإنما مجرد سطر فارغ.

تدريب

استخرج رقم هاتف سامي -العنصر 3 من الصف الأول-، وتذكر أن الفهارس تبدأ من الصفر وليس الواحد، ثم أضف بعض المدخلات من عندك باستخدام عملية append()‎.

لاحظ أننا نفقد بياناتنا عند الخروج من بايثون، لكن سنرى كيفية الاحتفاظ بها عند شرح الملفات في هذا الكتاب.

يُستخدم الأمر del لحذف العناصر من القائمة:

>>> del aList[1]
>>> print( aList )
[42]

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

إذا أردنا ضم قائمتين معًا لجعلهما قائمةً واحدةً، فسنتخدم عامل الضم + الذي رأيناه في ضم السلاسل النصية من قبل:

>>> newList = aList + another
>>> print( newList )
[42, 1, 2, 7]

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

>>> print( newList[1] )
1

يمكن تطبيق إشارة عملية الضرب هنا مثل عامل تكرار لملء القائمة بمضاعفات نفس القيمة:

>>> zeroList = [0] * 5
>>> print( zeroList )
[0, 0, 0, 0, 0]

كما يمكن إيجاد فهرس عنصر ما في قائمة باستخدام العملية index()‎ كما يلي:

>>> print( [1,3,5,7].index(5) )
2
>>> print( [1,3,5,7].index(9) )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.index(x): x not in list

لاحظ أن محاولة العثور على فهرس لشيء غير موجود في القائمة ينتج خطأً، فإذا أردنا التحقق من وجود شيء في تجميعة ما فسنستخدم العامل in كما يلي:

>>> print( 5 in [1,3,5,7] )
True
>>> print( 9 in [1,3,5,7] )
False 

النتائج قيم بوليانية كما نرى، إما True أو False، وسنرى كيف نستخدم تلك النتائج في فصل لاحق.

أخيرًا، يمكن معرفة طول القائمة باستخدام الدالة المضمَّنة len()‎:

>>> print( len(aList) )
1
>>> print( len(newList) )
4
>>> print( len(zeroList) )
5

لا تدعم جافاسكربت ولا VBScript نوع القائمة مباشرةً، وسنرى أن لديهما نوع بيانات يحاكي وظيفة القائمة هنا، وهو نوع المصفوفات arrays.

الصف Tuple

توفر بعض لغات البرمجة بنية بيانات اسمها صف tuple، وما هي إلا تجميعة عشوائية من القيم لكنها تعامَل مثل وحدة واحدة، وهي تشبه القوائم في نواحٍ كثيرة، لكن مع فرق أن وحدات tuple غير قابلة للتغيير، مما يعني أننا لا نستطيع التعديل عليها أو إلحاق شيء إليها بعد إنشائها أول مرة، تمثَّل وحدات tuple في بايثون بقائمة من القيم المفصول بينها بفواصل:

>>> aTuple = 1,3,5
>>> print( aTuple[1] ) # استخدم الفهرسة مثل القوائم
3
>> aTuple[2] = 7   # tuple خطأ، لا يمكن تعديل 
Traceback (innermost last):
  File "<pyshell#20>", line 1, in ?
        aTuple[2] = 7
TypeError: object doesn't support item assignment

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

>>> t1 = 1,2,3 * 3
>>> t2 = (1,2,3) * 3
>>> print(t1)
(1, 2, 9)
>>> print( t2 )
(1, 2, 3, 1, 2, 3, 1, 2, 3)

يطبَّق الضرب في الحالة الأولى على العنصر الأخير فقط في وحدة Tuple، أما في الحالة الثانية فتضاعَف المجموعة بالكامل، ولتجنب مثل هذا الغموض، سنستخدم الأقواس في كل مرة نصف فيها وحدات tuple.

لعل أحد أهم الأمور التي يجب تذكرها هو أن الأقواس المربعة تستخدم لفهرسة وحدات tuple، بينما تُستخدم الأقواس العادية لتعريفها، ولا يمكن تغييرها بعد إنشائها؛ أما في سوى هذه الأمور فما ينطبق من العمليات على القوائم ينطبق أيضًا على الصفوف، ورغم أننا لا نستطيع تعديل وحدة الصف بعد إنشائها؛ إلا أننا نستطيع إضافة أعضاء جديدة إليها باستخدام عامل الإضافة، فهذا سينشئ صفًا جديدًا، كما يلي:

>>> tup1 = (1,2,3)
>>> tup2 = tup1 + (4,) 
# وليس عددًا صحيحًا tuple الفاصلة تجعل هذا وحدة 

>>> print( tup2 )
(1,2,3,4)

إذا لم نستخدم الفاصلة اللاحقة بعد العدد 4 فستفسره بايثون على أنه العدد الصحيح 4 داخل أقواس وليس على أنه صف، لكن سنحصل على خطأ بأي حال بما أنه لا يمكن إضافة أعداد صحيحة إلى الصفوف، لذا سنضيف الفاصلة لنخبر بايثون بأن تعامل الأقواس على أنها صف، فبإضافة الفاصلة الزائدة نقنع بايثون بأن وحدة الصف التي فيها مدخل واحد هي صف فعلًا؛ أما بالنسبة للغتي جافاسكربت وVBScript فليس لديهما أي مفهوم للصف tuple.

القاموس Dictionary أو Hash

يحتوي هذا النوع من البيانات على قيمة ترتبط بمفتاح، كما يربط القاموس اللغوي المعنى بكلمة، وقد تكون تلك القيمة سلسلةً نصيةً أو لا، وتُجلب تلك القيمة بفهرسة القاموس بالمفتاح، ولا يلزم أن يكون المفتاح سلسلةً من الأحرف -رغم أنه غالبًا كذلك-، وهذا على خلاف القاموس اللغوي، فقد يكون أي نوع غير قابل للتغير، بما في ذلك الأعداد والصفوف، كما قد تحتوي القيم المرتبطة بالمفاتيح على أي نوع من البيانات.

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

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

>>> dct = {}
>>> dct['boolean'] = "A value which is either true or false"
>>> dct['integer'] = "A whole number"
>>> print( dct['boolean'] )
A value which is either true or false

لاحظ أننا نبدأ القاموس بأقواس معقوصة ثم نستخدم الأقواس المربعة لإسناد القيم وقراءتها، كما تُستخدم دالة النوع dict()‎ لإعادة قاموس فارغ، ويمكن بدء القاموس أثناء إنشائه كما فعلنا في حالة القوائم، باستخدام الصيغة التالية:

>>> addressBook = {
... 'Samy' : ['Samy', '9 Some St',' Anytown', '0123456789'],
... 'Warda' : ['Warda', '11 Nother St', 'SomePlace', '0987654321']
... }
>>>

يُفصل المفتاح والقيمة بنقطتين رأسيتين، وتُفصل الأزواج بفواصل إنجليزية ,، كما يمكن تخصيص القاموس باستخدام صيغة مختلفة -انظر أدناه-، ويمكنك اختيار إحدى الطريقتين لاستخدامها.

>>> book = dict(Samy=['Samy', '9 Some St',' Anytown', '0123456789'], 
...             Warda=['Warda', '11 Nother St', 'SomePlace', '0987654321'])
>>> print( book['Samy'][3] )
0123456789

لاحظ أننا لا نحتاج إلى علامات اقتباس حول المفتاح في التعريف، لأن بايثون تفترض أنها سلسلة نصية، لكننا نحتاج إليها بأي حال من أجل استخراج القيم، وهذا الأسلوب يَحُد من عملية الطريقة الثانية، لهذا يفضِّل المبرمجون استخدام الطريقة الأولى التي يستخدمون الأقواس المعقوصة فيها، وبهذا نكون قد أنشأنا دفتر عناوين من قاموس مفاتيحه هي الأسماء، ويخزن قوائمنا مثل قيم، ونريد أن نستخدم الاسم وحده لجلب كل المعلومات بدلًا من حساب الفهرس العددي، كما يلي:

>>> print( addressBook['Warda'] )
['Warda', '11 Nother St', 'SomePlace', '0987654321']
>>> print( addressBook['Samy'][3] )
0123456789

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

>>> name = 0
>>> street = 1
>>> town = 2
>>> tel = 3

نستطيع الآن استخدام تلك المتغيرات لنعرف اسم مدينة Warda:

>>> print( addressBook['Warda'][town] )
SomePlace

لاحظ أن town ليست داخل علامات اقتباس لأنها اسم متغير، وستحوله بايثون إلى قيمة الفهرس التي أسندناها -وهي 2-، على عكس 'Warda' المحاطة بعلامات اقتباس لأن المفتاح هو سلسلة نصية، وهنا بدأ دفتر العناوين يتحول إلى ما يشبه التطبيق القابل للاستخدام بسبب مزايا القواميس، حيث لن نحتاج إلى كثير من العمل الإضافي لحفظ البيانات واستعادتها وإضافة محث استعلام ليسمح لنا بتحديد البيانات التي نريدها.

يمكن استخدام قاموس لتخزين البيانات، وسيكون دفتر العناوين حينها قاموسًا مفاتيحه الأسماء، وستكون القيم قواميس مفاتيحها هي حقول الأسماء، كما يلي:

addressBook = {
... 'Samy' : {'name':      'Samy', 
...           'street':    '9 Some St',
...           'town':      'Anytown', 
...           'tel':       '0123456789'},
... 'Warda' : {'name':      'Warda', 
...           'street':    '11 Nother St', 
...           'town':      'SomePlace', 
...           'tel':       '0987654321'}
... }

لاحظ أن هذا التنسيق سهل القراءة رغم أنه يحتاج إلى الكثير من الكتابة، ويُشار إلى مثل هذه البيانات التي تخزَّن بتنسيق يجمع بين المعنى والمحتوى في تنسيق مقروء للبشر باسم البيانات الموثقة ذاتيًا.

حين ندخِل هيكل بيانات داخل هيكل آخر مطابق له، كما في حالة إدخال قاموس داخل قاموس آخر هنا، فإنه يُسمى بالتداخل أو التشعب nesting، حيث يكون القاموس الداخلي قاموسًا متشعبًا من الخارجي، ويمكن الوصول إلى تلك البيانات بطريقة تشبه أسلوب القوائم ذات الفهارس المسماة:

>>> print( addressBook['Warda']['town'] )
SomePlace

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

لا تدعم القواميس كثيرًا من عوامل التجميعات التي رأيناها من قبل، فلا تدعم عامل الضم ولا التكرار ولا الإلحاق، رغم إمكانية إسناد أزواج من المفاتيح والقيم مباشرةً كما رأينا في بداية هذا القسم.

وتُستخدم العملية keys()‎ لتسهيل الوصول إلى مفاتيح القاموس، إذ تسمح لنا بالحصول على قائمة من جميع المفاتيح الموجودة في قاموس ما، فإذا أردنا الحصول على كل الأسماء الموجودة في دفتر العناوين الذي أنشأناه من قبل؛ فسيكون ذلك كما يلي:

>>> print( list(addressBook.keys()) )
['Samy','Warda']

لاحظ أننا استخدمنا list()‎ للحصول على قيم المفاتيح الحقيقية، أما إذا حذفنا list()‎ فسنحصل على نتيجة غريبة بعض الشيء لن نذكرها الآن.

لاحظ أن القواميس لا تخزِّن مفاتيحها بنفس ترتيب إدخالها، فقد نرى أن المفاتيح تظهر بترتيب غريب عن ترتيبها الأول، بل قد يتغير الترتيب بتغير الوقت، لكن هذا لا يهم بما أننا نستطيع استخدام المفاتيح للوصول إلى البيانات المرتبطة بها، إذ سنحصل على القيم الصحيحة في كل مرة.

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

قواميس VBScript

توفر VBScript كائن قاموس يقدم تسهيلات تماثل قاموس بايثون، لكن استخدامه مختلف قليلًا عن حالة بايثون، حيث ننشئ القاموس هنا بالتصريح عن متغير يحمل الكائن، ثم ننشئ ذلك الكائن، وبعد ذلك نضيف المدخلات إلى القاموس الجديد كما يلي:

Dim dict     ' Create a variable.
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "a", "Athens" ' Add some keys and items.
dict.Add "b", "Belgrade"
dict.Add "c", "Cairo"

لاحظ أن دالة CreateObject توضح أننا ننشئ كائن "Scripting.Dictionary"، وهو كائن Dictionary من وحدة Scripting الخاصة بلغة VBScript، وسندرس ذلك بالتفصيل حين نأتي على الكائنات، لكننا نأمل بذكرها هنا أن تتذكر مفهوم استخدام كائن من وحدة ما كما ذكرنا في فصل التسلسلات البسيطة.

يجب كذلك استخدام كلمة Set المفتاحية عند إسناد كائن إلى متغير في VBScript. نستطيع الآن أن نصل إلى البيانات كما يلي:

item = dict.Item("c") ' Get the item.
dict.Item("c") = "Casablanca" ' Change the item

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

فيما يلي نسخة كاملة مبسطة عن مثال دفتر العناوين السابق، ولكن في VBScript:

<script type="text/VBScript">
Dim addressBook
Set addressBook = CreateObject("Scripting.Dictionary")
addressBook.Add "Samy", "Samy, 9 Some St, Anytown, 0123456789"
addressBook.Add "Warda", "Warda, 11 Nother St, SomePlace, 0987654321"

MsgBox addressBook.Item("Warda")
</script>

ما فعلناه هنا هو تخزين جميع البيانات مثل سلسلة نصية واحدة بدلًا من استخدام قائمة -وهذا يصعّب استخراج الحقول المنفردة على عكس القائمة والقاموس-، ثم نستطيع الوصول إلى تفاصيل Warda ونطبعها في صندوق رسالة.

قواميس جافاسكربت

لا تحتوي جافاسكربت على كائن قاموس فيها، رغم أننا نستطيع الوصول إلى كائن Scripting.Dictionary الذي ذكرناه في VBScript باستخدام إنترنت إكسبلورر، لنتمكن حينئذ من استغلال جميع مزاياه، لكن لن نشرح ذلك هنا مرةً أخرى بما أنه نفس الكائن.

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

المصفوفات أو المتجهات Arrays

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

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

مصفوفات VBScript

تكون المصفوفة في VBScript تجميعة بيانات ذات طول ثابت، حيث يمكن التوصَل إليها بواسطة فهرس عددي، ويصرَّح عنها ويوصَل إليها كما يلي:

Dim AnArray(42)    ' A 43! element array
AnArray(0) = 27    ' index starts at 0
AnArray(1) = 49
AnArray(42) = 66   ' assign to last element
myVariable = AnArray(1)  ' read the value

تُستخدم الكلمة المفتاحية Dim لتحديد أبعاد المتغير، وهي الطريقة التي نخبر بها VBScript عن المتغير، فهي تتوقع إذا بدأنا الشيفرة بـ OPTION EXPLICIT؛ أن نحدد أبعاد أي متغير نستخدمه من خلال Dim، وهذا سلوك محمود يؤدي إلى برامج ثابتة قوية الأداء، كما أننا حددنا آخر فهرس صالح 42، وهذا يعني أن المصفوفة فيها 43 عنصرًا بما أنها تبدأ من الصفر.

تُستخدم الأقواس في VBScript لحساب أبعاد المصفوفة وفهرستها، وهذا على خلاف الأقواس المربعة المستخدمة في بايثون وجافاسكربت، حيث لا يوجد في VBScript نوع بيانات إلا النوع Variant، والذي يخزِّن أي نوع من أنواع القيم، وبناءً عليه فلا تخزِّن المصفوفات في هذه اللغة إلا المتغيرات بما أن عناصر المصفوفة تكون من نوع واحد، لكن في نفس الوقت، فإن المتغير هنا قد يحمل أي نوع من أنواع القيم، مما يعني مرةً أخرى أن المصفوفات في VBScript تخزن أي شيء، وهذا أمر مربك عند التفكير فيه.

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

كما يمكن التصريح عن عدة مصفوفات متعددة الأبعاد لنمذجة جداول بيانات كما في قوائم بايثون إذا استخدمنا تدريب دفتر العناوين مثلًا.

Dim MyTable(2,3)  ' 3 rows, 4 columns
MyTable(0,0) = "Samy"  ' Populate Samy's entry
MyTable(0,1) = "9 Some Street"
MyTable(0,2) = "Anytown"
MyTable(0,3) = "0123456789"
MyTable(1,0) = "Warda"  ' And now Warda...
...and so on...

لكن لا توجد طريقة سهلة لملء البيانات دفعةً واحدةً كما فعلنا في قوائم بايثون، بل يجب أن نملأ كل حقل على حدة، وعلى الرغم من وجود الدالة Arrayفي VBScript والتي تستطيع ملء مصفوفة دفعةً واحدةً -مما يعني الحاجة إلى جعلها متشعبةً-؛ فستصبح معقدةً وصعبة القراءة.

إذا دمجنا قواميس VBScript مع هذه الخاصية لمصفوفاتها، فسنحصل على نفس النتيجة التي حصلنا عليها في بايثون، كما يلي:

<script type="text/VBScript">
Dim addressBook
Set addressBook = CreateObject("Scripting.Dictionary")
Dim Samy(3)
Samy(0) = "Samy"
Samy(1) = "9 Some St"
Samy(2) = "Anytown"
Samy(3) = "0123456789"
addressBook.Add "Samy", Samy

MsgBox addressBook.Item("Samy")(3) ' Print the Phone Number
</script>

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

Dim DynArray()  ' no size specified

ثم إذا أردنا تغيير حجمها نفعل ذلك كما يلي:

<script type="text/vbscript">
Dim DynArray()
ReDim DynArray(5)  ' Initial size = 5
DynArray(0) = 42
DynArray(4) = 26
MsgBox "Before: " & DynArray(4)  ' prove that it worked
' Resize to 21 elements keeping the data we already stored
ReDim Preserve DynArray(20)
DynArray(15) = 73
MsgBox "After Preserve: " & DynArray(4) & " " & DynArray(15)' Old and new still there
' Resize to 51 items but lose all data
Redim DynArray(50)
MsgBox "Without Preserve: " & DynArray(4) & " Oops, Where did it go?"
</script>

لاحظ أن السلوك الافتراضي هو حذف كل محتويات المصفوفة عند تغيير حجمها، فإذا أردنا الحفاظ على محتوياتها؛ فيجب أن نخبر VBScript أن تحفظ المحتويات باستخدام Preserve.

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

مصفوفات جافاسكربت

رغم أنه يطلَق على هذا النوع من البيانات في جافاسكربت مصفوفة؛ إلا أنها تسمية خاطئة، فهو مزيج غريب يجمع بين مزايا القوائم والقواميس والمصفوفات العادية، حيث نستطيع مثلًا التصريح عن مصفوفة من عشرة عناصر من نوع ما كما يلي:

var items = new Array(10);

لاحظ استخدام كلمة new المفتاحية، إذ تشبه دالة CreateObject()‎ التي استخدمناها في VBScript لإنشاء قاموس، كذلك فقد استخدمنا الأقواس لتحديد حجم المصفوفة، نستطيع الآن أن نملأ المصفوفة ونصل إليها كما يلي:

items[4] = 42;
items[7] = 21;
var aValue = items[4];

وهنا نستخدم الأقواس المربعة للوصول إلى عناصر المصفوفة، وتبدأ الفهارس فيها من الصفر، لكن رغم هذا كله فمصفوفات جافاسكربت ليست مقيدة بتخزين نوع واحد من البيانات، بل يمكن إسناد أي شيء إلى عناصرها:

items[9] = "A short string";
var msg = items[9];

كما يمكن إنشاء مصفوفات من خلال توفير قائمة بالعناصر:

var moreItems = new Array("one","two","three",4,5,6);
aValue = moreItems[3];  // -> 4
msg = moreItems[0];   // -> "one"

كذلك يمكن تحديد طول المصفوفة باستخدام خاصية تدعى length، التي تستخدَم كما يلي:

var size = items.length;

وعلى غرار أسلوب بايثون في استدعاء الدوال في وحداتها، فإن الصياغة name.property هنا هي نفسها، لكن دون أقواس لاحقة.

ورغم أن مصفوفات جافاسكربت تبدأ بالصفر كما ذكرنا؛ إلا أن فهارسها ليست مقصورةً على الأرقام وحدها، ولهذا يمكن استخدام السلاسل النصية أيضًا -والتي تكون هنا أشبه بالقواميس-، كما يمكن توسيع مصفوفة بإسناد قيمة إلى فهرس تكون أكثر من الحد الأقصى الحالي، مما يعني أننا لا نحتاج إلى تحديد الحجم عند إنشاء المصفوفة، رغم كون ذلك سلوكًا مستحسنًا في البرمجة، ويوضح المثال التالي خصائص مصفوفات جافاسكربت التي ذكرناها:

<script type="text/javascript">
var items = new Array(10);
var moreItems = new Array(1); 
items[42] = 7;
moreItems["foo"] = 42;
msg = moreItems["foo"];
document.write("msg = " + msg + " and items[42] = " + items[42] );
</script>

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

لننظر أخيرًا في مثال دفتر العناوين مرةً أخرى، باستخدام مصفوفات جافاسكربت هذه المرة:

<script type="text/javascript">
var addressBook = new Array();
addressBook["Samy"] = new Array("Samy", "9 Some St", "Anytown", "0123456789");
addressBook["Warda"] = new Array("Warda", "11 Nother St", "SomePlace", "0987654321");

document.write(addressBook.Warda);
</script>

يمكن الوصول إلى المفتاح كما لو كان خاصيةً مثل length، ويتبين مما سبق أن مصفوفات جافاسكربت ما هي إلا هياكل بيانات مرنة للغاية.

المكدس Stack

يكدِّس العاملون في المطاعم الأطباق النظيفة فوق بعضها، ثم تؤخَذ واحدًا تلو الآخرللعملاء بدءًا من الأعلى، فيكون الطبق الأخير هو آخر طبق يُستخدم، وفي نفس الوقت أقل طبق يُستخدم، يشبه هذا المثال مكدسات البيانات بالضبط، إذ نضع العنصر في قمة المكدس أو نأخذ عنصرًا منه، ويكون العنصر المأخوذ هو آخر عنصر مضاف إلى المكدس، ويطلَق على هذه الخاصية للمكدسات "آخرها دخولًا أولها خروجًا" أو LIFO - Last In First Out. من الخصائص المفيدة في المكدسات أننا نستطيع عكس قائمة العناصر عن طريق دفع القائمة في المكدس ثم إخراجها مرةً أخرى، فتكون النتيجة عكس القائمة الأولى.

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

تدريب

اكتب مكدسًا باستخدام قائمة في بايثون، وتذكر أننا نلحق العناصر في نهاية القائمة باستخدام append()‎، ونحذفها باستخدام فهارسها عن طريق del()‎، كما تستطيع استخدام ‎-1 مثل فهرس يشير إلى آخر عنصر في القائمة.

يجب أن تكون قادرًا على كتابة برنامج -مستفيدًا من هذه الإرشادات- يدفع 4 محارف إلى قائمة ثم يخرجها مرةً أخرى، ثم اطبعها بعد ذلك. راقب الترتيب الذي تستدعي به print و del، وإذا نجحت في هذا، فجرب طباعتها بعكس الترتيب الذي أدخلتها به، استخدم العملية pop()‎ الخاصة بقوائم بايثون والتي تعيد العنصر الأخير وتحذفه في خطوة واحدة، من أجل تسهيل التدريب.

الحقيبة Bag

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

المجموعات Sets

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

لا تستخدم جافاسكربت ولا VBScript المجموعات مباشرةً، لكن يمكن تنفيذ سلوك قريب باستخدام القواميس التي لا تحتوي إلا على مفاتيح بقيم فارغة؛ أما بايثون فتدعم المجموعات مثل نوع من أنواع البيانات فيها، وأحد الاستخدامات البسيطة لها ما يلي:

>>> A = set()  # أنشئ مجموعة فارغة
>>> B = set([1,2,3]) # مجموعة من قائمة
>>> C = {3,4,5} # تهيئة مثل [] في القوائم
>>> D = {6,7,8}
>>> # جرب الآن بعض عمليات القوائم
>>> print( B.union(C) )
{1, 2, 3, 4, 5}
>>> print( B.intersection(C) )
{3}
>>> print( B.issuperset({2}) )
True
>>> print( {3}.issubset(C) )
True
>>> print( C.intersection(D) == A )
True

لاحظ أننا نستطيع تهيئة المجموعة باستخدام الأقواس المعقوصة، لكن هذا قد يتشابه مع القواميس، لذا نفضل استخدام الصيغة set([...]])‎ رغم أنها تتطلب كتابةً أكثر، فيما يلي اختصارات لعمليات الاتحاد والتقاطع:

>>> print( B & C ) # B.intersection(C) كما في  
>>> print( B | C ) # B.union(C) كما في 

نستطيع أخيرًا التحقق من وجود عنصر ما في مجموعة باستخدام العامل in:

>>> print( 2 in B )
True

هذه العمليات كافية إلى الآن لما نشرحه، رغم وجود عمليات أخرى على المجموعات.

الطابور Queue

يشبه الطابور أو قائمة الانتظار؛ المكدس، باستثناء أن العنصر الأول فيه الذي يخرج أولًا أيضًا، ويسمى هذا السلوك "الأول دخولًا الأول خروجًا" أو FIFO - First In First Out، ويُنشَأ الطابور باستخدام قائمة أو مصفوفة.

تدريب

جرب كتابة طابور باستخدام قائمة، وتذكر أنك تستطيع الإضافة إلى القائمة باستخدام append()‎، وأن تحذف من موضع ما باستخدام del()‎. أضف أربعة محارف إلى الطابور ثم استخرجها واطبعها، غذ يجب أن تُطبع المحارف بنفس الترتيب الذي دخلت به.

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

الملفات Files

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

وبهذا تكون قد تعرفت على أبرز البيانات البرمجية التي قد تحتاج إليها.

ترجمة -بتصرف- للفصل الخامس: The Raw Materials من كتاب Learning To Program لصاحبه Alan Gauld.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...