Algoritmos e Estruturas de Dados em Swift: da Teoria aos Projetos Reais iOS
\nSwift não é apenas uma linguagem de programação moderna e segura para o ecossistema Apple. É uma ferramenta poderosa que permite escrever código eficiente e performático para iOS, macOS, watchOS e tvOS. No entanto, para criar aplicativos verdadeiramente rápidos e escaláveis, apenas conhecer a sintaxe não é suficiente. Compreender os algoritmos e estruturas de dados fundamentais é o que diferencia um desenvolvedor júnior de um engenheiro sênior.
\nNeste artigo, vamos analisar as principais estruturas de dados e algoritmos em Swift com exemplos práticos. Você verá como implementar pilha, fila, busca binária e ordenação, além de entender onde esses conhecimentos são aplicados no desenvolvimento real de aplicativos iOS. Vamos lá!
\n\n1. Pilha (Stack) e Fila (Queue): Estruturas Fundamentais
\nPilha e fila são duas estruturas de dados básicas frequentemente usadas no desenvolvimento iOS. Por exemplo, a pilha é usada para navegação em UINavigationController, e a fila, para gerenciar tarefas em OperationQueue ou DispatchQueue.
Implementação da Pilha (LIFO — Last In, First Out)
\nA pilha funciona pelo princípio "o último a entrar é o primeiro a sair". A adição de um elemento (push) e a remoção (pop) ocorrem em uma mesma extremidade — o topo da pilha.
\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// Exemplo de uso\nvar navigationStack = Stack<String>()\nnavigationStack.push("Tela Principal")\nnavigationStack.push("Detalhes do Produto")\nnavigationStack.push("Carrinho")\nprint(navigationStack.pop() ?? "") // Exibe: "Carrinho"\nprint(navigationStack.peek() ?? "") // Exibe: "Detalhes do Produto"\nOnde é aplicado: Desfazer/Refazer operações (Undo/Redo), verificação de parênteses em parsers, travessia em profundidade (DFS) em árvores.
\n\nImplementação da Fila (FIFO — First In, First Out)
\nA fila funciona pelo princípio "o primeiro a entrar é o primeiro a sair". Os elementos são adicionados ao final (enqueue) e removidos do início (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// Exemplo de uso\nvar taskQueue = Queue<String>()\ntaskQueue.enqueue("Carregar Imagens")\ntaskQueue.enqueue("Sincronizar Dados")\ntaskQueue.enqueue("Enviar Analytics")\nprint(taskQueue.dequeue() ?? "") // Exibe: "Carregar Imagens"\nOnde é aplicado: Processamento de requisições de rede, travessia em largura (BFS) em árvores, filas de tarefas em threads em segundo plano.
\n\n2. Busca Binária: Velocidade versus Simplicidade
\nA busca binária é um algoritmo clássico que encontra um elemento em um array ordenado em tempo logarítmico O(log n). No iOS, é frequentemente aplicada ao trabalhar com grandes conjuntos de dados ordenados, como na busca de contatos ou registros em um banco de dados.
\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