Operasi pengurangan daftar Python


227

Saya ingin melakukan sesuatu yang mirip dengan ini:

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> x  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]  
>>> y = [1,3,5,7,9]  
>>> y  
[1, 3, 5, 7, 9]  
>>> y - x   # (should return [2,4,6,8,0])

Tapi ini tidak didukung oleh daftar python Apa cara terbaik untuk melakukannya?


@ezdazuzena ini bukan substraksi. Inilah perbedaan antara dua daftar. Pembagian Anda bukan publikasi dari pertanyaan ini.
Celik

1
Apa yang harus [2, 2] - [2] kembali? [] [2]?
McKay

@McKay [2,2] - [2] harus kembali [2]. [2,2] - [1,2,2,3] harus kembali []
Robino

Pertanyaan ini adalah tentang pengurangan daftar tetapi jawaban yang diterima lebih dekat untuk mengatur pengurangan.
Robino

2
Apa yang harus [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] kembali, dan mengapa? Haruskah menemukan 232 di tengah dan mengembalikan 2142? atau haruskah ia menemukan yang pertama setiap kali dan mengembalikan 1242? Atau sesuatu yang lain? Apa yang saya katakan adalah bahwa ini bukan jawaban yang jelas dan tergantung pada kebutuhan.
McKay

Jawaban:


330

Gunakan pemahaman daftar:

[item for item in x if item not in y]

Jika Anda ingin menggunakan -sintaks infix, Anda bisa melakukan:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(args)

    def __sub__(self, other):
        return self.__class__(*[item for item in self if item not in other])

Anda kemudian dapat menggunakannya seperti:

x = MyList(1, 2, 3, 4)
y = MyList(2, 5, 2)
z = x - y   

Tetapi jika Anda tidak benar-benar membutuhkan properti daftar (misalnya, memesan), cukup gunakan set sebagai jawaban yang disarankan.


10
@ admica, jangan gunakan listuntuk nama variabel karena bayangan listkonstruktor. Jika Anda menggunakan 'daftar', silakan mendahului dengan garis bawah. Juga, dengan menjatuhkannya *, Anda memecahkan kode saya ...
aaronasterling

19
Jika Anda melakukannya, [1,1,2,2] - [1,2]Anda akan mendapatkan daftar kosong. [1,1,2,2] - [2]memberi [1,1]Jadi bukan benar-benar daftar substraksi, itu lebih seperti "Daftar dari Daftar X tanpa elemen dari set Y " .
Alfred Zien

@ AlfredZien apa yang dia katakan
RetroCode

Metode pemahaman daftar jauh lebih lambat (dalam contoh saya) daripada metode perbedaan set.
redfiloux

1
@BarnabasSzabolcs: Itu tidak akan menyelamatkan apa pun, karena itu akan dikonversi ymenjadi setsebelum setiap cek (yang biayanya mirip dengan karya asli). Anda harus melakukan di yset = set(y)luar listcomp, kemudian menguji if item not in yset, atau sebagai peretasan yang mengerikan, melakukan [item for yset in [set(y)] for item in x if item not in yset]yang menyalahgunakan daftar susunan bersarang untuk menyimpan cache ysetsebagai satu-baris. Solusi one-liner yang sedikit kurang jelek yang berkinerja cukup akan digunakan list(itertools.filterfalse(set(y).__contains__, x))karena argumen untuk filterfalsehanya dibangun sekali.
ShadowRanger

259

Gunakan setel perbedaan

>>> z = list(set(x) - set(y))
>>> z
[0, 8, 2, 4, 6]

Atau Anda mungkin hanya memiliki set x dan y sehingga Anda tidak perlu melakukan konversi apa pun.


50
ini akan kehilangan pemesanan apa pun. Itu mungkin atau mungkin tidak masalah tergantung pada konteksnya.
aaronasterling

63
Ini juga akan kehilangan kemungkinan duplikat yang mungkin perlu / ingin dipertahankan.
Opal

Saya mendapatkanTypeError: unhashable type: 'dict'
Havnar

Ini jauh lebih cepat dalam kasus di mana daftar yang dibandingkan adalah besar
JqueryToAddNumbers

2
Jika pemesanan dan duplikat item dalam daftar tidak penting untuk konteksnya, ini adalah jawaban yang bagus plus itu sangat mudah dibaca.
Watt Iamsuri

37

Itu adalah operasi "atur pengurangan". Gunakan struktur data yang ditetapkan untuk itu.

Dengan Python 2.7:

x = {1,2,3,4,5,6,7,8,9,0}
y = {1,3,5,7,9}
print x - y

Keluaran:

>>> print x - y
set([0, 8, 2, 4, 6])

1
list (set ([1,2,3,4,5]) - set ([1,2,3])) = [4, 5] jadi itulah daftar masing-masing yang ditetapkan terlebih dahulu, kemudian kurangi (atau beda satu arah) ) dan kembali ke daftar.
gseattle

2
Tidak bagus jika Anda ingin mempertahankan urutan item asli dari set x.
Zahran


20

Untuk banyak kasus penggunaan, jawaban yang Anda inginkan adalah:

ys = set(y)
[item for item in x if item not in ys]

Ini adalah gabungan antara jawaban aaronasterling dan jawaban quantumSoup .

Versi aaronasterling melakukan len(y)perbandingan item untuk setiap elemen x, sehingga dibutuhkan waktu kuadratik. Versi quantumSoup menggunakan set, sehingga ia melakukan pencarian set waktu konstan tunggal untuk setiap elemen dalam x—tapi, karena ia mengubah keduanya x dan ymenjadi set, ia kehilangan urutan elemen Anda.

Dengan mengubah hanya ymenjadi satu set, dan mengulanginya xsecara berurutan, Anda mendapatkan yang terbaik dari kedua dunia — waktu linier, dan pelestarian pesanan. *


Namun, ini masih memiliki masalah dari versi quantumSoup: Ini membutuhkan elemen Anda untuk dapat hashable. Itu cukup banyak dibangun ke dalam sifat set. ** Jika Anda mencoba, misalnya, kurangi daftar dicts dari daftar dicts lain, tetapi daftar untuk mengurangi besar, apa yang Anda lakukan?

Jika Anda dapat menghias nilai-nilai Anda dengan cara yang dapat di-hashable, itu memecahkan masalah. Misalnya, dengan kamus datar yang nilainya sendiri dapat diunggah:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]

Jika tipe Anda sedikit lebih rumit (misalnya, sering Anda berurusan dengan nilai yang kompatibel dengan JSON, yang dapat hashable, atau daftar atau dikte yang nilainya secara rekursif adalah jenis yang sama), Anda masih dapat menggunakan solusi ini. Tetapi beberapa tipe tidak dapat dikonversi menjadi hashable apa pun.


Jika barang Anda tidak, dan tidak dapat dibuat, dapat hashable, tetapi mereka dapat dibandingkan, Anda setidaknya bisa mendapatkan waktu log-linear ( O(N*log M), yang jauh lebih baik daripada O(N*M)waktu solusi daftar, tetapi tidak sebagus yang O(N+M)saat solusi set) dengan menyortir dan menggunakan bisect:

ys = sorted(y)
def bisect_contains(seq, item):
    index = bisect.bisect(seq, item)
    return index < len(seq) and seq[index] == item
[item for item in x if bisect_contains(ys, item)]

Jika barang Anda tidak dapat dipilah atau sebanding, maka Anda terjebak dengan solusi kuadratik.


* Perhatikan bahwa Anda juga bisa melakukan ini dengan menggunakan sepasang OrderedSetbenda, yang untuknya Anda dapat menemukan resep dan modul pihak ketiga. Tapi saya pikir ini lebih sederhana.

** Alasan set lookup adalah waktu yang konstan adalah yang harus dilakukan hanyalah nilai hash dan lihat apakah ada entri untuk hash itu. Jika tidak dapat mengaitkan nilainya, ini tidak akan berhasil.


7

Mencari nilai di set lebih cepat daripada mencari di daftar:

[item for item in x if item not in set(y)]

Saya percaya ini akan skala sedikit lebih baik daripada:

[item for item in x if item not in y]

Keduanya mempertahankan urutan daftar.


Akankah cache set(y)dan tidak dikonversi yke set baru di setiap loop? Jika tidak, Anda akan lebih jawaban kebutuhan abarnert ini: ys = set(y); [i for i in x if i not in ys].
Jacktose

2
Beberapa pengujian kasar menunjukkan bahwa if i not in set(y)membutuhkan waktu 25% lebih lama dari if i not in y(di mana yada daftar). Pra-konversi set membutuhkan waktu 55% lebih sedikit. Diuji dengan cukup pendek xdan y, tetapi perbedaan harus lebih diucapkan dengan panjang, jika ada.
Jacktose

1
@Jacktose: Ya, solusi ini lebih berfungsi, karena harus beralih dan hash setiap elemen yuntuk setiap elemen x; kecuali perbandingan kesetaraan benar-benar mahal relatif terhadap perhitungan hash, ini akan selalu kalah dari biasa item not in y.
ShadowRanger

@ShadowRanger yang masuk akal. Jika mengatur konversi adalah cara yang andal lebih cepat untuk melakukan pemeriksaan itu, Anda akan berpikir bahwa kompiler akan selalu melakukan pemeriksaan seperti itu.
Jacktose

5

Jika daftar memungkinkan elemen duplikat, Anda dapat menggunakan Penghitung dari koleksi:

from collections import Counter
result = list((Counter(x)-Counter(y)).elements())

Jika Anda perlu mempertahankan urutan elemen dari x:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]

Ini bagus, meskipun tidak salah memesan; memperbaiki itu sedikit lebih rumit .
ShadowRanger

@ShadowRanger, memang. tapi hanya sedikit.
Alain T.

Jangan pedulikan saya, saya hanya akan bergidik pada listcomps dengan caching dan efek samping (walaupun saya kira kombinasi keduanya menghilangkan efek samping yang terlihat secara eksternal?). :-)
ShadowRanger

Juga, kode ini tidak akan berfungsi seperti yang tertulis; Counter.subtracttidak menghapus elemen bernilai nol ( -dan -=lakukan, tetapi tidak subtract), jadi Anda tidak akan pernah berhenti menghapus elemen. Anda ingin mengganti not v in cdengan not c[v](yang mengembalikan nol untuk elemen yang tidak ada, sehingga Anda dapat dengan aman menguji pengembalian untuk "noliness" melalui not).
ShadowRanger

@ShadowRanger, Tangkapan bagus! Perbaiki sekarang.
Alain T.

3

Saya pikir cara termudah untuk mencapai ini adalah dengan menggunakan set ().

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> y = [1,3,5,7,9]  
>>> list(set(x)- set(y))
[0, 2, 4, 6, 8]

3

Solusi lain memiliki satu dari beberapa masalah:

  1. Mereka tidak menjaga ketertiban, atau
  2. Mereka tidak menghapus jumlah elemen yang tepat, misalnya untuk x = [1, 2, 2, 2]dan y = [2, 2]mereka mengonversi ymenjadi set, dan menghapus semua elemen yang cocok ( [1]hanya menyisakan ) atau menghapus salah satu dari setiap elemen unik (meninggalkan [1, 2, 2]), ketika perilaku yang tepat akan menghapus 2dua kali, pergi [1, 2], atau
  3. Mereka O(m * n)bekerja, di mana solusi optimal dapat O(m + n)bekerja

Alain berada di jalur yang benar denganCounter memecahkan # 2 dan # 3, tetapi solusi itu akan kehilangan pemesanan. Solusi yang menjaga ketertiban (menghapus nsalinan pertama dari setiap nilai untuk npengulangan dalam listnilai yang akan dihapus) adalah:

from collections import Counter

x = [1,2,3,4,3,2,1]  
y = [1,2,2]  
remaining = Counter(y)

out = []
for val in x:
    if remaining[val]:
        remaining[val] -= 1
    else:
        out.append(val)
# out is now [3, 4, 3, 1], having removed the first 1 and both 2s.

Cobalah online!

Untuk membuatnya menghapus salinan terakhir dari setiap elemen, cukup ubah forloop ke for val in reversed(x):dan tambahkan out.reverse()segera setelah keluar dari forloop.

Membangun Counteradalah O(n)dalam hal y's panjang, iterasi xadalah O(n)dalam hal x' s panjang, dan Counterpengujian keanggotaan dan mutasi adalah O(1), sementara list.appenddiamortisasi O(1)(diberikan appendbisa O(n), tapi bagi banyak appends, secara keseluruhan besar-O rata-rata O(1)sejak lebih sedikit dan lebih sedikit dari mereka membutuhkan realokasi), sehingga keseluruhan pekerjaan yang dilakukan adalah O(m + n).

Anda juga dapat menguji untuk menentukan apakah ada elemen di dalamnya yyang tidak dihapus dari xpengujian:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts

Catatan: Ini memang membutuhkan nilai-nilai yang dapat hashable, tetapi solusi apa pun yang tidak memerlukan objek hash juga bukan tujuan umum (misalnya dapat menghitung ints ke dalam array panjang tetap) atau harus melakukan lebih dari O(m + n)pekerjaan (misalnya big terbaik berikutnya -O akan membuat diurutkan listnilai unik / pasangan hitungan, mengubah O(1) dictpencarian menjadi O(log n)pencarian biner, Anda akan membutuhkan nilai unik dengan jumlah mereka, bukan hanya mengurutkan nilai-nilai non-unik, karena jika tidak, Anda akan membayar O(n)biaya untuk menghapus elemen dari yang diurutkan list).
ShadowRanger

2

Coba ini.

def subtract_lists(a, b):
    """ Subtracts two lists. Throws ValueError if b contains items not in a """
    # Terminate if b is empty, otherwise remove b[0] from a and recurse
    return a if len(b) == 0 else [a[:i] + subtract_lists(a[i+1:], b[1:]) 
                                  for i in [a.index(b[0])]][0]

>>> x = [1,2,3,4,5,6,7,8,9,0]
>>> y = [1,3,5,7,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0]
>>> x = [1,2,3,4,5,6,7,8,9,0,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0, 9]     #9 is only deleted once
>>>

1

Jawaban yang diberikan oleh @aaronasterling penampilan yang baik, bagaimanapun, tidak kompatibel dengan antarmuka default daftar: x = MyList(1, 2, 3, 4)vs x = MyList([1, 2, 3, 4]). Dengan demikian, kode di bawah ini dapat digunakan sebagai ramah daftar python:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(*args)

    def __sub__(self, other):
        return self.__class__([item for item in self if item not in other])

Contoh:

x = MyList([1, 2, 3, 4])
y = MyList([2, 5, 2])
z = x - y

0

Saya pikir ini lebih cepat:

In [1]: a = [1,2,3,4,5]

In [2]: b = [2,3,4,5]

In [3]: c = set(a) ^ set(b)

In [4]: c
Out[4]: {1}

Ini bukan pengurangan. Sebenarnya, ini adalah perbedaan simetris antara dua daftar.
Parth Chauhan

Selain itu ini hanya berfungsi untuk objek hashable di dalam daftar
zhukovgreen

-1

Contoh ini mengurangi dua daftar:

# List of pairs of points
list = []
list.append([(602, 336), (624, 365)])
list.append([(635, 336), (654, 365)])
list.append([(642, 342), (648, 358)])
list.append([(644, 344), (646, 356)])
list.append([(653, 337), (671, 365)])
list.append([(728, 13), (739, 32)])
list.append([(756, 59), (767, 79)])

itens_to_remove = []
itens_to_remove.append([(642, 342), (648, 358)])
itens_to_remove.append([(644, 344), (646, 356)])

print("Initial List Size: ", len(list))

for a in itens_to_remove:
    for b in list:
        if a == b :
            list.remove(b)

print("Final List Size: ", len(list))

8
Hindari ini, ini O (N ^ 2)
Alexander - Reinstate Monica
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.