المجموعات في Swift: الترتيب والبحث والتجميع
في درس المصفوفات تعرّفت على Array و Dictionary و Set، وعلى filter و map و reduce. لكن بيانات التطبيقات الحقيقية ليست أرقاماً بسيطة، بل قوائم من الكائنات: طلاب، ومنتجات، ورسائل. في هذا الدرس ستتعلم كيف ترتب هذه القوائم وتبحث فيها وتجمّعها، بدوال قصيرة تغنيك عن حلقات طويلة.
سنستخدم في أغلب الدرس هذه البيانات:
struct Student { let name: String let grade: Int let city: String}
let students = [ Student(name: "هدى", grade: 88, city: "القاهرة"), Student(name: "كريم", grade: 95, city: "عمّان"), Student(name: "ريم", grade: 72, city: "القاهرة"), Student(name: "زياد", grade: 60, city: "تونس"),]ترتيب الكائنات
Section titled “ترتيب الكائنات”sorted() وحدها تعمل مع الأرقام والنصوص. مع الكائنات يجب أن تخبر Swift كيف تقارن بين عنصرين:
let ascending = students.sorted { $0.grade < $1.grade }print(ascending.map { $0.name })
let descending = students.sorted { $0.grade > $1.grade }print(descending.map { $0.name })
if let top = students.max(by: { $0.grade < $1.grade }) { print("الأول: \(top.name)")}["زياد", "ريم", "هدى", "كريم"]["كريم", "هدى", "ريم", "زياد"]الأول: كريم- الدالة بين القوسين تستقبل عنصرين
$0و$1، وتُرجعtrueإذا كان الأول يجب أن يأتي قبل الثاني. sortedتُرجع مصفوفة جديدة. لترتيب مصفوفةvarفي مكانها استخدمsort.max(by:)وmin(by:)تُرجعان قيمة اختيارية، لأن المصفوفة قد تكون فارغة.
البحث والفحص
Section titled “البحث والفحص”if let found = students.first(where: { $0.grade > 90 }) { print(found.name)}print(students.contains { $0.city == "تونس" })print(students.allSatisfy { $0.grade >= 50 })print(students.filter { $0.city == "القاهرة" }.count)print(students.firstIndex { $0.name == "ريم" } ?? -1)كريمtruetrue22| الدالة | السؤال الذي تجيب عنه |
|---|---|
first(where:) |
ما أول عنصر يحقق الشرط؟ (قيمة اختيارية) |
contains |
هل يوجد عنصر واحد على الأقل يحققه؟ |
allSatisfy |
هل كل العناصر تحققه؟ |
firstIndex |
ما موضع أول عنصر يحققه؟ (قيمة اختيارية) |
enumerated و zip
Section titled “enumerated و zip”كثيراً ما تحتاج رقم العنصر مع العنصر نفسه. enumerated() تعطيك الاثنين معاً:
let ranked = students.sorted { $0.grade > $1.grade }for (index, student) in ranked.enumerated() { print("\(index + 1). \(student.name)")}1. كريم2. هدى3. ريم4. زيادكل عنصر هنا صف (Tuple): قيمتان بين قوسين، فككناهما مباشرة إلى index و student. و zip تربط مصفوفتين عنصراً بعنصر:
let subjects = ["رياضيات", "علوم", "لغة عربية"]let marks = [90, 85, 97]
for (subject, mark) in zip(subjects, marks) { print("\(subject): \(mark)")}رياضيات: 90علوم: 85لغة عربية: 97تخيّل zip مثل سحّاب المعطف: يمسك سنّاً من كل جهة ويضمهما معاً.
compactMap: حوّل واحذف الفارغ
Section titled “compactMap: حوّل واحذف الفارغ”map تحوّل كل عنصر. لكن إذا كان التحويل قد يفشل، ستحصل على مصفوفة مليئة بالقيم الاختيارية. compactMap تحوّل وتحذف كل nil في خطوة واحدة:
let inputs = ["12", "abc", "7", ""]let numbers = inputs.compactMap { Int($0) }print(numbers)print(numbers.reduce(0, +))[12, 7]19عدّ التكرارات
Section titled “عدّ التكرارات”القراءة من القاموس تُرجع قيمة اختيارية كما تعلمت. الصيغة [key, default: 0] تعطيك قيمة بديلة، وتسمح بالتعديل المباشر:
let votes = ["أحمر", "أزرق", "أحمر", "أخضر", "أحمر", "أزرق"]var counts: [String: Int] = [:]for vote in votes { counts[vote, default: 0] += 1}
print(counts["أحمر"] ?? 0)print(counts["أزرق"] ?? 0)print(counts["أصفر"] ?? 0)320في كل مرة نقرأ العدد الحالي للون، أو صفراً إن لم يظهر من قبل، ثم نزيده واحداً.
التجميع في قاموس
Section titled “التجميع في قاموس”Dictionary(grouping:by:) توزّع العناصر على مجموعات حسب مفتاح تختاره:
let byCity = Dictionary(grouping: students, by: { $0.city })
for city in byCity.keys.sorted() { let group = byCity[city] ?? [] let names = group.map { $0.name }.joined(separator: " و ") print("\(city): \(names)")}القاهرة: هدى و ريمتونس: زيادعمّان: كريمالنتيجة قاموس: كل مدينة مفتاح، وقيمتها مصفوفة طلابها. ولأن القاموس غير مرتب، رتبنا المفاتيح قبل المرور عليها حتى يكون الناتج ثابتاً. تخيّل معلماً يطلب من الطلاب أن يقفوا حسب مدنهم.
والمرور على القاموس مباشرة يعطيك كل زوج كصف من مفتاح وقيمة (والشرطة _ تعني أننا لا نحتاج المفتاح هنا):
let prices = ["قلم": 5, "دفتر": 15, "مسطرة": 7]var total = 0for (_, price) in prices { if price > 6 { total += price }}print(total)print(prices.values.reduce(0, +))print(prices.filter { $0.value < 10 }.count)22272سلسلة العمليات
Section titled “سلسلة العمليات”القوة الحقيقية تظهر عند ربط الدوال ببعضها:
let report = students .filter { $0.grade >= 70 } .sorted { $0.grade > $1.grade } .map { "\($0.name) (\($0.grade))" } .joined(separator: " ، ")print(report)كريم (95) ، هدى (88) ، ريم (72)تُقرأ السلسلة من الأعلى للأسفل كجملة: خذ الطلاب، اختر الناجحين، رتّبهم تنازلياً، حوّل كل واحد إلى نص، ثم ادمج النصوص.
عمليات المجموعات Set
Section titled “عمليات المجموعات Set”المجموعة Set التي عرفتها تدعم العمليات الرياضية المعروفة:
let football: Set = ["علي", "منى", "زياد"]let swimming: Set = ["منى", "هدى", "علي"]
print(football.intersection(swimming).sorted())print(football.subtracting(swimming).sorted())print(football.union(swimming).count)["علي", "منى"]["زياد"]4intersection للمشترك، و subtracting لما في الأولى وليس في الثانية، و union للكل. واستخدمنا sorted() لأن المجموعة بلا ترتيب.
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ
struct Bookفيهtitleوpagesوauthor، ومصفوفة من خمسة كتب. - اطبع عناوين الكتب مرتبة حسب عدد الصفحات، مع رقم كل كتاب باستخدام
enumerated(). - اطبع عنوان أول كتاب تتجاوز صفحاته 300 باستخدام
first(where:)، أو رسالة مناسبة إن لم يوجد. - تحدٍّ: جمّع الكتب حسب المؤلف بـ
Dictionary(grouping:by:)، واطبع لكل مؤلف مجموع صفحات كتبه.
أخطاء شائعة
Section titled “أخطاء شائعة”- انتظار ترتيب من القاموس: ترتيب
forعلى قاموس أو مجموعة يتغير من تشغيل لآخر. رتّب المفاتيح بـkeys.sorted(). - sort مع let:
sort()تعدّل المصفوفة نفسها فتحتاجvar. معletاستخدمsorted()التي تُرجع نسخة جديدة. - نسيان أن الناتج اختياري:
first(where:)وmax(by:)وfirstIndexتُرجع قيماً اختيارية. افتحها بـif letأو??. - map بدل compactMap:
inputs.map { Int($0) }تعطي[Int?]فيهاnil. استخدمcompactMapللحصول على[Int]نظيفة.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ما ناتج ["3", "x", "5"].compactMap { Int($0) }؟
compactMap تحوّل كل عنصر وتحذف النتائج التي قيمتها nil، فيبقى الرقمان فقط.
سؤال
ماذا تعطيك enumerated() عند المرور على مصفوفة؟
تُرجع أزواجاً من الموضع والعنصر، تفككها في الحلقة إلى متغيرين.
الخلاصة
Section titled “الخلاصة”sorted { $0.x < $1.x }ترتب الكائنات، وmax(by:)وmin(by:)للأكبر والأصغر.first(where:)وcontainsوallSatisfyللبحث والفحص دون حلقات.enumerated()تضيف رقم الموضع، وzipتربط مصفوفتين، وcompactMapتحذفnil.[key, default: 0]لعدّ التكرارات، وDictionary(grouping:by:)للتجميع.- مبروك! أنهيت كورس Swift. الخطوة التالية: افتح Xcode وابنِ أول تطبيق لك بـ SwiftUI.