Lambda em Python – Como usar para ordenar de listas e dicionários

A ordenação deve ser uma das operações mais utilizadas no dia-a-dia de um programador. Todas as linguagens de programação oferecem a funcionalidade de ordenar uma coleção de strings, inteiros ou objetos. As linguagens de programação utilizam diferentes tipos de algoritmo de ordenação em suas funções. A operação de ordenação do Python utiliza o conceito de lambdas para dar mais flexibilidade ao programador permitindo a criação de funções anônimas. Vamos utilizar esta operação em listas de inteiros e dicionários em Python e aproveitar para incrementar ainda mais o nosso software de contatos.

Algoritmos de Ordenação

Existem diversos algoritmos de ordenação e suas diferenças estão na quantidade de memória que utilizam e na velocidade em que executam a ordenação. Este é um assunto complexo então queria apenas dar uma explicação alto nível para que você tenha entendimento do que acontece por debaixo dos panos quando você utiliza uma função de ordenação de uma linguagem.

Vamos imaginar o algoritmo mais simples, chamado de Bubble Sort. Este algoritmo vai comparando pares de números contidos em uma lista e troca eles de lugar caso do da direita seja menor que o da esquerda. Veja o exemplo abaixo:

Primeira passada:
5 1 4 2 8 ) –> ( 1 5 4 2 8 ), como o 1 é menor que 5, ele os troca de posição
( 1 5 4 2 8 ) –>  ( 1 4 5 2 8 ), em seguida vai para o próximo par, como 5 > 4 eles são trocados
( 1 4 5 2 8 ) –>  ( 1 4 2 5 8 ), 5 > 4 por tanto troca
( 1 4 2 5 8 ) –> ( 1 4 2 5 8 ), como o 8 > 5, ou seja, já estão ordenados então não são trocados

Após a primeira passada, só há uma certeza: o maior número está na última posição.

Segunda passada:
1 4 2 5 8 ) –> ( 1 4 2 5 8 )
( 1 4 2 5 8 ) –> ( 1 2 4 5 8 ), 4 > 2 então troca
( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )

Após a segunda passada só há uma certeza: os dois últimos números do array estão ordenados. Se você analisar o resultado acima vai ver que o array já está ordenado, mas o algoritmo não tem como saber isto, então ele precisará fazer  outra passada nos 3 primeiros números para garantir que o número maior sempre é empurrado para o final.

Isto significa que as 3 próximas passadas são ineficientes pois não eram necessárias. Isto não é grande coisa para um array de 5 números, mas e se estivermos falando de milhares?

Aí a importância dos algoritmos de ordenação serem eficientes pois hoje em dia os sistemas lidam com uma quantidade muito grande de dados e este tipo de algoritmo Bubble Sort não dá conta do recado.

A boa notícia é que as linguagens de programação utilizam os algoritmos mais rápidos em suas funções e algumas delas são inteligentes o suficiente para analisar o tamanho do array antes e identificar qual algoritmo de ordenação mais apropriado para a situação.

Ordenação de Listas em Python

Ordenar listas em python é muito simples, toda lista tem uma função sort() que ordena os itens contidos nela. Funciona tanto para números como para strings. Lembrando o que aprendemos no post sobre strings, as letras maiúsculas tem o código ASCII menor e por isto vão aparecer primeiro na lista ordenada.

A função sort() no entanto tem uma característica, ela modifica a lista original. Se for preciso manter a lista original é necessário utilizar a função sorted(). Esta função não faz parte do objeto de listas e pode ser utilizada também em outros tipos de objetos.

lista = [3, 89, 798, 7]

lista.sort()
print(lista) # [3, 7, 89, 798]

ordenada = sorted(lista)
print(lista) # [3, 89, 798, 7]
print(ordenada) # [3, 7, 89, 798]

Ordenação decrescente da lista

E se quisermos ordenar de forma decrescente? Ambas as funções recebem um parâmetro reverse que indica ao python que queremos ordenar do maior para o menor.

lista = [3, 89, 798, 7]

lista.sort(reverse=True)
print(lista) # [798, 89, 7, 3]

ordenada = sorted(lista, reverse=True)
print(lista) # [3, 89, 798, 7]
print(ordenada) # [798, 89, 7, 3]

Modificando os elementos da lista antes de ordenar

Há ainda uma outra funcionalidade nestas funções de ordenação do python, que é o parâmetro key. Ele permite apontar para uma função que será chamada para da elemento da lista antes dela ser ordenada.

Esta função pode ser útil para transformar todos os itens em lowercase antes de ordenar eliminando assim o problema das letras maiúsculas/minúsculas.

palavras = ["Manuel", "marcos", "andré", "Eliana", "carlos"]
ordenado = sorted(palavras)
print(ordenado) # ['Eliana', 'Manuel', 'andré', 'carlos', 'marcos']

ordenado = sorted(palavras, key=str.lower)
print(ordenado) # ['andré', 'carlos', 'Eliana', 'Manuel', 'marcos']

Ordenação de dicionários em Python

Considere o seguinte dicionário:

dicionario = {
  'marca': 'Mercedes', 
  'modelo': 'Classe A', 
  'ano': 2005
}

ordenado = sorted(dicionario)
print(ordenado) # ['ano','marca','modelo']

Como vimos a função sorted() pode ser utilizada em outros tipos de estrutura, como por exemplo dicionários. No entanto há uma limitação: por padrão, a ordenação ocorrerá nas chaves como podemos ver no exemplo acima. Também notamos que o resultado da função é a lista das chaves ordenadas. E se quiséssemos ordenar por valores? Precisaremos utilizar uma versão mais avançada da função key.

Variáveis que apontam para funções

No exemplo de ordenação de listas aprendemos a utilizar o parâmetro key, mas o que faz este parâmetro exatamente? Assim como temos parâmetros que representam variáveis no código, podemos ter parâmetros que apontam para funções. Esses parâmetros se tornam uma variável especial que tem por objetivo guardar uma função em vez de um valor. A função sorted() vai chamar a função que está guardada no argumento key para cada um dos itens que está ordenando.

No exemplo anterior utilizamos a função str.lower para transformar tudo em minúsculo antes de ordenar, mas poderíamos ter criado nossa própria função:

def maiuscula(item):
  return item.upper()

palavras = ["Manuel", "marcos", "andré", "Eliana", "carlos"]

ordenado = sorted(palavras, key=maiuscula)
print(ordenado) # ['andré', 'carlos', 'Eliana', 'Manuel', 'marcos']

A função a ser utiliza precisa receber um item, que corresponde ao item que está sendo ordenado. Para ordenar os itens, o python precise passar por cada um dos itens ao menos uma vez e antes de decidir qual é o lugar correto daquele item na lista final, ele chama a função apontada no parâmetro key.

Lambda – Funções anônimas em Python

Há ainda um outro mecanismo que podemos utilizar no parâmetro key: criar uma função anônima.

As funções anônimas não tem um nome e tem um escopo muito limitado, elas só podem ser utilizadas no contexto onde estão sendo criadas justamente por que não é possível referenciá-las fora deste contexto. Em Python e em outras linguagens as funções anônimas são chamadas de lambda.

Sintaxe de funções lambdas em Python:

# lambda parâmetros : corpo da função

l = lambda a : a + 10
print(l(5)) # 15

Podemos utilizar este tipo de construção quando o código não requer reutilização e em casos onde chamamos funções que recebem parâmetros que referenciam funções, como é o caso do key.

Como ordenar dicionários pelos valores

Uma das maneiras de fazer isto é utilizar o método items() que vimos no post sobre dicionários em python. Esta função retornará uma lista de chave/valor. A função key chamará a função lambda para cada um dos itens da lista, ou seja a dupla chave/valor.

dicionario = {
  'marca': 'Mercedes', 
  'modelo': 'Classe A', 
  'ano': '2005'
}

print(dicionario.items())
# dict_items([('marca', 'Mercedes'), ('modelo', 'Classe A'), ('ano', '2005')])

Assim, utilizamos a função lambda para indicar ao python que queremos ordenar pelo segundo item, que será o valor (item[1]).

dicionario = {
  'marca': 'Mercedes', 
  'modelo': 'Classe A', 
  'ano': 2005
}

ordenado = sorted(dicionario.items(), key= lambda item : item[1])
print(ordenado) # [('ano', '2005'), ('modelo', 'Classe A'), ('marca', 'Mercedes')]

A função lambda recebe um item que no caso no código acima serão os seguintes:

('ano', '2005')
('modelo', 'Classe A')
('marca', 'Mercedes')

Como os valores estão na segunda posição de cada item, devemos indicar [1]. Se quiséssemos ordenar por chave era só passar item[0] para a função lambda.

Como ordenar um dicionário de dicionários em Python

Temos agora a oportunidade de melhorar ainda mais nosso programa de contatos utilizando como base a segunda versão que fizemos no post sobre como lidar com arquivos em python.

Agora que nosso usuário já cadastrou diversos contatos, ele gostaria de listar todos os contatos na tela. No entanto, uma lista de contatos desorganizada não auxilia muito quem está procurando por um nome. Antes de imprimir toda a lista queremos ordená-la.

Supondo a seguinte lista de contatos:

{
   "silvio santos":{
      "nome":"Silvio Santos",
      "telefone":"11 9876-0996",
      "email":"[email protected]"
   },
   "roberta miranda":{
      "nome":"Roberta Miranda",
      "telefone":"66 9986-0909",
      "email":"[email protected]"
   },
   "fausto silva":{
      "nome":"Fausto Silva",
      "telefone":"7898-38984",
      "email":"[email protected]"
   },
   "fernanda montenegro":{
      "nome":"Fernanda Montenegro",
      "telefone":"8090-3434",
      "email":"[email protected]"
   },
   "roberto carlos":{
      "nome":"Roberto Carlos",
      "telefone":"8903-9090",
      "email":"[email protected]"
   },
   "agnaldo raiol":{
      "nome":"Agnaldo Raiol",
      "telefone":"3455-9080",
      "email":"[email protected]"
   }
}

Consegue imagina a melhor função lambda para organizar esta lista em ordem alfabética? Lembrando que queremos ordenar pelo nome do contato. Vamos pensar juntos.

Como vê estamos trabalhando com um dicionário de dicionários. Cada um dos contatos é um dicionário mas usamos o nome em minúsculo como chave de cada um dos contatos. Neste caso não precisaremos criar uma função lambda pois a ordenação padrão do python é por chaves.

ordenados = sorted(contatos)
imprime_contatos(contatos, ordenados)

E se quiséssemos ordenar por telefone? Devemos utilizar a função lambda para indicar ao python que o telefone deve ser considerado para a ordenação. Veja abaixo como fica o código.

ordenado = sorted(contatos, key=lambda contato: contatos[contato]['telefone'])
imprime_contatos(contatos, ordenado)

A função lambda recebe o parâmetro contato, que no caso do dicionário será cada uma das chaves: “silvio santos”, “roberta miranda”, “fausto silva”, etc. Então a função lambda acessa esse contato e devolve o telefone dele para que o python possa utilizá-lo.

Porém a função sorted não retorna o dicionário ordenado, ela retorna uma lista das chaves ordenadas pelo campo que pedimos. No caso:

['silvio santos', 'agnaldo raiol', 'roberta miranda', 'fausto silva', 'fernanda montenegro', 'roberto carlos']

E então nossa função imprime_contatos é capaz de imprimir os itens na ordem em que aparecem nesta lista.

+---------------------+--------------+-------------------------------+
| Nome                | Telefone     | E-mail                        |
+=====================+==============+===============================+
| Silvio Santos       | 11 9876-0996 | [email protected]             |
+---------------------+--------------+-------------------------------+
| Agnaldo Raiol       | 3455-9080    | [email protected]          |
+---------------------+--------------+-------------------------------+
| Roberta Miranda     | 66 9986-0909 | [email protected]        |
+---------------------+--------------+-------------------------------+
| Fausto Silva        | 7898-38984   | [email protected]             |
+---------------------+--------------+-------------------------------+
| Fernanda Montenegro | 8090-3434    | [email protected] |
+---------------------+--------------+-------------------------------+
| Roberto Carlos      | 8903-9090    | [email protected]             |
+---------------------+--------------+-------------------------------+

Para incorporar esta lógica no nosso programa de contatos criaremos uma opção nova no menu para listar os contatos.

comando = input("Digite o comando: (novo, pes, list, sair):")

if comando == "list":
    ordenado = sorted(contatos, key=lambda contato: contatos[contato]['telefone'])
    imprime_contatos(contatos, ordenado)

Versão final: https://repl.it/@julianajuliano/Contatos-v3