機票

機票

學習練習

簡介

generator是一種函式或運算式,會回傳一種特殊的疊代器,稱為generator iterator。 Generator-iterators 是惰性的:它們不會把values存放在記憶體中,而是在需要時才_產生_值。

產生器函式和其他函式看起來沒什麼兩樣,但它包含一個或多個yield 運算式。 每個yield都會暫停程式碼執行,並保存當下的執行狀態(包含所有區域變數和 try 敘述)。 當產生器恢復執行時,會從暫停的地方接續狀態,這點和每次呼叫都會重置的一般函式不同。

建立產生器

產生器的建構方式和其他迴圈或遞迴函式很像,但它需要一個yield運算式,我們稍後會深入探討。

舉例來說,有個函式會從給定的數字陣列中回傳這些數字的_平方_。 以目前的寫法,必須先處理完所有輸入,才能回傳任何值:

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

你可以像這樣把那個函式改寫成產生器:

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

這麼做的理由是,當你不需要_一次_產生所有值時,就可以使用產生器。 這樣能節省記憶體和運算資源,因為只會計算你_目前正在處理_的那個值。

使用產生器

在 Python 中,產生器可以替代大多數的iterables。 這包括需要iterable/iterator作為引數的_函式_或_物件_。

如要使用squares_generator()產生器:

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

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

generator中的值也可以透過next()函式來產生或存取。 next()會呼叫產生器疊代器物件的__next__()方法,「推進」或求值程式碼直到它的yield運算式,接著「讓出」或回傳一個值:

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

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

當generator-iterator完全被取用完、沒有更多值可以回傳時,它會拋出StopIteration錯誤。

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

產生器疊代器是疊代器的一個特殊子集。 Iterators是讓我們能對_可疊代物件_進行迴圈的機制/協定。 產生器疊代器和常見 Python iterables所回傳的疊代器行為非常相似,但有幾個重要差異值得注意:

  • 它們是_惰性求值_的;疊代是_單向_的,無法「退回」到前一個值。
  • 它們會因為疊代回傳的值而被_取用_;不會重置,也不會保存在記憶體中。
  • 它們無法排序,也無法反轉。
  • 它們不是序列型態,而且_沒有_indexes。 你不能用加法或減法來參照前一個或未來的值,也不能使用方括號([])表示法或切片。
  • 它們不能和len()函式一起使用,因為它們沒有長度。
  • 它們可以是_有限_或_無限_的,從_無限_的generator-iterator收集所有值時請務必小心!

yield 運算式

yield 運算式和return運算式非常相似。 不同於return運算式,yield會在_特定時間點_把值交給呼叫者,並暫停其餘任何值的求值或回傳,直到有人要求它們為止。 當yield被求值時,它會暫停外層函式的執行,並_在當下_回傳函式的值。 接著函式會_繼續留在作用域中_,而當__next__()被呼叫時,執行會繼續進行,直到再次遇到yield為止。

Note

在函式之外禁止使用yield運算式。

>>> 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

為什麼要建立產生器?

產生器在許多應用中都很實用。

當你面對可能很龐大的一批值時,可能不會想把它們全部放進記憶體。 產生器可以讓你一件一件地處理較大的資料,既能節省記憶體,也能提升效能。

當某個流程或計算很_複雜_、很_耗費資源_,或是_無限_時,產生器也非常有幫助:

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

現在,每當對infinite_sequence物件呼叫__next__()時,它就會回傳_前一個數字_ + 1。

說明

Conda Airlines 是程式設計界最大的航空公司,每天有超過 10,000 個航班!

他們目前以人工方式為所有乘客分配座位,這必須自動化。

他們請_你_開發一套軟體,讓乘客座位分配自動化。 他們要求你的軟體要節省記憶體、效能要好。

1. 產生座位字母

Conda 想為他們的飛機產生座位字母。 一架飛機是由一排排座位組成的。 每一排有 4 個座位。 每一排的座位一律命名為A、B、C和D。 一排中的第一個座位是A,第二個座位是B,以此類推。 到達D之後,應該重新從A開始。

實作一個函式generate_seat_letters(<number>),它接受一個int,代表要產生多少個座位字母。 這個函式接著應該回傳座位字母的_可迭代物件_。

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

2. 產生座位

Conda 想要一套能為他們的飛機產生指定數量座位的系統。 每架飛機的每一排都有 4 個座位。 排數以數字定義,從1開始往上遞增。 座位的排序應該像這樣:1A、1B、1C、1D、2A、2B、2C、2D、3A、3B、3C、3D、……

以下是一個例子:

x 1 2
排 5 21
座位字母 A D
結果 5A 21D

由於乘客的迷信,許多航空公司的班機上沒有 13 號_排_。 Conda Airlines 也遵循這項慣例,所以請務必_不要_產生 13 號_排_的座位。

實作一個函式generate_seats(<number>),它接受一個int,代表要產生多少個座位。 這個函式接著應該回傳這些座位的_可迭代物件_。

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

3. 將座位分配給乘客

現在你已經有個會產生座位的函式,可以用它來把座位分配給乘客。

實作一個函式assign_seats(<passengers>),它接受一份乘客姓名的list。 這個函式接著應該回傳一個_字典_,以passenger為_鍵_,seat_number為_值_。

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

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

4. 票券代碼

Conda Airlines 希望每張票都有唯一的代碼。 由於他們是大型航空公司,航班非常多。 這表示會有多個航班擁有相同的座位號碼。 他們要你打造一套系統,產生一組由 12 個字元組成的唯一票券字串。

這組代碼以assigned_seat開頭,接著是flight_id。 代碼其餘部分以0s填滿。

實作一個函式generate_codes(<seat_numbers>, <flight_id>),它接受一份由seat_numbers組成的list,以及一個含有航班編號的string。 這個函式接著應該回傳一個generator,逐一產生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'
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Python Exercism

準備好開始 機票 了嗎?

註冊 Exercism,透過 17 個概念146 個練習 和真人引導來學習並精通 Python,全部免費。