HOWTO · Python
Python 中列表的組合
本教程演示瞭如何在 python 中查詢列表元素的組合
本頁內容
使用 itertools.combinations(),可在不重複使用輸入位置的情況下選取固定數量的項目。如果同一個位置可以重複選取,則使用 itertools.combinations_with_replacement()。這兩個函式都會產生組合:選取順序不同不會形成新的結果;排列則會將不同順序視為不同結果。
選擇合適的組合方式
需要固定的選取數量時,使用 combinations();允許重複選取同一個位置時,使用 combinations_with_replacement();需要產生所有子集合時,則使用 powerset() 函式。標準函式庫中的這些函式會回傳由 tuple 組成的迭代器。它們依輸入位置讀取資料,因此不同位置上的相同值可能產生看起來重複的 tuple。結果順序依照輸入位置排列;每個 tuple 內的值不會自動排序。若需要自訂規則,可以使用遞迴,明確控制選取流程。
使用 itertools.combinations() 進行不重複選取
itertools.combinations(iterable, r) 會產生長度為 r 的 tuple,而且不會重複使用同一個位置。它會依照 iterable 的輸入順序讀取資料。若要一次儲存或檢視所有結果,請將迭代器轉成 list;否則可以直接逐一迭代。
執行以下範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
from itertools import combinations
items = ["red", "blue", "green"]
for pair in combinations(items, 2):
print(pair)
這些雙元素結果最多只會使用每個位置一次。若要取得個別項目,將 r 設為 1;若要將整個輸入作為一個 tuple,則將它設為輸入位置的數量。如果 r 大於位置數量,迭代器就不會產生任何結果。
選取項目可能重複時使用 itertools.combinations_with_replacement()
若允許重複選取同一個輸入位置,請使用 itertools.combinations_with_replacement(iterable, r)。它同樣會依照位置順序產生長度為 r 的 tuple。例如,從 ["red", "blue"] 選取一對時,結果會包含 ("red", "red") 和 ("blue", "blue");combinations() 則不會產生這些配對。當重複選取有其意義時,這種方式就很實用,例如選兩球冰淇淋,而且兩球可以是同一種口味。引數 r 仍會決定每個 tuple 的長度。
使用遞迴或產生器自訂實作
遞迴回溯函式可以將結果逐步建立在 list 中。每一步先附加一個值,再從下一個位置遞迴呼叫,接著移除該值,讓迴圈嘗試其他選項。產生器遵循相同的選取邏輯,但會在找到結果時逐一產生。除非呼叫端明確將結果轉成 list,否則不必保留整個結果集合;不過,這並不代表它本身一定比較快。
以下兩個自訂函式都要求 r 是非負整數,否則會引發 ValueError。當 r = 0 時,各自都會回傳唯一的空 tuple;當 r 大於輸入位置數量且大於零時,兩者都不會產生結果。產生器函式本體會延遲執行,因此要等到開始迭代時才會檢查引數。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
def get_combinations(items, r):
if not isinstance(r, int) or isinstance(r, bool) or r < 0:
raise ValueError("r must be a nonnegative integer")
results = []
def backtrack(start, chosen):
if len(chosen) == r:
results.append(tuple(chosen))
return
for index in range(start, len(items)):
chosen.append(items[index])
backtrack(index + 1, chosen)
chosen.pop()
backtrack(0, [])
return results
def generate_combinations(items, r):
if not isinstance(r, int) or isinstance(r, bool) or r < 0:
raise ValueError("r must be a nonnegative integer")
if r == 0:
yield ()
return
for index in range(len(items)):
for suffix in generate_combinations(items[index + 1 :], r - 1):
yield (items[index],) + suffix
items = ["red", "blue", "green"]
print(get_combinations(items, 2))
print(list(generate_combinations(items, 2)))
若要撰寫延遲產生結果的自訂版本,請在完整選取結果形成時使用 yield。無論採用哪一種實作,下一次遞迴呼叫都要從 index + 1 開始,避免重複使用同一個位置。如果輸入中有重複值,即使它們來自不同位置,輸出看起來也可能相同。
使用 powerset() 函式產生所有子集合
冪集包含各種大小的子集合,包括空 tuple 和完整輸入。這種實作會合併每種可能大小的結果。chain.from_iterable() 會回傳迭代器;只有在需要一次建立所有子集合時,才將它包在 list() 中。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
from itertools import chain, combinations
def powerset(items):
values = list(items)
return chain.from_iterable(
combinations(values, size) for size in range(len(values) + 1)
)
for subset in powerset(["red", "blue"]):
print(subset)
若輸入有 n 個位置,冪集就有 2**n 個子集合。這個數量會快速增加,因此如果任務只需要固定大小的選取,建議使用固定大小的組合迭代器。只有在結果確實需要包含所有大小的子集合時,才使用冪集。