2017-02-22 54 views
32

我有一個list並想構建(通過理解)另一個列表。我想在大小受到限制,這個新的列表,通過條件如何限制理解的大小?

下面的代碼將失敗:

a = [1, 2, 1, 2, 1, 2] 
b = [i for i in a if i == 1 and len(b) < 3] 

Traceback (most recent call last): 
    File "compr.py", line 2, in <module> 
    b = [i for i in a if i == 1 and len(b) < 3] 
    File "compr.py", line 2, in <listcomp> 
    b = [i for i in a if i == 1 and len(b) < 3] 
NameError: name 'b' is not defined 

因爲b在沒有定義尚未理解建立的時間。

有沒有辦法在構建時限制新列表的大小?

注:我能打破理解成for循環用正確的break到達櫃檯時,但我想知道是否有一種使用一個理解的機制。

回答

52

您可以用生成器表達式做過濾,然後用islice()限制迭代次數:

from itertools import islice 

filtered = (i for i in a if i == 1) 
b = list(islice(filtered, 3)) 

這保證比你要生產這些3個元素你沒有做更多的工作。

請注意,在這裏使用列表理解沒有任何意義;列表理解不能被打破,你被鎖定在迭代到最後。

+0

'[1 /我在範圍內(-5,5)]'確實爆發了,並且不會迭代到最後。 –

+12

@StefanPochmann:它引發了一個異常,這與* break語句不同*。最後,你根本沒有列表結果。 –

+0

對我而言,你並不清楚你的意思是「break」陳述,那麼這個詞可以用更一般的方式來理解。 [例如](http://stackoverflow.com/a/38675546/1672429)不久前你說*「['return']打破循環」*。無論如何,迭代不會結束。另外,沒有列表結果甚至不是一個問題。考慮'倒數= [1/x對於a]',我認爲這是合理的代碼,如果'a'包含一個零,那麼可能需要一個'ZeroDivisionError'而不需要一個列表。 –

6

@Martijn Pieters完全正確,itertools.islice是解決此問題的最佳方法。但是,如果您不介意附加(外部)庫,則可以使用iteration_utilities,其中包含許多itertools及其應用程序(以及其他一些應用程序)。它可以使這個更容易一點,至少,如果你喜歡函數式編程:

>>> from iteration_utilities import Iterable 

>>> Iterable([1, 2, 1, 2, 1, 2]).filter((1).__eq__)[:2].as_list() 
[1, 1] 

>>> (Iterable([1, 2, 1, 2, 1, 2]) 
...   .filter((1).__eq__) # like "if item == 1" 
...   [:2]     # like "islice(iterable, 2)" 
...   .as_list())   # like "list(iterable)" 
[1, 1] 

iteration_utilities.Iterable類使用發電機內部所以它只能處理儘可能多的項目neccessary,直到你調用任何as_*(或get_*) -方法。


免責聲明:我的the iteration_utilities library作者。

+1

這是一個非常好的圖書館,謝謝(仍然閱讀文檔以掌握衆多功能) – WoJ

+1

我可能會建議將第一個鏈接更改爲項目主頁:http://iteration-utilities.readthedocs.io/ EN /最新/? – jpmc26

3

你可以使用itertools.count生成一個計數器和itertools.takewhile在發電機停止迭代當計數器達到(在這種情況下3)所需的整數

from itertools import count, takewhile 
c = count() 
b = list(takewhile(lambda x: next(c) < 3, (i for i in a if i == 1))) 

或類似的想法建立一個結構提高StopIteration來終止發電機。這就是你會得到你原來的想法breaking the list comprehension最接近的,但我不會推薦它作爲最佳實踐:

c = count() 
b = list(i if next(c) < 3 else next(iter([])) for i in a if i == 1) 

例子:

>>> a = [1,2,1,4,1,1,1,1] 

>>> c = count() 
>>> list(takewhile(lambda x: next(c) < 3, (i for i in a if i == 1))) 
[1, 1, 1] 

>>> c = count() 
>>> list(i if next(c) < 3 else next(iter([])) for i in a if i == 1) 
[1, 1, 1] 
+0

這有什麼優勢比其他答案呢? – jpmc26

+0

@ jpmc26對於這個確切的目的,我認爲它不比Martijn的解決方案更好,但它更具通用性,因爲終止發電機的條件可以是任何事情,而不僅僅是計數器。另外,OP要求具體瞭解列表理解,並且這是與 –

+1

最接近的有效語法。謝謝。由於您在其他答案之後發佈了這篇文章,因此您可能需要在靈活性方面爲您的答案做些工作。 – jpmc26

-2

使用枚舉:

b = [n for i,n in enumerate(a) if n==1 and i<3] 
+4

這完全錯了。首先,除了前面3個'a'項(這個問題想限制'b'而不是'a'),它會丟棄所有的東西,它會處理整個迭代。找到第三個項目後不會停止。它只是丟棄了以後的所有東西(不過它會堅持檢查'n == 1和i <3')。 – MSeifert