Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Roteiro) Funções de ordem superior #18

Closed
dunossauro opened this issue Mar 21, 2017 · 1 comment
Closed

(Roteiro) Funções de ordem superior #18

dunossauro opened this issue Mar 21, 2017 · 1 comment
Labels

Comments

@dunossauro
Copy link
Owner

roteiro

@ghost
Copy link

ghost commented Mar 23, 2017

Funções de ordem superior

Você deve achar que esquecemos muitas funções embutidas no vídeo passado, não? Funções como:

  • map()
  • max()
  • min()
  • iter()
  • sorted()
  • filter()

Porém, essas funções têm características especiais. Como assim? Elas podem receber além do iterável, uma outra função como argumento. Vamos lá. Você já foi introduzido ao map no vídeo passado.

map()

A função map(), fazendo um gancho com o vídeo anterior, é uma função de mapeamento, contudo, ela recebe o iterável em conjunto a uma função, a que fará o mapeamento. Vamos lá:

def func(x):
    """
    Exemplo do vídeo passado
    """
    return x +2

list(map(func, [1,2,3])) # [3, 4, 5]

A função que chamamos de func() é uma função extremamente simples, retorna a entrada somada com 2, simples assim. Um ponto que vale a pena ser tocado é que as funções usadas por map só podem receber um argumento. Por que? A função map vai pegar um elemento da sequência e aplicar a função. Só isso, sério.

Agora vamos complicar as coisas um pouco mais....

# Uma lista de listas, isso em python também é uma matriz
lista = [[1,2], [2,3], [3,4], [4,5], [5,6]]

def func(x):
    """
    Retorna a mesma coisa que entrou
    """
    return x

list(map(func, lista))

Você concorda comigo que a entrada não é exatamente um elemento único, mas uma sequência?

Então, como temos uma lista agora, podemos fazer coisas de lista? Aplicar outras funções de iteráveis? Vamos lá:

# Uma lista de listas, isso em python também é uma matriz
lista = [[1,2], [2,3], [3,4], [4,5], [5,6]]

def func_rev(x):
    """
    Retorna a lista que entrou, porém invertida
    """
    return list(reversed(x))

list(map(func_rev, lista)) # [[2, 1], [3, 2], [4, 3], [5, 4], [6, 5]]

Isso, isso, isso. Olha como a coisa está ficando linda? O que fizemos agora é uma composição de funções, mas dentro de um map. A notação matemática disso, caso você tenha curiosidade em saber é:

uma função comum = f(x)

composição de funções = f(g(x))

Bom, agora você aprendeu o poder do map(), podemos viajar entre a outra gama de funções de ordem superior que o python oferece. Porém, fica o adendo teórico:

Funções de ordem superior são funções que recebem, como argumento, ou retornam outra funções.

Viu, foi simples.

max()

A função max é uma função de redução, e sem a função como parâmetro, ela vai ter o comportamento das funções que vimos no outro vídeo.

max([1, 2, 3, 4, 5]) # 5

Só que... (e... lá vem)

Se essa lista for uma lista de listas, como prosseguir?

lista = [[1,2], [2,3], [3,4], [4,5], [5,6]]

max(lista) # [5, 6]

É, ainda está funcionando.

lista = [[7,2], [5,3], [5,4], [5,5], [5,6]]

max(lista) # [7, 2]

[7, 2] é um bom resultado, mas vamos pensar que o que eu queria eram as somas dois dois elementos, nesse caso o resultado veio errado. 7 + 2 = 9 quando 5 + 6 = 11. Vamos tentar outra vez:

lista = [[7,2], [5,3], [5,4], [5,5], [5,6]]

def func(x):
    return x[0] + x[1]

max(lista, key=func) # [5,6]

Viu, esse argumento key pode ser uma mão na roda em muitos dias tristes em que você está sem vontade de cantar uma bela canção.

lista = [[7,2], [5,3], [5,4], [5,5], [5,6]]

max(lista, key=sum) # [5, 6]

Como você já sabe compor funções, vamos imaginar que nossa sequência de entrada poderia ser maior que dois elementos, uma maneira bonita de fazer isso seria usar o sum(). Fica muito mais elegante.

min()

Agora que já entendemos o conceito das HOFs, tudo fica mais simples. A função min() é a função equivalente a max(). Quando a max pega o maior item da sequência, min pega o menor.

lista = [[7,2], [5,3], [5,4], [5,5], [5,6]]

min(lista, key=sum) # [5, 3]

Não temos muito mais o que falar sobre min, é só um complemento.

iter()

A função embutida iter() tem duas formas, a primeira devolve o iterável de uma sequência.

lista = [1, 2, 3, 4, 5]

iter(lista) # <list_iterator at xpto>

Ele faz a chamada do método __iter__() do objeto. Até então nenhuma novidade. Vamos supor que isso seja um facilitador para transformar nossos objetos em sequências imutáveis e preguiçosas.

Porém, a segunda forma é bem interessante.

"""
Exemplo roubado do Steven Lott
"""
lista = [1, 2, 3, 4, 5]

list(iter(lista.pop, 3)) #[5, 4]

Vamos por partes que agora vem muita informação pra pouca linha de código.

vamos dar um help() em iter:

help(iter)

iter(...)
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator

    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.

Então, quer dizer que o callable é chamado até que o retorno seja o sentinela. Como passamos como callable pop e se pop for chamado sem argumentos, ele retorna o último elemento da lista e retira ele da mesma. Nesse caso o sentinela é 3. Então ele vai desmontando a lista e gerando um novo iterável de tudo que foi removido da lista anterior.

Ou seja, vamos fazer um mapinha básico:

(1) lista = [1, 2, 3, 4, 5]

(2) saida = []

(3) saida.append(lista.pop())

(4) print(saida) # [5]

Agora que o pop_append ficou claro. Deu pra entender o que faz a segunda forma da função iter()? Sim, deu.

Então vamos explorar um exemplo mais eficiente, o da documentação:

with open('mydata.txt') as fp:
    for line in iter(fp.readline, ''):
        print(line) # só essa linha foi modificada

O método readline, quando passado sem parâmetros efetua a leitura de um único caracter. Nesse caso ele printaria uma letra do arquivo por linha. Mas, como usamos iter(fp.readline, '') ele vai nos retornar uma sequência de strings até que o sentinela que no caso é vazio apareça.

Ou seja, é passado um objeto com um método no lugar de uma função. O método tem suas particularidades como não precisar de argumentos e agir no objeto em si. Isso parece óbvio, porém, quando construímos nossas próprias classes, o retorno pode não ser o esperado, como nas sequências embutidas do python.

sorted()

Para os viciados em listas, como eu, o método sort da lista funciona bem, apesar de ordenar a lista e não trazer uma nova lista, o que as vezes é uma dor de cabeça.

lista = [1, 2, 3, 3, 2, 1]

lista.sort()

print(lista) # [1, 1, 2, 2, 3, 3]

Para agradar a todos, temos a função embutida sorted() assim como as outras HOFs temos o parâmetro opcional key e podemos decidir como a ordenação será feita.

Vamos pensar em uma tupla de tuplas, uma saída de um banco, por exemplo:

autores = (('Fernando Pessoa', 17, 'Portugal'),
           ('Carlos Drummond Andrade', 14, 'Brasil'),
           ('Nenê Altro', 4, 'Brasil'))

Agora vamos ordenar:

sorted(autores)

#[('Carlos Drummond Andrade', 14, 'Brasil'),
# ('Fernando Pessoa', 17, 'Portugal'),
# ('Nenê Altro', 4, 'Brasil')]

Até então, tudo certo. Ele ordenou pelo index 0 da tupla, que nesse caso eram os nomes.
Vamos tentar usar a magia da key agora:

sorted(autores, key=lambda x: x[1])

#[('Nenê Altro', 4, 'Brasil'),
# ('Carlos Drummond Andrade', 14, 'Brasil'),
# ('Fernando Pessoa', 17, 'Portugal')]

Nesse caso, a ordenação foi dada pelo index 1, que foi o que nós determinamos na função lambda, vamos tentar mais um caso:

sorted(autores, key=lambda x: x[0][-1])

#[('Fernando Pessoa', 17, 'Portugal'),
# ('Carlos Drummond Andrade', 14, 'Brasil'),
# ('Nenê Altro', 4, 'Brasil')]

Nesse caso ele fez a ordenação pelo index 0, só que invertido.

Mas não paramos por aí. sorted() ainda tem mais um argumento escondido reverse, que por padrão vem sempre false.
Mas podemos pedir o True dele:

sorted(autores, key=lambda x: x[0][-1], reverse=True)

#[('Nenê Altro', 4, 'Brasil'),
# ('Carlos Drummond Andrade', 14, 'Brasil'),
# ('Fernando Pessoa', 17, 'Portugal')]

E agora obtivemos o mesmo resultado, só que invertido.

Só pra não dizer que não falei das flores. No lugar desse lambda que não é muito bonito, existe uma função bem bonita no módulo operator chamada itemgetter():

from operator import itemgetter

sorted(autores, key=itemgetter(1))

#[('Nenê Altro', 4, 'Brasil'),
# ('Carlos Drummond Andrade', 14, 'Brasil'),
# ('Fernando Pessoa', 17, 'Portugal')]

Mas, teremos alguns momentos a sós com o módulo operator, calma jovenzinho. Uma hora a gente chega lá.

filter()

Bom, já estamos chegando ao final e filter() não poderia ficar de fora. A única razão pro filter ser a última função a ser comentada por agora é única e simplesmente por fugir das definições passadas até agora.

Filter não é uma função nem de mapeamento, nem de redução. Filter é uma função de filtragem. Veja bem, só por isso ela ficou por último. Chega de enrolar, vamos ao código:

lista = [1, 2, 3, 4, 5]

impares = lambda x: x % 2

filter(impares, lista) # [1, 3, 5]

Nem doeu, né? Vale uma lembrança, aparentemente iria retornar só os pares, porém zero é False, lembra? Então o retorno foram os ímpares.

Caso você queira inverter, temos o filterfalse do módulo itertools, que vai ser tema de outro vídeo, mas fica o gostinho:

from itertools import filterfalse

lista = [1, 2, 3, 4, 5]

impares = lambda x: x % 2

filterfalse(impares, lista) # [2, 4]

Por hoje é só pessoal. No próximo take vamos aprender a criar nossas próprias HOFs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant