Thao tác trừ danh sách Python


227

Tôi muốn làm một cái gì đó tương tự như thế này:

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

Nhưng điều này không được hỗ trợ bởi danh sách python Cách tốt nhất để làm điều đó là gì?


@ezdazuzena đây không phải là chất nền. Đây là sự khác biệt giữa hai danh sách. Chia sẻ của bạn không phải là sự công bố của câu hỏi này.
Celik

1
Điều gì [2, 2] - [2] trở lại? []? [2]?
McKay

@McKay [2,2] - [2] sẽ trả về [2]. [2,2] - [1,2,2,3] sẽ trở lại []
Robino

Câu hỏi này là về phép trừ danh sách nhưng câu trả lời được chấp nhận gần hơn để đặt phép trừ.
Robino

2
Điều gì [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] trở lại, và tại sao? Có nên tìm 232 ở giữa và trả lại 2142 không? hoặc nó nên tìm lần đầu tiên mỗi lần và trả lại 1242? Hay cái gì khác? Điều tôi đang nói là đây không phải là những câu trả lời rõ ràng và phụ thuộc vào nhu cầu.
McKay

Câu trả lời:


330

Sử dụng một danh sách hiểu:

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

Nếu bạn muốn sử dụng -cú pháp infix, bạn có thể thực hiện:

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

sau đó bạn có thể sử dụng nó như sau:

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

Nhưng nếu bạn hoàn toàn không cần thuộc tính danh sách (ví dụ: đặt hàng), chỉ cần sử dụng các bộ như các câu trả lời khác được đề xuất.


10
@admica, không sử dụng listtên biến vì nó tạo bóng cho hàm listtạo. Nếu bạn sử dụng 'danh sách', vui lòng đặt trước nó bằng dấu gạch dưới. Ngoài ra, bằng cách thả *, bạn đã phá vỡ mã của tôi ...
aaronasterling

19
Nếu bạn làm [1,1,2,2] - [1,2]bạn sẽ nhận được danh sách trống. [1,1,2,2] - [2]cho [1,1]nên nó không phải là thực sự danh sách trừ, nó là giống như "Danh từ Danh sách X mà không cần các yếu tố từ bộ Y " .
Alfred Zien

@AlfredZien những gì anh ấy nói
RetroCode

Phương pháp hiểu danh sách chậm hơn (trong ví dụ của tôi) so với phương pháp khác biệt đã đặt.
redfiloux

1
@BarnabasSzabolcs: Điều đó sẽ không tiết kiệm được một thứ, bởi vì nó sẽ chuyển đổi ythành settrước mỗi lần kiểm tra (chi phí tương tự như công việc ban đầu). Bạn cần phải thực hiện yset = set(y)bên ngoài listcomp, sau đó kiểm tra if item not in ysethoặc như một hack nghiêm trọng, thực hiện [item for yset in [set(y)] for item in x if item not in yset]hành vi lạm dụng listcomps lồng nhau để lưu cache ysetdưới dạng một lớp lót. Một giải pháp một lớp lót ít xấu xí hơn sẽ thực hiện đầy đủ sẽ được sử dụng list(itertools.filterfalse(set(y).__contains__, x))vì đối số filterfalsechỉ được xây dựng một lần.
ShadowRanger

259

Sử dụng cài đặt chênh lệch

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

Hoặc bạn có thể có x và y được đặt để bạn không phải thực hiện bất kỳ chuyển đổi nào.


50
Điều này sẽ mất bất kỳ thứ tự. Điều đó có thể hoặc không quan trọng tùy thuộc vào bối cảnh.
aaronasterling

63
Điều này cũng sẽ mất bất kỳ bản sao có thể cần / muốn duy trì.
Opal

Tôi nhận đượcTypeError: unhashable type: 'dict'
Havnar

Đây là cách nhanh hơn trong trường hợp danh sách được so sánh lớn
JqueryToAddNumbers

2
Nếu thứ tự và trùng lặp của các mục trong danh sách không quan trọng đối với bối cảnh, thì đây là một câu trả lời tuyệt vời cộng với nó rất dễ đọc.
Watt Iamsuri

37

Đó là một hoạt động "đặt trừ". Sử dụng cấu trúc dữ liệu thiết lập cho điều đó.

Trong Python 2.7:

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

Đầu ra:

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

1
list (set ([1,2,3,4,5]) - set ([1,2,3])) = [4, 5] để liệt kê từng cái để đặt trước, sau đó trừ (hoặc khác một chiều ) và quay lại danh sách.
gseatussy

2
Không tốt nếu bạn muốn duy trì thứ tự mục gốc của bộ x.
Zahran

34

nếu các mục trùng lặp và đặt hàng có vấn đề:

[i for i in a if not i in b or b.remove(i)]

a = [1,2,3,3,3,3,4]
b = [1,3]
result: [2, 3, 3, 3, 4]

2
Điều này hoạt động, mặc dù đó là O(m * n)thời gian chạy (và tôi co rúm mỗi khi listcomp bao gồm các tác dụng phụ); bạn có thể cải thiện nó bằng cách sử dụngcollections.Counter để có được O(m + n)thời gian chạy.
ShadowRanger

Tôi đang có một thời gian khó hiểu điều này, ai đó có thể giải thích?
anushka

20

Đối với nhiều trường hợp sử dụng, câu trả lời bạn muốn là:

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

Đây là sự kết hợp giữa câu trả lời của aaronasterlingcâu trả lời của quantumSoup .

Phiên bản của aaronasterling thực hiện len(y)so sánh vật phẩm cho từng yếu tố x, vì vậy phải mất thời gian bậc hai. Phiên bản của quantumSoup sử dụng các tập hợp, do đó, nó thực hiện tra cứu tập hợp thời gian không đổi duy nhất cho từng phần tử trong Tập tin x, bởi vì nó chuyển đổi cả hai xythành tập hợp, nó làm mất thứ tự các phần tử của bạn.

Bằng cách chuyển đổi chỉ ythành một tập hợp và lặp lại xtheo thứ tự, bạn sẽ có được thời gian tuyến tính tốt nhất của cả hai thế giới và bảo toàn trật tự. *


Tuy nhiên, điều này vẫn có một vấn đề từ phiên bản của quantumSoup: Nó yêu cầu các yếu tố của bạn có thể được băm. Điều đó được xây dựng khá nhiều vào bản chất của các bộ. ** Nếu bạn đang cố gắng, ví dụ, trừ một danh sách các ký hiệu từ một danh sách các ký tự khác, nhưng danh sách để trừ là lớn, bạn sẽ làm gì?

Nếu bạn có thể trang trí các giá trị của mình theo một cách nào đó mà chúng có thể băm được, điều đó sẽ giải quyết được vấn đề. Ví dụ: với một từ điển phẳng có giá trị tự băm:

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

Nếu các loại của bạn phức tạp hơn một chút (ví dụ: thường thì bạn đang xử lý các giá trị tương thích JSON, có thể băm hoặc liệt kê hoặc các ký tự có giá trị đệ quy cùng loại), bạn vẫn có thể sử dụng giải pháp này. Nhưng một số loại không thể chuyển đổi thành bất cứ thứ gì có thể băm được.


Nếu các mục của bạn không và không thể được tạo ra, có thể băm, nhưng chúng có thể so sánh được, thì ít nhất bạn có thể có được thời gian log-linear ( O(N*log M), tốt hơn rất nhiều so với O(N*M)thời gian của giải pháp danh sách, nhưng không tốt bằng các O(N+M)thời điểm các giải pháp quy định) bằng cách phân loại và sử dụng 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)]

Nếu các mục của bạn không thể băm hay so sánh được, thì bạn bị mắc kẹt với giải pháp bậc hai.


* Lưu ý rằng bạn cũng có thể thực hiện việc này bằng cách sử dụng một cặp OrderedSetđối tượng mà bạn có thể tìm thấy công thức nấu ăn và mô-đun của bên thứ ba. Nhưng tôi nghĩ điều này đơn giản hơn.

** Lý do thiết lập tra cứu là thời gian không đổi là tất cả những gì phải làm là băm giá trị và xem liệu có mục nào cho hàm băm đó không. Nếu nó không thể băm giá trị, điều này sẽ không hoạt động.


7

Tra cứu giá trị theo bộ nhanh hơn tìm kiếm trong danh sách:

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

Tôi tin rằng điều này sẽ mở rộng hơn một chút so với:

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

Cả hai đều giữ trật tự của danh sách.


Nó sẽ lưu trữ set(y)và không chuyển đổi ythành một bộ mới trên mỗi vòng lặp? Nếu không, bạn cần câu trả lời của abarnert : ys = set(y); [i for i in x if i not in ys].
Jacktose

2
Một số thử nghiệm sơ bộ cho thấy if i not in set(y)mất nhiều thời gian hơn 25% so với if i not in y( ydanh sách ở đâu). Chuyển đổi trước bộ mất ít thời gian hơn 55%. Đã thử nghiệm với khá ngắn xy, nhưng sự khác biệt sẽ rõ rệt hơn với độ dài, nếu có bất cứ điều gì.
Jacktose

1
@Jacktose: Vâng, giải pháp này thực hiện được nhiều công việc hơn, bởi vì nó phải lặp lại và băm mọi phần tử ycho mọi phần tử của x; trừ khi so sánh bằng thực sự tốn kém so với tính toán băm, điều này sẽ luôn thua đơn giản item not in y.
ShadowRanger

@ShadowRanger có ý nghĩa. Nếu chuyển đổi được đặt là cách nhanh hơn đáng tin cậy để thực hiện kiểm tra đó, bạn sẽ nghĩ trình biên dịch sẽ luôn luôn thực hiện kiểm tra theo cách đó.
Jacktose

5

Nếu danh sách cho phép các phần tử trùng lặp, bạn có thể sử dụng Bộ đếm từ các bộ sưu tập:

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

Nếu bạn cần giữ nguyên thứ tự các phần tử từ x:

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

Điều này là tốt, mặc dù nó mất trật tự; sửa lỗi đó phức tạp hơn một chút .
ShadowRanger

@ShadowRanger, nó thực sự. nhưng chỉ một chút thôi
Alain T.

Đừng bận tâm, tôi sẽ rùng mình khi nghe các listcomps với bộ nhớ đệm và tác dụng phụ (mặc dù tôi cho rằng sự kết hợp của cả hai sẽ loại bỏ các tác dụng phụ có thể nhìn thấy bên ngoài?). :-)
ShadowRanger

Ngoài ra, mã này sẽ không hoạt động như văn bản; Counter.subtractkhông xóa các phần tử có giá trị bằng 0 ( --=làm, nhưng không subtract), vì vậy bạn sẽ không bao giờ ngừng xóa các phần tử. Bạn muốn thay thế not v in cbằng not c[v](trả về 0 cho các phần tử không tồn tại, vì vậy bạn có thể kiểm tra mức hoàn trả cho "zeroiness" một cách an toàn thông qua not).
ShadowRanger

@ShadowRanger, bắt tốt! Đã sửa nó ngay.
Alain T.

3

Các giải pháp khác có một trong một vài vấn đề:

  1. Họ không giữ trật tự, hoặc
  2. Họ không xóa một số phần tử chính xác, ví dụ như x = [1, 2, 2, 2]y = [2, 2]họ chuyển đổi ythành a setvà xóa tất cả các phần tử phù hợp ( [1]chỉ để lại ) hoặc xóa một trong từng phần tử duy nhất (để lại [1, 2, 2]), khi hành vi phù hợp sẽ xóa 2hai lần, rời đi [1, 2], hoặc
  3. Họ làm O(m * n)việc, nơi một giải pháp tối ưu có thể làm O(m + n)việc

Alain đã đi đúng hướngCounter để giải quyết # 2 và # 3, nhưng giải pháp đó sẽ mất thứ tự. Giải pháp duy trì trật tự (loại bỏ các nbản sao đầu tiên của mỗi giá trị cho các nlần lặp lại trong các listgiá trị cần xóa) là:

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.

Hãy thử trực tuyến!

Để làm cho nó loại bỏ các bản sao cuối cùng của mỗi phần tử, chỉ cần thay đổi forvòng lặp thành for val in reversed(x):và thêm out.reverse()ngay sau khi thoát khỏi forvòng lặp.

Xây dựng Counterđược O(n)về y's chiều dài, iterating xO(n)về x' s chiều dài, và Counterthử nghiệm thành viên và đột biến là O(1), trong khi list.appendđược phân bổ O(1)(một cho appendcó thể O(n), nhưng đối với nhiều appends, tổng thể lớn-O trung bình O(1)kể từ khi ngày càng ít trong số họ yêu cầu phân bổ lại), vì vậy công việc tổng thể được thực hiện là O(m + n).

Bạn cũng có thể kiểm tra để xác định xem có bất kỳ yếu tố nào trong yđó không bị xóa khỏi xbằng cách kiểm tra không:

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

Lưu ý: Điều này không yêu cầu các giá trị có thể băm được, nhưng bất kỳ giải pháp nào không yêu cầu các đối tượng có thể băm đều không phải là mục đích chung (ví dụ: có thể tính ints vào mảng có độ dài cố định) hoặc phải làm nhiều hơn O(m + n)công việc (ví dụ: lớn nhất tiếp theo -O sẽ tạo một sắp xếp listcác cặp giá trị / số đếm duy nhất, thay đổi O(1) dicttra cứu thành O(log n)tìm kiếm nhị phân; bạn cần các giá trị duy nhất với số lượng của chúng, không chỉ sắp xếp các giá trị không duy nhất, vì nếu không, bạn sẽ phải trả O(n)chi phí để loại bỏ các yếu tố từ sắp xếp list).
ShadowRanger

2

Thử cái này.

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

2

Tôi nghĩ cách dễ nhất để đạt được điều này là sử dụng 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]

1

Câu trả lời được cung cấp bởi @aaronasterling ngoại hình đẹp, tuy nhiên, nó không phải là tương thích với giao diện mặc định của danh sách: x = MyList(1, 2, 3, 4)vs x = MyList([1, 2, 3, 4]). Do đó, đoạn mã dưới đây có thể được sử dụng như một thân thiện với danh sách python hơn:

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

Thí dụ:

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

0

Tôi nghĩ rằng điều này là nhanh hơn:

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}

Đây không phải là phép trừ. Trong thực tế, đây là sự khác biệt đối xứng giữa hai danh sách.
Parth Chauhan

Hơn nữa, điều này chỉ hoạt động cho các đối tượng có thể
băm

-1

Ví dụ này trừ hai danh sách:

# 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
Tránh điều này, đó là O (N ^ 2)
Alexander - Tái lập Monica
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.