الخوارزميات وهياكل البيانات في Swift: من النظرية إلى المشاريع الحقيقية لتطبيقات iOS
\nSwift ليست مجرد لغة برمجة حديثة وآمنة للنظام البيئي لـ Apple. إنها أداة قوية تتيح كتابة كود فعال وعالي الأداء لتطبيقات iOS وmacOS وwatchOS وtvOS. ومع ذلك، لإنشاء تطبيقات سريعة وقابلة للتوسع حقًا، لا يكفي معرفة القواعد النحوية فقط. فهم الخوارزميات وهياكل البيانات الأساسية هو ما يميز المطور المبتدئ عن المهندس الخبير.
\nفي هذه المقالة، سنحلل هياكل البيانات والخوارزميات الرئيسية بلغة Swift مع أمثلة عملية. سترى كيفية تنفيذ الكومة (Stack)، قائمة الانتظار (Queue)، البحث الثنائي والفرز، وستفهم أين تُطبق هذه المعرفة في التطوير الفعلي لتطبيقات iOS. هيا بنا!
\n\n1. الكومة (Stack) وقائمة الانتظار (Queue): هياكل أساسية
\nالكومة وقائمة الانتظار هما هيكلان أساسيان للبيانات يُستخدمان غالبًا في تطوير iOS. على سبيل المثال، تُستخدم الكومة للتنقل في UINavigationController، بينما تُستخدم قائمة الانتظار لإدارة المهام في OperationQueue أو DispatchQueue.
تنفيذ الكومة (LIFO — Last In, First Out)
\nتعمل الكومة على مبدأ "آخر من يدخل هو أول من يخرج". تحدث إضافة العنصر (push) والحذف (pop) من نفس الطرف — قمة الكومة.
\nstruct Stack<Element> {\n private var storage: [Element] = []\n \n mutating func push(_ element: Element) {\n storage.append(element)\n }\n \n @discardableResult\n mutating func pop() -> Element? {\n return storage.popLast()\n }\n \n func peek() -> Element? {\n return storage.last\n }\n \n var isEmpty: Bool {\n return storage.isEmpty\n }\n}\n\n// مثال الاستخدام\nvar navigationStack = Stack<String>()\nnavigationStack.push("الشاشة الرئيسية")\nnavigationStack.push("تفاصيل المنتج")\nnavigationStack.push("سلة التسوق")\nprint(navigationStack.pop() ?? "") // سيطبع: "سلة التسوق"\nprint(navigationStack.peek() ?? "") // سيطبع: "تفاصيل المنتج"\nأين تُطبق: التراجع عن العمليات (Undo/Redo)، التحقق من صحة الأقواس في المحللات، اجتياز الشجرة بالعمق (DFS).
\n\nتنفيذ قائمة الانتظار (FIFO — First In, First Out)
\nتعمل قائمة الانتظار على مبدأ "أول من يدخل هو أول من يخرج". تُضاف العناصر إلى النهاية (enqueue) وتُحذف من البداية (dequeue).
\nstruct Queue<Element> {\n private var storage: [Element] = []\n \n mutating func enqueue(_ element: Element) {\n storage.append(element)\n }\n \n @discardableResult\n mutating func dequeue() -> Element? {\n guard !storage.isEmpty else { return nil }\n return storage.removeFirst()\n }\n \n func peek() -> Element? {\n return storage.first\n }\n \n var isEmpty: Bool {\n return storage.isEmpty\n }\n}\n\n// مثال الاستخدام\nvar taskQueue = Queue<String>()\ntaskQueue.enqueue("تحميل الصور")\ntaskQueue.enqueue("مزامنة البيانات")\ntaskQueue.enqueue("إرسال التحليلات")\nprint(taskQueue.dequeue() ?? "") // سيطبع: "تحميل الصور"\nأين تُطبق: معالجة طلبات الشبكة، اجتياز الشجرة بالعرض (BFS)، قوائم انتظار المهام في الخيوط الخلفية.
\n\n2. البحث الثنائي: السرعة مقابل البساطة
\nالبحث الثنائي هو خوارزمية كلاسيكية تجد عنصرًا في مصفوفة مرتبة بزمن لوغاريتمي O(log n). في iOS، يُستخدم غالبًا عند التعامل مع مجموعات بيانات كبيرة مرتبة، مثل البحث عن جهات الاتصال أو السجلات في قاعدة البيانات.
\nfunc binarySearch<T: Comparable>(in array: [T], target: T) -> Int? {\n var lowerBound = 0\n var upperBound = array.count - 1\n \n while lowerBound <= upperBound {\n let midIndex = (lowerBound + upperBound) / 2\n let midValue = array[mid