Algorithmes et structures de données en Swift : de la théorie aux projets iOS réels
\nSwift n'est pas seulement un langage de programmation moderne et sécurisé pour l'écosystème Apple. C'est un outil puissant qui permet d'écrire un code efficace et performant pour iOS, macOS, watchOS et tvOS. Cependant, pour créer des applications vraiment rapides et évolutives, la seule connaissance de la syntaxe ne suffit pas. Comprendre les algorithmes et structures de données fondamentaux est ce qui distingue un développeur junior d'un ingénieur senior.
\nDans cet article, nous analyserons les structures de données et algorithmes clés en langage Swift avec des exemples pratiques. Vous verrez comment implémenter une pile, une file d'attente, une recherche binaire et un tri, et vous comprendrez où ces connaissances sont appliquées dans le développement réel d'applications iOS. C'est parti !
\n\n1. Pile (Stack) et file d'attente (Queue) : structures fondamentales
\nLa pile et la file d'attente sont deux structures de données de base souvent utilisées dans le développement iOS. Par exemple, la pile est utilisée pour la navigation dans UINavigationController, et la file d'attente pour la gestion des tâches dans OperationQueue ou DispatchQueue.
Implémentation de la pile (LIFO — Last In, First Out)
\nLa pile fonctionne sur le principe "dernier arrivé, premier sorti". L'ajout d'un élément (push) et la suppression (pop) se font à une seule extrémité — le sommet de la pile.
\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// Exemple d'utilisation\nvar navigationStack = Stack<String>()\nnavigationStack.push("Écran principal")\nnavigationStack.push("Détails du produit")\nnavigationStack.push("Panier")\nprint(navigationStack.pop() ?? "") // Affiche : "Panier"\nprint(navigationStack.peek() ?? "") // Affiche : "Détails du produit"\nOù est-ce appliqué : Annulation d'opérations (Undo/Redo), vérification de la validité des parenthèses dans les analyseurs syntaxiques, parcours en profondeur d'un arbre (DFS).
\n\nImplémentation de la file d'attente (FIFO — First In, First Out)
\nLa file d'attente fonctionne sur le principe "premier arrivé, premier sorti". Les éléments sont ajoutés à la fin (enqueue) et supprimés du début (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// Exemple d'utilisation\nvar taskQueue = Queue<String>()\ntaskQueue.enqueue("Téléchargement d'images")\ntaskQueue.enqueue("Synchronisation des données")\ntaskQueue.enqueue("Envoi d'analytiques")\nprint(taskQueue.dequeue() ?? "") // Affiche : "Téléchargement d'images"\nOù est-ce appliqué : Traitement des requêtes réseau, parcours en largeur d'un arbre (BFS), files d'attente de tâches dans les threads d'arrière-plan.
\n\n2. Recherche binaire : vitesse contre simplicité
\nLa recherche binaire est un algorithme classique qui trouve un élément dans un tableau trié en temps logarithmique O(log n). Dans iOS, elle est souvent utilisée pour travailler avec de grands ensembles de données triés, par exemple lors de la recherche de contacts ou d'enregistrements dans une base de données.
\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