[英]Python - removing items from lists
# 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的風格指南說應該只有一種正確的方法來做每件事。 我從來不知道這是什么。
以下是一些嘗試:
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 ]
更新:我看到一些關於性能的奢侈聲明,我想聲稱我的解決方案已經盡可能快。 創建中間結果,無論它們是中間列表還是必須重復調用的中間迭代器,總是會比直接給出L2
和L3
直接迭代更慢,就像我在這里做的那樣。
$ 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微秒,顯然)是最好的,除非提問者在L1
有重復並希望每次刪除一次副本出現在其他列表中的時間。
update ::: post包含對與frozensets相比較低的集合性能的錯誤指控的引用。 我認為在這個實例中使用凍結集仍然是明智的,即使不需要對集合本身進行散列,只是因為它在語義上更正確。 雖然,在實踐中,我可能不會打擾額外的6個字符。 我沒有動力去編輯帖子,所以請注意,“指控”鏈接鏈接到一些錯誤運行的測試。 評論中記錄了血淋淋的細節。 :::更新
Brandon Craig Rhodes 發布的第二大塊代碼相當不錯,但由於他沒有回應我關於使用冷凍裝置的建議(好吧,不是我開始寫這篇文章的時候),我還是要繼續發布我自己。
手頭工作的整個基礎是檢查一系列值( L1
)中的每一個是否屬於另一組值; 該組值是L2
和L3
的內容。 在該句中使用“set”一詞就說明了:即使L2
和L3
是list
s,我們也不關心它們的類似列表的屬性,例如它們的值的順序或它們的數量。包含。 我們只關心他們共同擁有的價值集 (在那里)。
如果將該組值存儲為列表,則必須逐個檢查列表元素,並檢查每個元素。 這是相對耗時的,而且它的語義很糟糕:再次,它是一組“值”,而不是列表。 因此Python具有這些整齊的集合類型,它們包含許多獨特的值,並且可以快速告訴您是否存在某些值。 這與python的dict
類型在查找鍵時的工作方式非常相似。
集合和frozensets之間的區別在於集合是可變的,這意味着它們可以在創建后進行修改。 這兩種類型的文檔都在這里 。
由於我們需要創建的集合,存儲在L2
和L3
中的值的並集在創建后不會被修改,因此在語義上適合使用不可變數據類型。 據稱這也有一些性能上的好處。 嗯,它有一些優勢是有意義的; 否則,為什么Python已經frozenset
為內置?
更新 ......
布蘭登回答了這個問題:凍結套裝的真正優勢在於它們的不變性使它們可以清洗 ,允許它們成為字典鍵或其他套件的成員。
我運行了一些非正式的時序測試,比較了相對較大(3000元素)的凍結和可變集合的創建和查找速度; 差別不大。 這與上述鏈接相沖突,但支持Brandon所說的關於它們相同但在可變性方面的內容。
...... 更新
現在,因為frozensets是不可變的,所以它們沒有更新方法。 Brandon使用set.update
方法來避免創建然后丟棄臨時列表以設置創建; 我將采取不同的方法。
items = (item for lst in (L2, L3) for item in lst)
此生成器表達式使items
成為迭代器,連續地覆蓋L2
和L3
的內容。 不僅如此,它還沒有創建一個完整的列表 - 中間對象。 在生成器中使用嵌套for
表達式有點令人困惑,但我設法通過記住它們以與編寫實際for循環時相同的順序嵌套來保持它的排序,例如
def get_items(lists):
for lst in lists:
for item in lst:
yield item
該生成器函數等效於我們分配給items
的生成器表達式。 好吧,除了它是一個參數化的函數定義,而不是直接賦值給變量。
無論如何,足夠的題外話。 發電機的重要性在於它們實際上並沒有做任何事情。 好吧,至少不是馬上:他們只是設置工作,以便在迭代生成器表達式后再完成。 這被正式稱為懶惰 。 我們將通過將items
傳遞給frozenset
函數來做到這一點(好吧,無論如何),該函數迭代它並返回一個冷凍冷凍集。
unwanted = frozenset(items)
實際上,您可以通過將生成器表達式放在對frozenset
的調用內部來實際組合最后兩行:
unwanted = frozenset(item for lst in (L2, L3) for item in lst)
只要生成器表達式創建的迭代器是您正在調用的函數的唯一參數,這種簡潔的語法技巧就可以工作。 否則你必須在通常單獨的括號中寫它,就像你將一個元組作為參數傳遞給函數一樣。
現在我們可以像Brandon一樣建立一個新列表,並具有列表理解能力 。 它們使用與生成器表達式相同的語法,並且基本上做同樣的事情,除了它們渴望而不是懶惰 (再次,這些是實際的技術術語),因此他們可以正確地迭代項目並從中創建列表。
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
,其中包含不在L2
或L3
的L1
元素,保持它們最初的順序以及它們的數量。
如果您只想知道哪些值在L1
而不在L2
或L3
,則更容易:您只需創建該集:
L1_unique_values = set(L1) - unwanted
你可以用它來制作一個列表, 就像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))
假設您的個人列表不包含重復項....使用Set
和Difference
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)))
在列表中執行此類操作可能會很快妨礙您的程序性能。 每次刪除都會發生什么,List操作會執行一個新的malloc和移動元素。 如果你有一個非常龐大的列表或其他,這可能是昂貴的。 所以我建議這個 -
我假設你的清單有獨特的元素。 否則,您需要在dict中維護一個具有重復值的列表。 無論如何,對於您提供的數據,這里是 -
方法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)
這可能比列表理解答案更少pythonesque,但有一個更簡單的外觀:
l1 = [ ... ]
l2 = [ ... ]
diff = list(l1) # this copies the list
for element in l2:
diff.remove(element)
這里的優點是我們保留了列表的順序 ,如果有重復的元素 ,我們每次在l2中出現時只刪除一個元素 。
我認為對於這樣一個簡單的問題,直覺的答案太長了,而Python已經有了一個內置函數來將兩個列表鏈接為一個生成器。
程序如下:
itertools.chain
鏈接L2和L3,而不創建占用大量內存的副本 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]
這應該是最快,最簡單,耗電量最少的解決方案之一。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.