تخطَّ إلى المحتوى

المجموعات في Kotlin: Set و Map ودوال المجموعات

الدرس 11 من 11 · Kotlin

في درس المصفوفات والقوائم تعرّفت على List و Map وعلى filter و map. الآن حان وقت إكمال الصورة: نوع ثالث مهم هو Set، وخرائط تتغير أثناء التشغيل، ومجموعة دوال تجعل ترتيب البيانات وتجميعها والبحث فيها سطراً واحداً. هذه الدوال هي ما يجعل كود Kotlin قصيراً وممتعاً في القراءة.

المجموعة (Set) تشبه القائمة، لكنها ترفض التكرار:

val visitors = mutableSetOf("سارة", "عمر")
println(visitors.add("ليلى"))
println(visitors.add("سارة"))
println(visitors)
println("عمر" in visitors)
val numbers = listOf(3, 1, 3, 2, 1)
println(numbers.toSet())
println(numbers.distinct())
الناتج
true
false
[سارة, عمر, ليلى]
true
[3, 1, 2]
[3, 1, 2]
  • setOf(...) للقراءة فقط، و mutableSetOf(...) تقبل الإضافة والحذف.
  • add تُرجع false إذا كان العنصر موجوداً، ولا تضيفه مرة ثانية.
  • toSet() تحوّل قائمة إلى مجموعة فتختفي التكرارات، و distinct() تفعل الشيء نفسه وتُرجع قائمة.

المجموعة مثل سجل الحضور: الاسم يُكتب مرة واحدة مهما دخل صاحبه وخرج.

والمجموعات تدعم عمليات الرياضيات المعروفة:

val football = setOf("علي", "منى", "زياد")
val swimming = setOf("منى", "هدى", "علي")
println(football intersect swimming)
println(football union swimming)
println(football subtract swimming)
الناتج
[علي, منى]
[علي, منى, زياد, هدى]
[زياد]

intersect للمشترك بين المجموعتين، و union لكل العناصر، و subtract لما في الأولى وليس في الثانية.

الخريطة القابلة للتعديل MutableMap

Section titled “الخريطة القابلة للتعديل MutableMap”

mapOf تنشئ خريطة للقراءة فقط. للإضافة والتعديل استخدم mutableMapOf:

val stock = mutableMapOf("قلم" to 40, "دفتر" to 15)
stock["مسطرة"] = 8
stock["قلم"] = 38
stock.remove("دفتر")
println(stock)
println(stock["قلم"])
println(stock["ممحاة"])
println(stock.getOrDefault("ممحاة", 0))
println(stock.keys)
println(stock.values.sum())
الناتج
{قلم=38, مسطرة=8}
38
null
0
[قلم, مسطرة]
46
  • stock[key] = value: تضيف زوجاً جديداً، أو تستبدل القيمة إذا كان المفتاح موجوداً. المفاتيح لا تتكرر.
  • قراءة مفتاح غير موجود تُرجع null، ولهذا نوع القيمة المقروءة Int?.
  • keys و values: كل المفاتيح وكل القيم.

والمرور على الخريطة يفكك كل زوج إلى متغيرين:

val ages = mapOf("علي" to 13, "سارة" to 14)
for ((name, age) in ages) {
println("$name عمره $age")
}
الناتج
علي عمره 13
سارة عمره 14

مثال عملي: عدّ التكرارات

Section titled “مثال عملي: عدّ التكرارات”
val votes = listOf("أحمر", "أزرق", "أحمر", "أخضر", "أحمر", "أزرق")
val counts = mutableMapOf<String, Int>()
for (vote in votes) {
counts[vote] = (counts[vote] ?: 0) + 1
}
println(counts)
الناتج
{أحمر=3, أزرق=2, أخضر=1}

في كل مرة نقرأ العدد الحالي للون، أو صفراً بفضل المعامل ?: إذا لم يظهر من قبل، ثم نزيده واحداً.

سنستخدم في بقية الدرس قائمة من كائنات data class:

data class Student(val name: String, val grade: Int, val city: String)
val students = listOf(
Student("هدى", 88, "القاهرة"),
Student("كريم", 95, "عمّان"),
Student("ريم", 72, "القاهرة"),
Student("زياد", 60, "تونس"),
)
println(students.sortedBy { it.grade }.map { it.name })
println(students.sortedByDescending { it.grade }.map { it.name })
println(students.maxByOrNull { it.grade }?.name)
println(listOf(5, 2, 9).sorted())
الناتج
[زياد, ريم, هدى, كريم]
[كريم, هدى, ريم, زياد]
كريم
[2, 5, 9]
  • sortedBy { ... }: ترتب تصاعدياً حسب الخاصية التي تختارها، وتُرجع قائمة جديدة دون المساس بالأصلية.
  • maxByOrNull: العنصر صاحب أعلى قيمة، أو null إذا كانت القائمة فارغة.
println(students.find { it.grade > 90 }?.name)
println(students.find { it.grade > 99 })
println(students.any { it.city == "تونس" })
println(students.all { it.grade >= 50 })
println(students.count { it.city == "القاهرة" })
println(students.sumOf { it.grade })
الناتج
كريم
null
true
true
2
315
الدالة السؤال الذي تجيب عنه
find ما أول عنصر يحقق الشرط؟ (أو null)
any هل يوجد عنصر واحد على الأقل يحققه؟
all هل كل العناصر تحققه؟
count كم عنصراً يحققه؟

groupBy توزّع العناصر على مجموعات حسب مفتاح، وتُرجع خريطة:

val byCity = students.groupBy { it.city }
for ((city, group) in byCity) {
println("$city: ${group.map { it.name }}")
}
val (passed, failed) = students.partition { it.grade >= 75 }
println(passed.size)
println(failed.size)
الناتج
القاهرة: [هدى, ريم]
عمّان: [كريم]
تونس: [زياد]
2
2

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

القوة الحقيقية تظهر عند ربط الدوال ببعضها:

val report = students
.filter { it.grade >= 70 }
.sortedByDescending { it.grade }
.joinToString(" ، ") { "${it.name} (${it.grade})" }
println(report)
الناتج
كريم (95) ، هدى (88) ، ريم (72)

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

  1. أنشئ قائمة أرقام فيها تكرارات، واطبع عدد الأرقام المختلفة فيها.
  2. أنشئ mutableMapOf تربط اسم المنتج بسعره، وأضف منتجاً، وعدّل سعر آخر، ثم اطبع أغلى منتج باستخدام maxByOrNull.
  3. أنشئ data class Book(val title: String, val pages: Int, val author: String) وقائمة كتب، واطبع عناوين الكتب مرتبة حسب عدد الصفحات.
  4. تحدٍّ: جمّع الكتب حسب المؤلف بـ groupBy، واطبع لكل مؤلف مجموع صفحات كتبه.
  • التعديل على مجموعة للقراءة فقط: listOf و setOf و mapOf لا تملك add ولا remove. استخدم النسخ التي تبدأ بـ mutable.
  • توقّع تعديل الأصل: sortedBy و filter تُرجع قائمة جديدة. إذا لم تخزن النتيجة فلن يتغير شيء.
  • نسيان null مع الخرائط: stock["قلم"] + 1 خطأ ترجمة لأن القيمة قد تكون null. استخدم ?: أو getOrDefault.
  • first بدل find: first { ... } ترمي NoSuchElementException إذا لم تجد شيئاً. find و firstOrNull أكثر أماناً.

سؤال

ما ناتج listOf(1, 2, 2, 3, 3, 3).toSet().size؟

سؤال

ماذا تُرجع الدالة groupBy؟

  • Set مجموعة بلا تكرار، وتدعم intersect و union و subtract.
  • mutableMapOf خريطة تقبل الإضافة والتعديل والحذف، وقراءتها قد تُرجع null.
  • sortedBy للترتيب، و find و any و all و count للبحث والفحص.
  • groupBy تجمّع العناصر في خريطة، و partition تقسمها إلى قائمتين.
  • مبروك! أنهيت كورس Kotlin. الخطوة التالية: ابنِ أول تطبيق أندرويد لك.