تعلّم البرمجة بلغة كوتلن (47): العمليات على التجميعات Collections

تعلّم البرمجة بلغة كوتلن (47): العمليات على التجميعات Collections
أستمع الى المقال

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

ولأننا لم ندرس بعد كيفية إنشاء دوال خاصة بنا يكون لديها معامل من نوع بيانات الدوال، استخدمنا حتى الآن، دوال مهمة ومستخدمة بكثرة من مكتبة كوتلن القياسية. مثل: ()filter، و ()partition، و ()map. (لاحقًا في درس قادم في هذا القسم، سنشرح تفصيليًا كيفية إنشاء هذه الدوال والتعامل معها).

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

تهيئة التجميعات Collections عبر اللامبدا:

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

رأينا سابقًا في درس القوائم، كيف يمكننا الإعلان عن قائمة List عبر استخدام دالتي: ()listOf للقوائم غير القابلة للتغيير و ()mutableListOf للقوائم القابلة للتغيير. أمّا الإعلان عن قائمة باستخدام تعبير اللامبدا، فسيكون كالتالي:

val listName = List(size, { elements } )

لدى دالة ()List (خلافاً للدوال العادية، تبدأ بحرف كبير) معاملين، الأول يستقبل قيمة حجم size (عدد عناصر) القائمة، والمعامل الثاني هو تعبير اللامبدا الذي نضع به قيمة العناصر. ولأن معامل اللامبدا هو المعامل الأخير في معاملات دالة ()List، يمكننا كتابته خارج أقواس الدالة، كما وضّحنا في الدرس السابق:

val listName = List(size)  { elements }

معامل فهرس القائمة index:

تعبير اللامبدا في دالة ()List لديه معامل واحد من النوع Int وهو يُمثِّل الفهرس في القائمة المُراد إنشاؤها. لذا يمكننا استخدامه هكذا:

السطر أعلاه سينتج قائمة تكون عناصرها هي قيمة الفهرس index. ولأن الفهرس يبدأ من الصفر، كذلك العناصر ستبدأ من الصفر وتستمر إلى آخر عنصر حسب الـ size المُعطى للدالة.

أو يمكننا استبدال المعامل index بالكلمة it:

val listName = List(size)  { it }

سنفهم هذا الأمر عمليًا في الفقرتين التاليتين.

تهيئة قائمة غير قابلة للتغيير List:

لفهم طريقة عمل دالة ()List، فلنلقي نظرة على المثال التالي:

في قائمة list1، أنشأنا قائمة بالحجم 10. أمّا العناصر، فستكون قيمتها هي قيمة فهرس القائمة، والذي تُمثله الكلمة it في تعبير اللامبدا.

وفي قائمة list2، أنشأنا قائمة بعدد 10 عناصر قيمة كل عنصر منها هي العدد صفر.

وفي list3، استخدمنا اللامبدا لإيجاد قيمة العناصر الخمسة. فكما نعرف، ان كل حرف يُمثَّل برقم في الحاسب، استفدنا من ذلك في إنشاء عناصر القائمة، بجمع قيمة الحرف الرقمية و قيمة رقم الفهرس في القائمة الجديدة.

وعند تنفيذ الشفرة، ستكون النتيجة:

تهيئة قائمة قابلة للتغيير MutableList:

بنفس الطريقة، يمكننا إنشاء القوائم القابلة للتغيير MutableList، باستخدام دالة ()MutableList:

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

{ 10 * (it + 1) }

أي الرقم 10 مضروب في رقم فهرس القائمة. ولأن فهرس القائمة يبدأ من صفر، لذا أضفنا له 1 حتى تكون بداية عملية الضرب من العدد 1.

أمّا القائمة التي سيتم إسنادها للمتغير positiveToNegative، استخدمنا عناصر القائمة decades لإنشاء عناصرها. التعبير التالي، سينتج لنا نفس عناصر decades ولكن بالسالب:

{ decades[it] * -1 }

كما نرى، نضع حجم القائمة بين قوسي الدالة، ثم بين قوسي اللامبدا المعقوفين، نضع التعبير الذي نريده والذي ستُمثل نتيجته عناصر القائمة المُراد إنشاؤها.

وعند تنفيذ الشفرة، تكون النتيجة:

برنامج تحويل مقياس درجة الحرارة:

ولضمان الفهم الصحيح لتعبير اللامبدا في دالتي ()List أو ()MutableList، دعونا ننظر إلى مثال من الواقع. بكتابة برنامج يقرأ مدخلات المستخدم لقياس درجة الحرارة السيليزية C (درجة مئوية)، وهو المقياس الرئيسي المعتمد في حياتنا اليومية في غالبية دول العالم، ثم يحوِّلها إلى الفاهرنهايت F وهو المقياس المعتمد في الولايات المتحدة الأمريكية:

في بداية الشفرة، لدينا قائمة celsius محددة الحجم بـ 5 عناصر. لذا ستعمل دالة ()readln لخمس مرات متتالية، يتمكن فيها المستخدم من إدخال عناصر القائمة. ولأن دالة ()readln تعيد كائن من النوع String، لذا نستخدم دالة ()toInt لتحويل المُدخلات إلى النوع Int حتى نستطيع التعامل معها حسابيًا. 

وسيكون المُدخَل الأول للمستخدم هو العنصر الأول في القائمة، والمُدخَل الثاني هو العنصر الثاني … الخ. ومتى ما تم إدخال خمسة عناصر، ينتهي عمل دالة ()readln ويتم حفظ القائمة في المتغير celsius.

أمّا بالنسبة لقائمة fahrenheit، لإرتباطها الوثيق بقائمة celsius، وضعنا لها حجم مساوي لحجم القائمة celsius، والذي وصلنا إليه عبر استخدام الخاصيّة size. وأيضًا، نريد أن تكون عناصرها كنتيجة لتعبير اللامبدا التالي:

{ (celsius[it].toDouble() * 9 / 5) + 32 }

للحصول على درجة الحرارة بالفاهرنهايت، تقول المعادلة أنه يجب ضرب قيمة الدرجة المئوية في 9 ثم تقسيم الناتج على 5 ثم إضافة 32 للناتج.

إذًا ما نحتاجه هو قيمة عناصر celsius والتي هي قيم الدرجة المئوية المُدخلة للبرنامج. لذلك وبما أن الكلمة it تًمثِّل الفهرس في تعبير اللامبدا، يمكننا الوصول لعناصر قائمة celsius بوضعها بين قوسي الفهرسة [ ]. وللحصول على أرقام أكثر دقة من النوع Int، حوّلنا عناصر قائمة celsius إلى النوع Double عند استخدامها في المعادلة عبر دالة ()toDouble.

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

أنواع العمليات على التجميعات في كوتلن:

توفر كوتلن، إجراء العمليات التالية على التجميعات:

  • التحويلات Transformations: تحويل جميع العناصر (الكائنات) في التجميعة، حسب نتيجة القاعدة التي تكون في تعبير اللامبدا.
  • التصفية أو الفلترة Filtering: إرجاع مجموعة مجتزأة من العناصر بناءً على معايير أو قاعدة معينة.
  • التجميع Grouping: تجميع بعض الكائنات في مجموعات أصغر من العناصر بناءً على معايير معينة.
  • إعادة أجزاء من التجميعة Retrieving collection parts: إرجاع مجموعة فرعية من العناصر بقصّها أو حذف بعضها … الخ.
  • إعادة عنصر واحد Retrieving single element: إرجاع عنصر واحد من التجميعة بناءً على معايير أو قاعدة معينة.
  • الترتيب Ordering: ترتيب عناصر التجميعة بناءً على معايير معينة لكل عنصر.
  • تجميع غير متعلق بالمحتوى Aggregate: إرجاع قيمة واحدة بعد تطبيق بعض العمليات على كافة العناصر في التجميعة.

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

بعض دوال إجراء العمليات على التجميعات:

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

  • ()map: هي إحدى الدوال التي تقوم بإجراء التحويلات Transformations في التجميعات. إذ تقوم بتطبيق الإجراء في تعبير اللامبدا، على كل عنصر في التجميعة التي تستدعيها، ثم تُنشئ تجميعة جديدة من العناصر التي تم تحويلها. ويكون ترتيب العناصر في التجميعة الجديدة، هو نفس ترتيبها في التجميعة التي تستدعي دالة ()map. وإذا كنا نريد أن نستخدم فهرس التجميعة أيضًا بالإضافة للعناصر، يمكننا استخدام دالة أخرى تقوم بهذا وهي دالة ()mapIndexed. يمكننا إيجاد المزيد من الدوال لإجراء عمليات التحويل لعناصر التجميعات، في هذا الرابط من موقع كوتلن الرسمي.
  • ()filter: تقوم هذه الدالة، بأحد أهم العمليات التي يتم إجراؤها على التجميعات في كوتلن، ألا وهي تصفية filter التجميعة، حسب الشرط أو القاعدة predicate (نتيجة تعبير اللامبدا) المُرسل إليها. بعد التصفية، تعيد تجميعة جديدة بالعناصر التي توافق الشرط فقط، وتجاهل التي لا توافقه. أمّا كنا نريد أيضًا إعادة قائمة أخرى بالعناصر التي لا توافق شرط التصفية، يمكننا استخدام دالة ()partition، والتي تقوم بهذه المهمة. يمكننا إيجاد المزيد من الدوال لإجراء عمليات التصفية لعناصر التجميعات، في هذا الرابط من موقع كوتلن الرسمي.
  • ()groupBy: هذه هي إحدى أهم الدوال التي تقوم بعمليات تجميع عناصر التجميعات في تجميعة جديدة. ما تقوم به هذه الدالة هو إعادة تجميعة جديدة من النوع Map، حسب الشرط الذي ينتجه تعبير اللامبدا المُرسل إليها. المزيد عن التجميع Grouping في هذا الرابط.
  • بعض الدوال تستخدم في إقتطاع جزء من التجميعة حسب نتيجة شرط اللامبدا، مثل: ()takeWhile، و ()takeLastWhile، و ()takeIf، و ()dropWhile، و ()dropLastWhile وغيرها المتواجدة في هذا الرابط.
  • لإرجاع عنصر واحد من تجميعةٍ ما، يمكننا استخدام دالة ()first والتي ستعيد أول عنصر في التجميعة يوافق نتيجة شرط اللامبدا. أو دالة ()last والتي ستعيد آخر عنصر في التجميعة يوافق نتيجة شرط اللامبدا. المزيد من دوال إعادة عنصر واحد، يمكن إيجاده في هذا الرابط.
  • يمكننا ترتيب عناصر التجميعة عبر دوال مثل: ()sortedBy و ()sortedByDescending حسب نتيجة الشرط الذي سنضعه في تعبير اللامبدا. تقوم الدالة الأولى بترتيب العناصر تصاعديًا، بينما الأخرى ترتبها تنازليًا. وأيضًا يمكننا عكس ترتيب العناصر في التجميعة، باستخدام دالة ()reversed. المزيد من الدوال في هذا الرابط.
  • لإعادة قيمة واحدة حسب المحتوى في التجميعة، نستخدم دوال مثل: ()minOf و ()maxOf لإعادة أصغر وأكبر قيمة في عناصر التجميعة. بعض الدوال الأخرى مثل: ()count تعيد عدد عناصر التجميعة، و ()sumOf والتي يمكن استخدامها مع تجميعات الأعداد. تعيد الدالة مجموع الأعداد في التجميعة، حسب نتيجة الشرط في تعبير اللامبدا. أيضًا المزيد من الدوال في هذا الرابط.

التطبيق العملي لكل الدوال أعلاه:

لفهم عمل هذه الدوال والتفريق بينها، يمكننا وضعها جميعًا في شفرة واحدة، ورؤية نتائج عمل كل منها:

وعند تنفيذ الشفرة، نرى نتائج هذه الدوال كما هو موضح في الصورة التالية:

دوال أخرى مهمة في العمليات على التجميعات:

بالإضافة للدوال المذكورة أعلاه، هناك بعض الدوال المهمة والتي تستخدم بكثرة عند إجراء عمليات على التجميعات. من هذه الدوال، دالة ()forEach، والتي مهمتها تشبه كثيرًا حلقة التكرار for. بالإضافة للدوال: ()any و ()all و ()none، والتي ستعيد true أو false حسب نتيجة تعبير اللامبدا.

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

هذه المرة بعد قراءة مدخلات المستخدم وحفظها في قائمة celsius، أنشأنا قائمة fahrenheit فارغة. ثم استخدمنا دالة ()forEach للدوران على كل عناصر celsius وتحويلها للفاهرنهايت عبر المعادلة. بعد التحويل نضيف العنصر للقائمة fahrenheit عبر الدالة ()add والتي تتوفر لها لأنها قائمة من النوع القابل للتغيير MutableList. ثم نطبع قيمة العنصرين في القائمتين.

نلاحظ أننا استخدمنا الكلمة it للإشارة لعنصر في قائمة celsius، هذا لأن دالة ()forEach تم استدعاؤها عبر هذه القائمة.

في الأسطر الست الأخيرة في الشفرة، طبّقنا الدوال الثلاث: ()any و ()all و ()none. كل دالة منها تعيد true أو false. مثلًا، في السطر الأول نفحص ما إذا كان هناك عنصر في قائمة celsius أكبر من صفر، ستكون النتيجة true، لأن هناك عنصر أكبر من الصفر فعلًا. وهكذا في باقي الأسطر التالية، التي تشرح نفسها بنفسها.

وعند تنفيذ الشفرة، ستكون النتيجة:

ملاحظة أخيرة:

عند استخدام دالتي ()filter و ()map مع تجميعة Set، ستعيد الدالتين تجميعة من النوع قائمة List.

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

هل أعجبك المحتوى وتريد المزيد منه يصل إلى صندوق بريدك الإلكتروني بشكلٍ دوري؟
انضم إلى قائمة من يقدّرون محتوى إكسڤار واشترك بنشرتنا البريدية.