Hapus semua elemen yang terjadi dalam satu daftar dari yang lain

367

Katakanlah saya punya dua daftar, l1dan l2. Saya ingin melakukan l1 - l2, yang mengembalikan semua elemen l1tidak masuk l2.

Saya bisa memikirkan pendekatan loop naif untuk melakukan ini, tetapi itu akan menjadi sangat tidak efisien. Apa cara pythonic dan efisien untuk melakukan ini?

Sebagai contoh, jika sudah l1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2harus kembali[1,6]

kepenggemaran
sumber
12
Hanya sebuah tip: PEP8 menyatakan bahwa huruf kecil "L" tidak boleh digunakan karena terlihat terlalu mirip dengan 1.
spelchekr
2
Saya setuju. Saya membaca seluruh pertanyaan ini dan jawabannya bertanya-tanya mengapa orang terus menggunakan sebelas dan dua belas. Hanya ketika saya membaca komentar @spelchekr, itu masuk akal.
robline
@ Jim. Kerangka data dan daftar bukan hal yang sama.
Mengurangi aktivitas

Jawaban:

492

Python memiliki fitur bahasa yang disebut Daftar Pemahaman yang sangat cocok untuk membuat hal semacam ini sangat mudah. Pernyataan berikut melakukan apa yang Anda inginkan dan menyimpan hasilnya di l3:

l3 = [x for x in l1 if x not in l2]

l3akan mengandung [1, 6].

Donat
sumber
8
Sangat pythonic; Saya suka itu! Seberapa efisien?
fandom
2
Saya percaya cukup efisien, dan memiliki manfaat menjadi sangat mudah dibaca dan jelas untuk apa yang ingin Anda capai. Saya menemukan posting blog yang menurut Anda menarik terkait efisiensi: blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Donut
6
@fandom: pemahaman daftar itu sendiri cukup efisien (meskipun pemahaman generator mungkin lebih efisien dengan tidak menduplikasi elemen dalam memori), tetapi inoperator tidak seefisien itu dalam daftar. inpada daftar adalah O (n), sedangkan inpada himpunan adalah O (1). Namun, hingga Anda mencapai ribuan elemen atau lebih, Anda tidak akan melihat perbedaannya.
Daniel Pryden
1
l3 = [x for x in l1 if x not in set(l2)]? Saya yakin jika set(l2)akan dipanggil lebih dari satu kali.
Danosaure
5
Anda juga bisa mengatur l2s = set(l2)dan berkata l3 = [x for x in l1 if x not in l2s]. Sedikit lebih mudah.
spelchekr
149

Salah satu caranya adalah dengan menggunakan set:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])
Arkku
sumber
58
Ini juga akan menghapus duplikat dari l1, yang mungkin merupakan efek samping yang tidak diinginkan.
hati
37
..dan kehilangan pesanan elemen (jika pesanan penting).
Danosaure
3
Saya hanya ingin menambahkan bahwa saya waktu ini vs jawaban diterima dan itu lebih performant dengan faktor sekitar 3: timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969. Jadi, jika kinerja merupakan faktor penting, jawaban ini mungkin lebih tepat (dan juga jika Anda tidak peduli dengan duplikat atau pesanan)
wfgeo
38

Sebagai alternatif, Anda juga dapat menggunakan filterdengan ekspresi lambda untuk mendapatkan hasil yang diinginkan. Sebagai contoh:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

Perbandingan Kinerja

Di sini saya membandingkan kinerja semua jawaban yang disebutkan di sini. Seperti yang diharapkan, operasi berbasis Arkku set adalah yang tercepat.

  • Perbedaan Set Arkku - Pertama (0,124 usec per loop)

    mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    10000000 loops, best of 3: 0.124 usec per loop
    
  • Pemahaman Daftar Daniel Pryden dengan setpencarian - Kedua (0,302 usec per loop)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.302 usec per loop
    
  • Pemahaman Daftar Donat pada daftar biasa - Ketiga (0,552 usec per loop)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.552 usec per loop
    
  • Moinuddin Quadri menggunakanfilter - Keempat (0,972 usec per loop)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "filter(lambda x: x not in l2, l1)"
    1000000 loops, best of 3: 0.972 usec per loop
    
  • Akshay Hazari menggunakan kombinasi reduce+filter - Kelima (3,97 usec per loop)

    mquadri$ python -m timeit "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2)"
    100000 loops, best of 3: 3.97 usec per loop
    

PS: set tidak mempertahankan urutan dan menghapus elemen duplikat dari daftar. Oleh karena itu, jangan gunakan setel perbedaan jika Anda membutuhkannya.

Moinuddin Quadri
sumber
32

Memperluas jawaban Donut dan jawaban lainnya di sini, Anda bisa mendapatkan hasil yang lebih baik dengan menggunakan pemahaman generator daripada pemahaman daftar, dan dengan menggunakan setstruktur data (karena inoperator adalah O (n) pada daftar tetapi O (1) di set).

Jadi, inilah fungsi yang akan bekerja untuk Anda:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

Hasilnya akan menjadi iterable yang akan malas mengambil daftar yang difilter. Jika Anda memerlukan objek daftar nyata (mis. Jika Anda perlu melakukan len()pada hasilnya), maka Anda dapat dengan mudah membuat daftar seperti:

filtered_list = list(filter_list(full_list, excludes))
Daniel Pryden
sumber
29

Gunakan tipe himpunan Python. Itu akan menjadi yang paling Pythonic. :)

Juga, karena itu asli, itu harus menjadi metode yang paling optimal juga.

Lihat:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm (untuk python lama)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2
nonot1
sumber
5
Ketika menggunakan set, harus dicatat bahwa output diurutkan, yaitu {1,3,2} menjadi {1,2,3} dan {"A", "C", "B"} menjadi {"A", "B", "C"} dan Anda mungkin tidak ingin memilikinya.
Pablo Reyes
2
metode ini tidak akan berfungsi jika daftar l1menyertakan elemen berulang.
jdhao
10

gunakan Set Comprehensions {x for x in l2} atau set (l2) untuk mendapatkan set, kemudian gunakan List Comprehensions untuk mendapatkan daftar

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

kode uji benchmark:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

hasil tes benchmark:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    
lbsweek
sumber
1
l2set = set( l2 )bukannyal2set = { x for x in l2 }
cz
1
Soultion bagus! Tetapi harus diingat, bahwa itu hanya bekerja dengan objek hashable.
Eerik Sven Puudist
7

Solusi Alternatif:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])
Akshay Hazari
sumber
2
Apakah ada keuntungan menggunakan metode ini? Sepertinya lebih rumit dan sulit dibaca tanpa banyak manfaat.
skrrgwasme
Itu mungkin tampak rumit. Reduce sangat fleksibel dan dapat digunakan untuk banyak tujuan. Ini dikenal sebagai lipatan. mengurangi sebenarnya foldl. Misalkan Anda ingin menambahkan hal-hal yang lebih kompleks di dalamnya maka akan dimungkinkan dalam fungsi ini tetapi pemahaman daftar yang merupakan jawaban terbaik yang dipilih hanya akan membuat Anda mendapatkan output dari jenis yang sama yaitu daftar dan mungkin dengan panjang yang sama sementara dengan lipatan Anda bisa ubah tipe output juga. en.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Solusi ini n * m atau kurang kompleksitas. Yang lain mungkin atau mungkin tidak lebih baik.
Akshay Hazari
1
kurangi (fungsi, daftar, akumulator awal (yang bisa dari jenis apa pun))
Akshay Hazari