Olá, estudante, tudo bem com você? Nesta lição, daremos sequência ao conteúdo sobre Python. Você aprendeu na lição anterior sobre recursividade, e agora, nosso objetivo é fornecer uma compreensão sólida dos tipos e estruturas de dados em Python. Exploraremos os tipos de dados mais conhecidos e oferecidos pela Python, como Frozensets, pilhas e filas. Também compreenderemos como usá-los eficazmente em seus programas. Vale ressaltar que listas, tuplas, conjuntos e dicionários também são estruturas de dados, entretanto não serão trabalhadas aqui, visto que foram apresentadas na lição anterior desta disciplina.
Ao final desta lição, você estará pronto(a) para escolher os tipos e estruturas de dados certos aos seus projetos e utilizará esse conhecimento para escrever um código Python mais eficiente e funcional. Vamos lá?
As estruturas de dados, como pilhas, desempenham um papel fundamental na resolução de muitos problemas do dia a dia no desenvolvimento de software. Um problema comum resolvido eficazmente com o uso de pilhas é o rastreamento de chamadas de funções, ou, em termos mais técnicos, o controle de fluxo de um programa. Cada vez que uma função é chamada, os detalhes, como parâmetros e variáveis locais, são empilhados na pilha de chamadas. À medida que a função é executada e suas chamadas recursivas são feitas, mais informações são empilhadas. Isso cria uma estrutura de pilha cuja função mais recente fica no topo. Quando ocorre um erro ou uma exceção, o rastreamento das chamadas de função permite identificar qual função causou o erro e como ela foi chamada. Com a pilha, você pode rastrear a origem do erro até a função que o desencadeou.
As pilhas são uma estrutura de dados eficaz para rastrear o fluxo de chamadas de funções em situações de programação complexas. Elas garantem que os desenvolvedores possam localizar rapidamente a origem de erros e problemas, economizando tempo e melhorando a qualidade do software. Portanto, o uso de pilhas é uma prática valiosa no desenvolvimento de software, especialmente em linguagens de programação que suportam funções aninhadas e chamadas recursivas.
No case fictício de hoje, você conhecerá a história da empresa Techlogix, especializada no desenvolvimento de software de logística e gerenciamento de estoque. Durante um projeto para otimizar a gestão de inventário de uma rede de supermercados, a equipe se deparou com a seguinte necessidade: garantir que os produtos fossem entregues na ordem correta. Isso significava que os produtos os quais expirassem primeiro precisavam ser entregues antes dos outros.
Diante da necessidade, a equipe de desenvolvimento precisou criar um sistema que organizasse os produtos em fila, com base na data de validade e, em seguida, os entregasse na ordem correta. Para isso, a equipe decidiu usar pilhas e filas. Assim, foi criada uma estrutura de dados que consistia em:
Pilha de recebimento: quando os produtos chegavam ao centro de distribuição, eles eram empilhados em uma pilha com base na data de validade. Os produtos com prazos de validade mais curtos eram colocados no topo da pilha.
Fila de entrega: quando os pedidos eram preparados para entrega, eles eram organizados em uma fila de prioridade, na qual os produtos com prazos de validade mais curtos tinham prioridade.
Note que este case fictício destaca como o uso de pilhas e filas desempenhou um papel crucial no desenvolvimento de um software de logística eficaz, dentro do que foi solicitado, otimizando o gerenciamento de estoque e melhorando a eficiência operacional de uma empresa de varejo.
Como mencionado no início desta lição, as estruturas de dados listas, tuplas, dicionários e conjuntos já foram vistas, de forma particular, na lição anterior, por isso, não serão abordadas agora. Sendo assim, nesse momento, nos aprofundaremos em outras estruturas.
Um frozenset é um tipo de dado que representa um conjunto imutável. A semelhança entre um frozenset e um set (visto na lição anterior) reside no fato de ambos serem estruturas de dados que representam conjuntos. Em outras palavras, tanto um frozenset quanto um set podem armazenar coleções de elementos únicos, onde a ordem dos elementos não é importante. No entanto a diferença principal entre eles é que um frozenset é imutável, ou seja, seus elementos não podem ser modificados após a criação do frozenset, enquanto um set é mutável, permitindo adições, remoções e alterações de elementos. Portanto, apesar de representarem conjuntos, a principal distinção entre um frozenset e um set é a sua mutabilidade: o primeiro é imutável, enquanto o segundo é mutável.
Ramalho (2015) diz que um frozenset é sempre hashable, porque seus elementos devem ser hashable por definição. Hashable, por sua vez, refere-se a um objeto que possui um valor hash associado, como tipos numéricos, strings, tuplas e o próprio frozenset. Sendo assim, um frozenset é uma coleção imutável de elementos únicos, semelhante a um conjunto, mas ao contrário do conjunto, não pode ser modificado após sua criação.
Segundo Ramalho (2015), não existe uma sintaxe especial para representar literais frozenset, você só precisa usar a função frozenset() e passar uma sequência (como uma lista ou uma tupla) como argumento. Exemplo: frz_set = frozenset([1, 2, 3, 4]).
Sua aplicabilidade é útil em cenários cuja imutabilidade é desejada, por exemplo, ao criar conjuntos que serão usados como chaves de dicionários ou quando a prevenção de modificações acidentais é necessária. Assim, ao optar por usar um frozenset, você garante que a coleção permaneça imutável, o que pode ser útil em situações nas quais a mutabilidade é indesejada.
Uma pilha é uma estrutura de dados linear que segue o princípio Last In, First Out (LIFO), que significa: o último elemento adicionado à pilha é o primeiro a ser removido. Menezes (2017) indica que uma pilha tem uma política bem definida: novos elementos são adicionados ao topo. Da mesma forma, durante a retirada, esse princípio é mantido, sendo realizada a remoção a partir do topo. Essa política é semelhante a uma pilha de pratos, ou seja, você adiciona um prato ao topo e remove o último prato que foi adicionado primeiro.
Essa estrutura de dados tem duas operações principais, sendo elas o push e o pop. O push adiciona um elemento ao topo da pilha (em Python, usamos o append), enquanto o pop remove um elemento do topo. Quando dizemos “topo da pilha”, precisamos entender que ele é o elemento adicionado mais recentemente que ainda não foi removido. Além disso, vale ressaltar: pilhas, geralmente, armazenam elementos de um tipo específico de dados, o que a torna uma estrutura homogênea.
O conceito fundamental a ser compreendido sobre uma pilha é: apenas o elemento no topo pode ser acessado. Para alcançar os elementos abaixo do topo, é necessário remover os elementos superiores. Essa restrição é essencial para manter o princípio LIFO mencionado.
Especificamente em Python, você pode implementar uma pilha usando uma lista. A função append() é usada para adicionar elementos ao topo (fazendo o papel de push), e pop() é usada para remover o elemento do topo. Observe a Figura 1:
Observe que, na linha 1, criamos a pilha através da lista, nas linhas 3 a 5, adicionamos, respectivamente, 1, 2 e 3 na pilha, e na linha 7, removemos o topo (3) da pilha e o armazenamos na variável topo.
Agora, compreendendo como as coisas funcionam, você deve estar pensando: mas para que irei usar pilhas em meus softwares? Pois bem, lhe darei algumas utilizações comuns para pilhas:
Desfazer e refazer em aplicações: mantendo um histórico de ações, as pilhas são usadas para implementar desfazer e refazer, o famoso (CTRL + Z).
Avaliação de expressões matemáticas: as pilhas são utilizadas para avaliar expressões matemáticas, especialmente as pós-fixas.
Controle de chamadas de funções: em linguagens de programação, a pilha de chamadas é uma estrutura fundamental para rastrear a execução de funções.
Reversão de sequências: ao inverter uma sequência, como uma string ou lista, uma pilha pode ser utilizada.
Uma fila é uma estrutura de dados que segue o princípio “primeiro a entrar, primeiro a sair” (First-In-First-Out, ou FIFO). É semelhante a uma fila na vida real, onde a pessoa que chega primeiro é atendida primeiro.
Menezes (2017) nos dá o exemplo das filas de banco. Quando a agência abre pela manhã, a fila está vazia. Quando os clientes começam a chegar, eles vão diretamente para o fim da fila. Os caixas, então, começam a atender esses clientes por ordem de chegada, ou seja, o cliente que chegou primeiro será atendido primeiro. Em termos de programação, uma fila é usada para organizar itens de maneira que o primeiro item inserido seja o primeiro a ser removido. As características de uma fila são:
Inserção e remoção ordenada: elementos são adicionados no final da fila (chamado de “enfileirar” ou “enqueue”) e removidos do início dela (chamado de “desenfileirar” ou “dequeue”). Essa característica garante que o primeiro elemento inserido seja o primeiro a ser removido.
Estrutura linear: uma fila é uma estrutura de dados linear, ou seja, os elementos são organizados em uma única linha.
Além das características, é importante que você compreenda as operações principais dessa estrutura de dados. Acompanhe-as, a seguir:
Enfileirar (enqueue): adiciona um elemento ao final da fila.
Desenfileirar (dequeue): remove o elemento do início da fila.
Frente: retorna o elemento no início da fila, sem removê-lo.
Vazia: verifica se a fila está vazia.
Tamanho: retorna o número de elementos na fila.
Especificamente em Python, a biblioteca padrão fornece a classe queue, a qual inclui uma implementação de fila, chamada queue. Na Figura 2 está um exemplo básico de como usar uma fila em Python:
Vamos compreender linha a linha?
Na linha 1, é realizada a importação da biblioteca Queue.
Na linha 3, é instanciado um objeto fila do tipo Queue.
Nas linhas 5, 6 e 7, são adicionados os elementos 10, 20 e 30 na fila.
Na linha 9, é removido o elemento 10, porque foi o primeiro a entrar na fila.
Na linha 11, é armazenado, na variável tamanho, o número 2, que é o tamanho da fila após a retirada da linha 9.
Na linha 13, é armazenado, na variável esta_vazia, o false, pois a fila não se encontra vazia.
A Python oferece uma ampla gama de tipos de dados para atender às necessidades dos desenvolvedores em diversas aplicações. Nesta lição, conhecemos os frozensets, que são conjuntos imutáveis, ideais para situações nas quais a imutabilidade é essencial, como ao criar chaves de dicionários ou para garantir a integridade de dados. Vimos também as pilhas, as quais são úteis para operações como desfazer/refazer em aplicações e controle de chamadas de funções. Por fim, as filas, que são valiosas para operações como processamento de tarefas em ordem de chegada, garantindo uma execução sequencial organizada.
Esses tipos de dados são apenas algumas das ferramentas poderosas oferecidas pela Python, os quais destacam-se pela sua simplicidade, eficiência e versatilidade no desenvolvimento de uma variedade de aplicações.
Compreender conceitos como frozensets, pilhas e filas é essencial aos desenvolvedores, pois essas estruturas de dados são projetadas para resolver problemas específicos de forma eficiente, permitindo que os profissionais escolham a ferramenta certa para cada situação. Além disso, utilizar a estrutura de dados adequada pode resultar em melhor desempenho do código, tornando-o mais rápido e eficiente. O conhecimento sobre essas estruturas também faz parte das boas práticas de programação e, consequentemente, leva a um código mais claro e organizado. Portanto, dominar conceitos como frozensets, pilhas e filas é essencial para o seu sucesso como desenvolvedor(a).
Agora, que tal implementar o uso de pilhas em Python? Siga o passo a passo:
Abra seu navegador web e acesse o site OnlineGDB, em https://www.onlinegdb.com.
Escolha a linguagem de programação que deseja usar. Selecione “Python” na lista suspensa.
No editor de código, você pode escrever o código Python conforme a Figura 3.
Saiba, agora, o que o código da Figura 3 realiza:
Linha 1: declara a classe pilha que será utilizada nesse código.
Linhas 2 e 3: declaram um array de itens vazio no construtor da classe.
Linhas 5 e 6: declaram método vazia() que retorna boolean se itens estão vazios.
Linhas 8 e 9: declaram o método empilhar() que adiciona o item do argumento ao array.
Linhas 11 a 15: declaram o método desempilhar() que verifica se a lista não está vazia primeiro, antes de desempilhar() o item, ou, caso esteja vazia, retorna None.
Linhas 17 a 20: instanciam um objeto do tipo pilha e empilham os números 1, 2 e 3, respectivamente.
Linhas 22 e 23: enquanto a lista não estiver vazia, imprimirá na tela o texto “Item desempilhado” e o item daquela posição.
Clique no botão “Run” para executar o código. Os resultados dos exemplos serão exibidos na janela de saída do OnlineGDB.
Sinta-se à vontade para experimentar e explorar as filas e frozensets vistos nesta lição. Você pode adicionar, modificar ou remover elementos para entender melhor como as pilhas funcionam em Python. Desafie seus colegas e professores a implementar as funcionalidades vistas no case desta lição!
MENEZES, N. N. C. Introdução à Programação com Python: algoritmos e lógica de programação para iniciantes. 2. ed. São Paulo: Novatec, 2017.
RAMALHO, L. Python fluente: programação clara, concisa e eficiente. São Paulo: Novatec, 2015.