2010-10-16 260 views
8
# I have 3 lists: 
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
# I want to create another that is L1 minus L2's memebers and L3's memebers, so: 
L4 = (L1 - L2) - L3 # Of course this isn't going to work 

我想知道,什麼是「正確」的方式來做到這一點。我可以通過很多不同的方式做到這一點,但是Python的風格指南認爲,應該只有一種正確的方式來完成每件事情。我從來不知道這是什麼。Python - 從列表中刪除項目

+3

沒有一個正確的方法來做這件事,直到你決定你是否照顧或不關心重複和訂購。可能是某種列表理解或根據你所關心的設定工作。 – istruble 2010-10-16 05:40:20

+1

另外,可以假設列表中的所有項目都會一直可用?如果不是,或者有時不會,那將非常重要。 – martineau 2010-10-16 12:03:01

+1

你爲什麼不用套頭?那麼你的「算術」就可以工作。 – poke 2010-10-16 15:41:14

回答

10

這裏有一些嘗試:

L4 = [ n for n in L1 if (n not in L2) and (n not in L3) ] # parens for clarity 

tmpset = set(L2 + L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

現在我有時間去思考,我意識到L2 + L3事情創建了一個臨時列表,立即被扔掉。因此,一個更好的方法是:

tmpset = set(L2) 
tmpset.update(L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

更新:我看到一些過分的要求被拋向四周約的表現,我想斷言,我的解決方案已經儘可能地快。創建中間結果,無論它們是中間列表還是中間迭代器,都必須重複調用,總是比單獨給出L2L3以使該集合直接迭代,就像我在這裏完成的那樣。

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'ts = set(L2); ts.update(L3); L4 = [ n for n in L1 if n not in ts ]' 
10000 loops, best of 3: 39.7 usec per loop 

所有其他替代品(我能想到的)必然會比這慢。這樣的循環自己,例如,而不是讓set()構造做他們,增加了費用:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'unwanted = frozenset(item for lst in (L2, L3) for item in lst); L4 = [ n for n in L1 if n not in unwanted ]' 
10000 loops, best of 3: 46.4 usec per loop 

使用迭代器,都將它們涉及與狀態保存回調,顯然會更貴:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2);from itertools import ifilterfalse, chain' \ 
    'L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1))' 
10000 loops, best of 3: 47.1 usec per loop 

所以我相信答案我給昨晚仍遠和(爲「遠和」大於周圍5μsec,明明值)是最好的,除非提問會有重複的L1和希望每次重複出現在其中一個其他列表中時,每次都會刪除一次。

+0

通過從兩個列表迭代器的鏈構建一個凍結集可能可以實現更多的性能。 – intuited 2010-10-16 04:44:29

+0

不,凍結集的速度與正常速度的速度完全相同,但通常需要更多的開銷,因爲您必須自己創建中間結果或循環,如果在這裏您正在從幾個輸入迭代中構建它們。 – 2010-10-16 12:48:38

0

假設你的個人名單將不包含重複....使用SetDifference

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
print(list(set(L1) - set(L2) - set(L3))) 
+2

這會失去命令。 – 2010-10-16 04:23:08

+1

是的,一個列表和一個集合的主要區別... – mepcotterell 2010-10-16 04:24:18

+1

如果訂單/重複不是問題,這是最乾淨的選項,IMO – 2010-10-16 04:32:00

0

在列表中執行此類操作可能會很快妨礙您的程序性能。每次刪除都會發生什麼,列表操作會執行一個新的malloc &移動元素。如果您有非常大的列表或其他地方,這可能會很昂貴。所以我會建議這 -

我假設你的列表有獨特的元素。否則,你需要在你的字典中保留一個具有重複值的列表。反正你提供的數據,在這裏它是 -

方法1

d = dict() 
for x in L1: d[x] = True 

# Check if L2 data is in 'd' 
for x in L2: 
    if x in d: 
     d[x] = False 

for x in L3: 
    if x in d: 
     d[x] = False 

# Finally retrieve all keys with value as True. 
final_list = [x for x in d if d[x]] 

方法2 如果一切看起來像太多的代碼。然後你可以嘗試使用set。但是這樣你的列表將會丟失所有重複的元素。

final_set = set.difference(set(L1),set(L2),set(L3)) 
final_list = list(final_set) 
+0

列表理解不會刪除昂貴的操作。 – aaronasterling 2010-10-16 04:38:29

+0

#aaron是的我知道。我指的是聖地亞哥公佈的解決方案。 – 2010-10-16 04:43:51

+1

嘿,你基本上使用字典作爲一個集合。他們有一個完整的其他數據類型:http://docs.python.org/library/stdtypes.html#types-set – intuited 2010-10-16 04:48:07

0

這可能是比列表理解答案pythonesque少,但有一個更簡潔的外觀給它:

l1 = [ ... ] 
l2 = [ ... ] 

diff = list(l1) # this copies the list 
for element in l2: 
    diff.remove(element) 

這裏的好處是,我們保護列表的順序,如果有重複元素,我們在每次出現在l2時只刪除一個元素。

+1

的討論這是非常昂貴,相反,更多看起來比簡單的理解複雜。 – aaronasterling 2010-10-16 04:37:57

+0

看起來有味道問題。我非常喜歡列表理解,我實際上傾向於過度使用它們,但我不認爲「如果n不在......中,n在n中」對眼睛來說很好。無論如何,我承認,計算成本很高。 – slezica 2010-10-16 04:44:19

6

更新:::帖子中包含了與frozensets相比劣勢集的錯誤指控。我堅持認爲在這個實例中使用一個冷凝集合仍然是明智的,即使不需要散列集合本身,僅僅因爲它在語義上更正確。雖然在實踐中,我可能不會打擾多餘的6個字符。我沒有動力去瀏覽和編輯這篇文章,所以只是建議「指控」鏈接到一些不正確運行的測試。評論中散佈了血淋淋的細節。 :::更新

由布蘭登·克雷格羅德的代碼posted第二塊是相當不錯,但他並沒有對我的建議作出迴應有關使用frozenset(當然,不是當我開始寫這個,反正) ,我會繼續並自己發佈。

手頭承諾的全部基礎是檢查一系列值(L1)中的每一個值是否都在另一組值中;該組值是L2L3的內容。在這個句子中使用「set」這個詞是說:即使L2L3list s,我們並不關心他們的列表類屬性,比如它們的值的順序或者它們的值有多少包含。我們只關心集合(這裏又是)它們共同包含的值。

如果該組值被存儲爲列表,則必須逐個檢查列表元素,檢查每個列表元素。這是相對耗時的,而且是不好的語義:再次,它是一組「值」,而不是一個列表。所以Python有這些整齊的集合類型,它們擁有一堆獨特的值,並且可以快速告訴你是否有某個值。這與python的dict類型在查找關鍵字時的工作方式基本相同。

frozensets是集是可變的,這意味着它們可以在創建之後可以修改之間的差異。這兩種類型的文檔是here

由於我們需要創建的集合,存儲在L2L3中的值的聯合一旦創建就不會被修改,它在語義上適合使用不可變數據類型。這也有一些性能優勢。那麼,這是有道理的,它會有一些優勢;否則,爲什麼Python將frozenset作爲內建函數?

更新 ...

布蘭登已經回答了這個問題:冰凍套真正的優勢在於他們的不變性,使他們有可能是hashable,使他們能夠字典鍵或其他組成員。

我跑比較用於創建和查找在相對大的(3000元素)冷凍並可變設定速度一些非正式定時測試;沒有太大的區別。這與上面的鏈接衝突,但支持布蘭登說他們是相同的,但在可變性方面。

... 更新

現在,因爲frozensets是不可改變的,他們沒有更新方法。布蘭登使用set.update方法來避免創建並丟棄臨時列表以創建集合;我將採取不同的方法。

items = (item for lst in (L2, L3) for item in lst) 

generator expression使得items一個迭代結束,連續的L2L3內容。不僅如此,它還可以在不創建完整列表的情況下完成 - 完整的中間對象。在生成器中使用嵌套for表達式有點令人困惑,但我設法通過記住它們的嵌套順序與它們在編寫實際for循環時的順序相同,例如

def get_items(lists): 
    for lst in lists: 
     for item in lst: 
      yield item 

generator function等同於我們分配給items發電機表達。那麼,除了它是一個參數化的函數定義,而不是直接賦值給一個變量。

無論如何,夠離題了。與發電機有關的大事是他們實際上沒有做任何事情。那麼,至少不是馬上:他們只是將工作設置在稍後完成,當時生成器表達式爲迭代爲。這正式被稱爲懶惰。我們將通過將items傳遞給frozenset函數來做到這一點(無論如何,我是這樣做的),該函數遍歷它並返回一個冷凍冷凍集。

unwanted = frozenset(items) 

其實你可以結合起來,最後兩行,通過把發電機表達權的通話裏面frozenset

unwanted = frozenset(item for lst in (L2, L3) for item in lst) 

只要這個整齊的語法技巧的工作由生成器表達式創建的iterator是您要調用的函數的唯一參數。否則,你必須把它寫在它通常單獨的一組括號中,就像你將一個元組作爲參數傳遞給函數一樣。現在

我們可以建立以同樣的方式,布蘭登做了一個新的列表,用list comprehension。這些使用相同的語法生成表達式,基本上做同樣的事情,但他們都渴望,而不是(再次,這些都是實際的技術術語),所以他們馬上在項目工作迭代和創建他們的名單。

L4 = [item for item in L1 if item not in unwanted] 

這等效於通過一個發電機表達式list,例如

L4 = list(item for item in L1 if item not in unwanted) 

但更習慣。

因此,這將創建列表L4,含有沒有在任何L2L3L1的元素,保持他們在最初的順序和他們的,有數量。


如果你只是想知道值在L1但不是在L2L3,它更容易:你剛纔創建集:

L1_unique_values = set(L1) - unwanted 

你可以列個清單出來它,as does st0le,但這可能不是你想要的。如果你真的想要的設定值那些只在L1發現,你可能有一個很好的理由保持這種設置set,或者確實是一個frozenset

L1_unique_values = frozenset(L1) - unwanted 

... Annnnd現在完全不同的東西:

from itertools import ifilterfalse, chain 
L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1)) 
+0

+1非常豐富。最近的增加(與itertools)是非常好的。我會說你已經在過濾列表中獲得了博士學位,這是基於包含在一組列表中的。 – aaronasterling 2010-10-16 08:02:34

+0

@aaron:這是需要多年的學習,但這是值得的。 – intuited 2010-10-16 08:06:02

+0

我錯過了什麼,或者是你的生成器表達只是'itertools.chain'?如果是的話,就使用它(你可以保留生成器和生成器表達式的解釋,但人們需要了解它們)。 – delnan 2010-10-16 12:21:09

0

我認爲對於這樣一個簡單的問題,intuited的答案太長了,Python已經有了一個內置函數來將兩個列表作爲一個生成器鏈接起來。

的過程如下:

  1. 使用itertools.chain到鏈L2和L3,而無需創建一個佔用內存的副本
  2. 創建從一組(在這種情況下,frozenset這樣做,因爲我們不在創建之後不會改變它)
  3. 使用列表理解過濾出L1中以及L2或L3中的元素。由於set/frozenset查找(x in someset)是O(1),這將非常快。

而現在的代碼:

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 

from itertools import chain 
tmp = frozenset(chain(L2, L3)) 
L4 = [x for x in L1 if x not in tmp] # [1, 3, 6] 

這應該是最快,最簡單和最佔用內存的解決方案之一。

+0

這不是最快的;檢查我的帖子中的測試。在集合和已經可迭代的列表之間放置迭代器會降低速度。 – 2010-10-16 19:37:31

+0

@Brandon Craig Rhodes:好吧,讓我們說「最快的解決方案之一」。感謝您發佈您的基準測試結果。 – AndiDog 2010-10-16 20:32:44

+0

的確 - 您的解決方案無疑是最快速的,當然也是這個問題值得關注的O(* n * log * m *)解決方案之一。我只是想確保程序員認識到迭代器不是精靈塵埃,它比在容器本身上循環更快;迭代器返回的每個項目都需要重新激活它的範圍,並重新開始其代碼,所以它們的好處不是免費的。 – 2010-10-16 21:34:02