Higher Order-Functions
"Funções de Ordem-Superior" (HoF - Higher-Order Functions) são descritas como funções que operam em outras funções, seja tomando-as como argumentos ou retornando-as como resultado. HoF também permitem composição de funções, ou seja, possuir pequenas funções que compõem funcionalidades que realizam tarefas mais extensas e complexas. As Funções de Ordem-Superior são componentes que estão cada vez mais presentes nas linguagens de programação modernas tais como Java, Javascript e R.
Funções de Ordem-Superior em Python
As funções de Ordem-Superior, são uma ferramenta poderosa em Python e podem melhorar significativamente como você constroi seus programas, pois tornam o código mais genérico, flexivel e reutilizável. Elas são parte do paradigma de programação funcional, de forma separada e organizada e apenas compor sub-resultados. As HoF são consideradas "cidadãos de primeira classe" e, portanto, podem ser usadas como qualquer outro tipo de valor.
# Higher-Order Functions
#Função que calcula o dobro de um valor
def dobro
(valor)
:
return
2
*
valor
#Função que calcula o quadrado de um valor
def quadrado
(valor)
:
return
valor
*
valor
#Função que calcula o negativo de um valor
def negativo
(valor)
:
return
-1
*
valor
#Função que aplica a função func ao valor
def mat_func
(func, valor)
:
return
func
(valor)
#Exemplos de Uso
print(mat_func
(dobro
,3
))
6
print(mat_func
(quadrado
,3
))
9
print(mat_func
(negativo
,3
))
-3
Propriedades de funções de Ordem-Superior.
Algumas das propriedades mais importantes das funções de Ordem-Superior que são aplicáveis em Python são:
- Na função de Ordem-Superior, podemos armazenar uma função dentro de uma variável.
- Uma função pode atuar como uma instância de um tipo de objeto.
- Na função de Ordem-Superior, podemos retornar uma função como resultado de outra função.
- Em funções de Ordem-Superior, podemos passar uma função como parâmetro ou argumento dentro de outra função.
- Podemos armazenar funções de Ordem-Superior do Python em formato de estruturas de dados,como listas, tabelas de hash, etc.
Funções de Ordem-Superior aplicadas a listas vazias.
As funções de ordem superior (HoFs) em Python são ferramentas poderosas que podem ser usadas para processar listas de forma eficiente e concisa. No entanto, é importante considerar o comportamento dessas funções quando aplicadas a listas vazias. Devido à sua natureza recursiva, as HoFs geralmente definem um caso base para lidar com listas vazias, garantindo que a função funcione de maneira consistente, mesmo na ausência de elementos.
Map, Filter e Reduce: as HoF mais utilizadas
Obviamente, você pode construir qualquer tipo de função de Ordem-Superior e acoplá-las em suas funcionalidades. Entretanto, três funções ganharam enorme destaque dentro das abordagens funcionais, tanto que foram incluidas na maioria das linguagens de programação com suporte a comportamento funcional: map, filter e reduce. Vamos investigar cada uma delas em mais detalhes abaixo!
MAP
A HoF MAP possui dois parâmetros: a função de transformação, que é aplicada a cada elemento de uma lista, e a lista a ser mapeada. No exemplo abaixo, a função de mapeamento fornecida é aplicada a cada elemento da lista, um por vês, e o elemento transformado é inserido em uma nova lista, que será o resultado da HoF MAP.
Observe que a HoF MAP é imutável: a lista fornecida como parâmetro não é alterada, e uma nova lista é gerada como resultado do mapeamento. Perceba que a cardinalidade da lista resultado também não se altera: se N elementos estão presentes na lista argumento, a lista resultado também possuirá N elementos.Quando aplicada a uma lista vazia, a HoF MAP geralmente retorna uma lista vazia. Isso ocorre porque não há elementos na lista original para aplicar a função de transformação.
Exemplo 1 Map:
# temperaturas em Fahrenheit
temp_fahrenheit = [ 78, 80, 100, 98
]
# função para realizar a conversão de temperatura para Celsius
def celsius
(T)
:
return
round
((float
(5
) / 9
) * (T-32
),2
)
#utilizando a função map para converter cada temperatura da lista
valores
= list
(map
(celsius
,temp_fahrenheit
))
#Saída
print(valores)
[25.56, 26.67, 37.78, 36.67]
Exemplo 2 Map:
# Listas contendo Siglas de alguns Estados Brasileiros
EstadosBR
= [ 'am','ba','ms','rr','to'
]
#utilizando a função map para converter os itens da lista letras maiusculas
EstadosBR
= list
(map
(str.upper
,EstadosBR
))
#Saída
print
(ESTADOSBR)
['AM','BA','MS','RR','TO']
Exemplo 3 Map:
def obter_nome
(email)
:
return email.
split
("@"
)[0
]
emails = [ "joaosilva@email.com", "mariagomes@email.com.br",
"pedrodutra@email.com"
]
nomes = list
(map
(obter_nome
,emails
))
print(nomes)
['joaosilva','mariagomes','pedrodutra']
FILTER
A HoF FILTER testa se cada elemento da lista argumento satisfaz a condição da função parâmetro. Para cada elemento cujo o resultado da função é TRUE, esse elemento é inserido na nova lista de resultado. Portanto, filter retorna um nova lista contendo apenas os elementos que satisfazem a função parâmetro.Quando a lista fornecida como argumento está vazia, a HoF FILTER retorna uma lista vazia.
A HoF FILTER também é imutável: a lista argumento permanece inalterada. Entretanto, a cardinalidade da lista resultado pode variar de 0 a N: se nenhum elemento satisfazer a função de condição, a lista resultado será vazia. Se todos os elementos atendem a função de condição, a lista resultado terá N elementos. Perceba que a função de condição deve avaliar cada elemento da lista, um a um, para um resultado TRUE ou FALSE. Logo, o resultado da função de condição só pode ser Booleana.
Exemplo 1 Filter:
# Criando a função de critério
def maior_que_zero
(numeros)
:
return
numeros
>
0
#Gerando uma lista com valores definidos aleatoriamente
valores
= [ 10, 4, -1 , 3, 5, -9 ,-11
]
#Aplicando filter na lista com valores
valores
= list
(filter
(maior_que_zero
,valores
))
#Saída
print(valores)
[ 10, 4, 3, 5 ]
Exemplo 2 Filter:
# Criando a função de critério
def retornaEstado
(x)
:
return
x
startswith
('M')
# Listas contendo Siglas de alguns Estados Brasileiros
ESTADOSBR
= [ 'MS','AM','BA','MT','TO','MG'
]
# utilizando a função filter para encontrar os estados que iniciam
com a letra M
ESTADOSBR
= list
(filter
(retornaEstado
,EstadosBR
))
#Saída
print(ESTADOSBR)
['MS','MT','MG']
Exemplo 3 Filter:
def eh_gmail
(email)
:
return email.
split
("@"
)[1
] == "gmail.com"
emails = [ "joaosilva@hotmail.com", "mariagomes@gmail.com",
"pedrodutra@outlook.com", "marcos@gmail.com"
]
usuarios_gmail
= filter
(eh_gmail
,emails
)
print(list
(usuarios_gmail)
)
['mariagomes@gmail.com','marcos@gmail.com']
REDUCE
A HoF REDUCE, diferentemente das funções MAP e FILTER, não é nativa no Python, e precisa ser importada de alguma biblioteca (functools). O REDUCE também é diferente pois não retorna (necessariamente) uma lista como resultado: ela pode retornar apenas um valor resultante da aplicação da função de redução a todos os elementos da lista.
Para tanto, REDUCE introduz um novo parâmetro que serve como acumulador para o resultado parcial a cada iteração com os elementos da lista.
A HoF REDUCE também é imutável: a lista argumento permanece inalterada. Para simplificar, podemos assumir que a cardinalidade do REDUCE é 1, pois a lista argumento será reduzida a um único elemento resultante. REDUCE combina elementos de uma lista usando uma função binária. Quando a lista fornecida como argumento está vazia, o comportamento do reduce depende do valor do acumulador. Se nenhum valor for fornecido para o acumulador, uma exceção IndexError é lançada. Caso um valor para o acumulador seja fornecido, este será o resultado da HoF REDUCE.
Exemplo 1 Reduce:
#Importando a função reduce
from functools
import
reduce
#Gerando uma lista com valores definidos aleatoriamente
numeros
= [ 47, 11, 42, 13
]
#criando a função soma
def soma
(acc,elem)
:
x = acc
+ elem
return x
# Aplicando a função reduce na lista.
# 0 é o valor de inicialização do parâmetro acumulador.
valor = reduce
(soma
,numeros
,0
)
#Saída
print(valor)
113
Exemplo 2 Reduce:
#Importando a função reduce
from functools
import
reduce
#criando função para verificar o comprimento
def comparar_comprimento
(nome_a, nome_b)
:
if len(nome_a) > len(nome_b)
:
return nome_a
else:
return nome_b
#nomes de pessoas
nomes = [ "João", "Maria", "Pedro", "Rodrigo Duran","Maria Clara"
]
resultado = reduce
(comparar_comprimento
,nomes
,"")
#Saída
print(resultado)
Rodrigo Duran
Exemplo 3 Reduce:
#Importando a função reduce
from functools
import
reduce
def contar_emails_dominio
(acc,emails)
:
if email.
split
("@"
)[1
] == dominio
:
return acc
+ 1
else:
return acc
dominio = "gmail.com"
emails = [ "joaosilva@gmail.com", "mariagomes@yahoo.com", "marcos@gmail.com",
"pedrodutra@hotmail.com"
]
numero_emails_dominio = reduce
(contar_emails_dominio
,
emails
,0
)
#Saída
print("Número de emails do dominio:"
,dominio
)
print(numero_emails_dominio
)
Número de emails do dominio gmail.com:
2
O PODER DA COMPOSIÇÃO
Encadeando Funções de Ordem-Superior e tornando seu código muito mais enxuto (e legível)!
Compondo Funções de Ordem-Superior
Uma grande vantagem oferecida por Funções de Ordem-Superior é a composição. Como MAP e FILTER
retornam um iteravel, podemos imediatamente encadear uma outra função de Ordem-Superior a partir do resultado da anterior.
Dessa forma, podemos criar funções menores que gerenciam um (ou poucos) objetivos, compondo funções mais complexas utilizando várias funções menores.
Esta técnica pode reduzir o número de bugs e fazer com que o código seja mais fácil de ser compreendido.
Assim, pode-se compor quantas funções achar necessário.
Exemplo 1 Composição de Funções:
produto = [ 'lapis', 'borracha', 'caderno', 'lapiseira', 'caderno'
]
# função que retorna se determinada string contem a substring la
def encontra
(lista
):
return startswith
("la"
)
# função que retorna se uma determinada string tem tamanho maior que 5
def tamanho
(lista
):
return len
(lista
) >
5
resultado = list
(filter
(encontra
,filter
(tamanho
,produto
)))
#Saída
print(resultado
)
['lapiseira'
]
Exemplo 2 Composição de Funções:
# temperaturas em Fahrenheit
temp_fahrenheit = [ 78, 80, 100, 98
]
# função para realizar a conversão de temperatura para Celsius
def celsius
(T)
:
return round
((float
(5
) / 9
) * (T-32
),2
)
# função para encontrar altas temperaturas
def temperaturas_altas
(x
):
return x
>
30
resultado = list
(filter
(temperaturas_altas
,map
(celsius
,temp_fahrenheit
)))
#Saída
print(resultado
)
[37.78, 36.67
]
Exemplo 3 Composição de Funções:
def obter_nome
(email)
:
return email.
split
("@"
)[0
]
def eh_gmail
(email)
:
return email.
split
("@"
)[1
] == "gmail.com"
emails = [ "joaosilva@gmail.com", "mariagomes@yahoo.com.br",
"marcos@gmail.com","pedrodutra@hotmail.com"
]
primeiro_nome_gmail = list
(map
(obter_nome
,filter
(eh_gmail
,emails
)))
print(primeiro_nome_gmail)
['joaosilva','marcos']
Exemplo 4 Composição de Funções:
def eh_hotmail
(email)
:
return email.
split
("@"
)[1
] == "hotmail.com"
def converter_para_outlook
(email)
:
return email.
replace
("hotmail", "outlook"
)
emails = [ "joaosilva@gmail.com", "mariagomes@yahoo.com.br",
"marcos@gmail.com","pedrodutra@hotmail.com"
]
emails_alterados = list
(map
(converter_para_outlook
,filter
(eh_hotmail
,emails
)))
print(emails_alterados)
['pedrodutra@outlook.com']