2017-07-13 131 views
7

考慮下面的函數,它的輸出被認爲是iterables的序列的笛卡爾乘積:爲什麼我的笛卡爾產品功能不起作用?

def cart(*iterables): 
    out = ((e,) for e in iterables[0]) 
    for iterable in iterables[1:]: 
     out = (e1 + (e2,) for e1 in out for e2 in iterable) 
    return out 

當發電機推導與列表解析替換工作正常。當只有2次迭代時也可用。但是,當我嘗試

print(list(cart([1, 2, 3], 'ab', [4, 5]))) 

我得到

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

爲什麼這樣,而不是笛卡爾乘積?

+0

您可以將中間結果存儲在內存中(如工作的列表方法),並且不會延遲他們對該gen的評估。進出口。其值在迭代中反覆變化。 –

+1

我知道這個問題是關於在Python中實現Cartesian產品的算法,但是爲了防止有人在這裏搜索如何在Python中執行Cartesian產品,請注意,這已經在['itertools.product']中實現了( https://docs.python.org/3/library/itertools.html#itertools.product)。 – jdehesa

回答

8

您正在創建生成器表達式,直到for iterable in iterables[1:]:循環的下一次迭代才迭代。他們正在使用關閉,它們在運行時查找。

在這方面,生成器表達式本質上是小函數,它們創建它們自己的作用域,並且需要將父作用域中的任何名稱視爲閉包以使其工作。迭代時會執行'函數',只有當需要關閉並解析爲所引用變量的當前值時。

因此,您創建一個生成器表達式是這樣的:

(e1 + (e2,) for e1 in out for e2 in iterable) 

其中iterable是從父範圍(你的函數當地人)採取了關閉。但是,直到下一次循環時,查找才完成,,在這一點iterable是序列中的下一個元素。

因此,對於您輸入的[1, 2, 3], 'ab', [4, 5],您創建了一個生成器表達式iterable = 'ab',但在實際迭代時,for循環已分配一個新值,現在爲iterable = [4, 5]。當您最後遍歷最終(鏈接)生成器時,只有iterable的最後一個分配纔算。

您正在通過iterables[0], iterables[-1] * len(iterables) - 1有效地創建產品;全部跳過iterables[1]iterables[-2],全部替換爲iterables[-1]

你可以使用一個發電機功能避免關閉的問題,傳遞iterable綁定到一個地方:

def gen_step(out, iterable): 
    for e1 in out: 
     for e2 in iterable: 
      yield e1 + (e2,) 

def cart(*iterables): 
    out = ((e,) for e in iterables[0]) 
    for iterable in iterables[1:]: 
     out = gen_step(out, iterable) 
    return out 

你可以做同樣的拉姆達返回生成器表達式:

def cart(*iterables): 
    out = ((e,) for e in iterables[0]) 
    for iterable in iterables[1:]: 
     out = (lambda it=iterable: (e1 + (e2,) for e1 in out for e2 in it))() 
    return out 
+0

替代品仍然很懶。尼斯。 –