簡體   English   中英

Python - 從列表中刪除項目

[英]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 ]

更新:我看到一些關於性能的奢侈聲明,我想聲稱我的解決方案已經盡可能快。 創建中間結果,無論它們是中間列表還是必須重復調用的中間迭代器,總是會比直接給出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微秒,顯然)是最好的,除非提問者在L1有重復並希望每次刪除一次副本出現在其他列表中的時間。

update ::: post包含對與frozensets相比較低的集合性能的錯誤指控的引用。 我認為在這個實例中使用凍結集仍然是明智的,即使不需要對集合本身進行散列,只是因為它在語義上更正確。 雖然,在實踐中,我可能不會打擾額外的6個字符。 我沒有動力去編輯帖子,所以請注意,“指控”鏈接鏈接到一些錯誤運行的測試。 評論中記錄了血淋淋的細節。 :::更新

Brandon Craig Rhodes 發布的第二大塊代碼相當不錯,但由於他沒有回應我關於使用冷凍裝置的建議(好吧,不是我開始寫這篇文章的時候),我還是要繼續發布我自己。

手頭工作的整個基礎是檢查一系列值( L1 )中的每一個是否屬於另一組值; 該組值是L2L3的內容。 在該句中使用“set”一詞就說明了:即使L2L3list s,我們也不關心它們的類似列表的屬性,例如它們的值的順序或它們的數量。包含。 我們只關心他們共同擁有的價值 (在那里)。

如果將該組值存儲為列表,則必須逐個檢查列表元素,並檢查每個元素。 這是相對耗時的,而且它的語義很糟糕:再次,它是一組“值”,而不是列表。 因此Python具有這些整齊的集合類型,它們包含許多獨特的值,並且可以快速告訴您是否存在某些值。 這與python的dict類型在查找鍵時的工作方式非常相似。

集合frozensets之間的區別在於集合是可變的,這意味着它們可以在創建后進行修改。 這兩種類型的文檔都在這里

由於我們需要創建的集合,存儲在L2L3中的值的並集在創建后不會被修改,因此在語義上適合使用不可變數據類型。 據稱這也有一些性能上的好處。 嗯,它有一些優勢是有意義的; 否則,為什么Python已經frozenset為內置?

更新 ......

布蘭登回答了這個問題:凍結套裝的真正優勢在於它們的不變性使它們可以清洗 ,允許它們成為字典鍵或其他套件的成員。

我運行了一些非正式的時序測試,比較了相對較大(3000元素)的凍結和可變集合的創建和查找速度; 差別不大。 這與上述鏈接相沖突,但支持Brandon所說的關於它們相同但在可變性方面的內容。

...... 更新

現在,因為frozensets是不可變的,所以它們沒有更新方法。 Brandon使用set.update方法來避免創建然后丟棄臨時列表以設置創建; 我將采取不同的方法。

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

生成器表達式使items成為迭代器,連續地覆蓋L2L3的內容。 不僅如此,它還沒有創建一個完整的列表 - 中間對象。 在生成器中使用嵌套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 ,其中包含不在L2L3L1元素,保持它們最初的順序以及它們的數量。


如果您只想知道哪些值在L1而不在L2L3 ,則更容易:您只需創建該集:

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))

假設您的個人列表不包含重復項....使用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)))

在列表中執行此類操作可能會很快妨礙您的程序性能。 每次刪除都會發生什么,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已經有了一個內置函數來將兩個列表鏈接為一個生成器。

程序如下:

  1. 使用itertools.chain鏈接L2和L3,而不創建占用大量內存的副本
  2. 從中創建一個集合(在這種情況下,凍結集將執行,因為我們在創建后不會更改它)
  3. 使用列表推導過濾出L1和L2或L3中的元素。 由於set / frozenset lookup(某些集合中的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM