Percursos
/
Python
Python
/
Exercícios
/
Bilhetes de avião
Bilhetes de avião

Bilhetes de avião

Exercício de aprendizagem

Introdução

Um generator é uma função ou expressão que devolve um tipo especial de iterador chamado generator iterator. Os Generator-iterators são preguiçosos: não guardam os seus values em memória, mas geram os seus valores quando é preciso.

Uma função geradora é semelhante a qualquer outra função, mas contém uma ou mais expressões yield. Cada yield suspende a execução do código, guardando o estado de execução atual (incluindo todas as variáveis locais e as instruções try). Quando o gerador é retomado, recupera o estado a partir da suspensão, ao contrário das funções normais, que são reiniciadas a cada chamada.

Construir um gerador

Os geradores são construídos de forma muito semelhante a outras funções com ciclos ou recursivas, mas exigem uma expressão yield, que vamos explorar em detalhe um pouco mais à frente.

Um exemplo é uma função que devolve os quadrados de uma determinada lista de números. Tal como está escrita, todos os dados de entrada têm de ser processados antes de se poder devolver algum valor:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

Podes converter essa função num gerador assim:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

A razão para isto é que usas um gerador quando não precisas de produzir todos os valores de uma só vez. Isto poupa memória e poder de processamento, uma vez que só é calculado o valor com que estás a trabalhar neste momento.

Utilizar um gerador

Os geradores podem ser usados em vez da maioria dos iterables em Python. Isto inclui funções ou objetos que exigem um iterable/iterator como argumento.

Para usar o gerador squares_generator():

>>> squared_numbers = squares_generator([1, 2, 3, 4])

>>> for square in squared_numbers:
...     print(square)
...
1
4
9
16

Os valores dentro de um generator também podem ser produzidos ou acedidos através da função next(). next() chama o método __next__() de um objeto gerador-iterador, "avançando" ou avaliando o código até à sua expressão yield, que depois "cede" ou devolve um valor:

>>> squared_numbers = squares_generator([1, 2])

>>> next(squared_numbers)
1
>>> next(squared_numbers)
4

Quando um generator-iterator é totalmente consumido e já não tem mais valores para devolver, lança um erro StopIteration.

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
Note

Os gerador-iteradores são um subconjunto especial de iteradores. Os Iterators são o mecanismo ou protocolo que permite iterar sobre iterables. Os gerador-iteradores e os iteradores devolvidos pelos iterables comuns de Python comportam-se de forma muito semelhante, mas há algumas diferenças importantes a ter em conta:

  • São avaliados de forma preguiçosa; a iteração é unidirecional e não há forma de "voltar atrás" para um valor anterior.
  • São consumidos ao iterar sobre os valores devolvidos; não há forma de os reiniciar ou guardar em memória.
  • Não podem ser ordenados nem invertidos.
  • Não são tipos de sequência e não têm indexes. Não podes referenciar um valor anterior ou futuro através de adição ou subtração, nem podes usar a notação de parênteses retos ([]) ou fatiamento.
  • Não podem ser usados com a função len(), uma vez que não têm comprimento.
  • Podem ser finitos ou infinitos. Tem cuidado ao recolher todos os valores de um generator-iterator infinito!

A expressão yield

A expressão yield é muito semelhante à expressão return. Ao contrário da expressão return, o yield cede valores a quem o chama num ponto específico, suspendendo a avaliação ou o retorno de quaisquer valores adicionais até que sejam pedidos. Quando o yield é avaliado, pausa a execução da função que o contém e devolve os valores da função nesse momento. A função permanece então no âmbito, e quando __next__() é chamado, a execução é retomada até o yield voltar a ser encontrado.

Note

A utilização de expressões yield é proibida fora de funções.

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1

Porquê criar um gerador?

Os geradores são úteis em muitas aplicações.

Quando trabalhas com uma coleção de valores potencialmente grande, podes não querer colocá-los todos em memória. Um gerador pode ser usado para trabalhar com dados maiores peça a peça, poupando memória e melhorando o desempenho.

Os geradores também são muito úteis quando um processo ou cálculo é complexo, dispendioso ou infinito:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

Agora, sempre que __next__() é chamado no objeto infinite_sequence, devolverá o número anterior + 1.

Instruções

A Conda Airlines é a maior companhia aérea do mundo da programação, com mais de 10 000 voos por dia!

Neste momento, atribuem todos os assentos aos passageiros à mão; isto terá de ser automatizado.

Pediram-te a ti que criasses um programa para automatizar a atribuição dos assentos aos passageiros. Pedem que o teu programa seja eficiente em termos de memória e tenha um bom desempenho.

1. Gera as letras dos assentos

A Conda quer gerar as letras dos assentos dos seus aviões. Um avião é composto por filas de assentos. Cada fila tem 4 assentos. Os assentos de cada fila têm sempre os nomes A, B, C e D. O primeiro assento da fila é A, o segundo é B, e assim por diante. Depois de chegar a D, deve recomeçar com A.

Implementa uma função generate_seat_letters(<number>) que aceita um int com o número de letras de assentos a gerar. A função deve depois devolver um iterável de letras de assentos.

>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"

2. Gera assentos

A Conda quer um sistema capaz de gerar um determinado número de assentos para os seus aviões. Cada avião tem 4 assentos em cada fila. As filas são definidas por números, a começar em 1 e a aumentar. Os assentos devem ser ordenados assim: 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...

Eis um exemplo:

x 1 2
Fila 5 21
Letra do assento A D
Resultado 5A 21D

Muitas companhias aéreas não têm a fila número 13 nos seus voos, devido à superstição dos passageiros. A Conda Airlines também segue esta convenção, por isso certifica-te de que não geras assentos para a fila número 13.

Implementa uma função generate_seats(<number>) que aceita um int com o número de assentos a gerar. A função deve depois devolver um iterável com os assentos indicados.

>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"

3. Atribui assentos aos passageiros

Agora que tens uma função que gera assentos, podes usá-la para atribuir assentos aos passageiros.

Implementa uma função assign_seats(<passengers>) que aceita uma list com nomes de passageiros. A função deve depois devolver um dicionário em que passenger é a chave e seat_number é o valor.

>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']

>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}

4. Códigos dos bilhetes

A Conda Airlines gostaria de ter um código único para cada bilhete. Como são uma grande companhia aérea, têm muitos voos. Isso significa que há vários voos com o mesmo número de assento. Querem que cries um sistema que formule um bilhete único, uma string com 12 carateres de comprimento.

Este código começa com o assigned_seat seguido do flight_id. O resto do código é preenchido com 0s.

Implementa uma função generate_codes(<seat_numbers>, <flight_id>) que aceita uma list de seat_numbers e uma string com o número do voo. A função deve depois devolver um generator que produz um ticket_number.

>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)

>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'
Editar via GitHub A ligação abre numa nova janela ou separador
Python Exercism

Estás pronto para começar Bilhetes de avião?

Inscreve-te no Exercism para aprenderes e dominares Python com 17 conceitos146 exercícios, e mentoria humana real, tudo grátis.