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

المجموعات في Swift: الترتيب والبحث والتجميع

الدرس 11 من 11 · 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: "تونس"),
]

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:) تُرجعان قيمة اختيارية، لأن المصفوفة قد تكون فارغة.
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)
الناتج
كريم
true
true
2
2
الدالة السؤال الذي تجيب عنه
first(where:) ما أول عنصر يحقق الشرط؟ (قيمة اختيارية)
contains هل يوجد عنصر واحد على الأقل يحققه؟
allSatisfy هل كل العناصر تحققه؟
firstIndex ما موضع أول عنصر يحققه؟ (قيمة اختيارية)

كثيراً ما تحتاج رقم العنصر مع العنصر نفسه. 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

القراءة من القاموس تُرجع قيمة اختيارية كما تعلمت. الصيغة [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)
الناتج
3
2
0

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

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 = 0
for (_, price) in prices {
if price > 6 {
total += price
}
}
print(total)
print(prices.values.reduce(0, +))
print(prices.filter { $0.value < 10 }.count)
الناتج
22
27
2

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

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 التي عرفتها تدعم العمليات الرياضية المعروفة:

let football: Set = ["علي", "منى", "زياد"]
let swimming: Set = ["منى", "هدى", "علي"]
print(football.intersection(swimming).sorted())
print(football.subtracting(swimming).sorted())
print(football.union(swimming).count)
الناتج
["علي", "منى"]
["زياد"]
4

intersection للمشترك، و subtracting لما في الأولى وليس في الثانية، و union للكل. واستخدمنا sorted() لأن المجموعة بلا ترتيب.

  1. أنشئ struct Book فيه title و pages و author، ومصفوفة من خمسة كتب.
  2. اطبع عناوين الكتب مرتبة حسب عدد الصفحات، مع رقم كل كتاب باستخدام enumerated().
  3. اطبع عنوان أول كتاب تتجاوز صفحاته 300 باستخدام first(where:)، أو رسالة مناسبة إن لم يوجد.
  4. تحدٍّ: جمّع الكتب حسب المؤلف بـ Dictionary(grouping:by:)، واطبع لكل مؤلف مجموع صفحات كتبه.
  • انتظار ترتيب من القاموس: ترتيب 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] نظيفة.

سؤال

ما ناتج ["3", "x", "5"].compactMap { Int($0) }؟

سؤال

ماذا تعطيك enumerated() عند المرور على مصفوفة؟

  • 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.