파이썬리스트 빼기 연산


227

나는 이것과 비슷한 것을하고 싶다 :

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

그러나 이것은 파이썬 목록에서 지원하지 않습니다. 가장 좋은 방법은 무엇입니까?


@ezdazuzena 이것은 뺄셈이 아닙니다. 이것이 두 목록의 차이점입니다. 당신의 공유는이 질문의 중복이 아닙니다.
Celik

1
[2, 2]-[2]는 무엇을 반환해야하나요? []? [2]?
McKay

@McKay [2,2]-[2]는 [2]를 반환해야합니다. [2,2]-[1,2,2,3]은 []를 반환해야 함
Robino

이 질문은 목록 빼기에 관한 것이지만 허용되는 대답은 빼기를 설정하는 데 더 가깝습니다.
Robino

2
[2, 1, 2, 3, 2, 4, 2]-[2, 3, 2]는 무엇을 반환해야하며, 그 이유는 무엇입니까? 중간에 232가 있고 2142를 반환해야합니까? 또는 매번 처음을 찾아 1242를 반환해야합니까? 또는 다른 것? 내가 말하는 것은 이것들이 명백한 대답이 아니며 필요에 달려 있다는 것입니다.
McKay

답변:


330

목록 이해력을 사용하십시오.

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

-infix 구문 을 사용하려면 다음을 수행하십시오.

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

그런 다음 다음과 같이 사용할 수 있습니다.

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

그러나 목록 속성 (예 : 순서)이 절대적으로 필요하지 않은 경우 다른 답변에서 권장하는대로 세트를 사용하십시오.


10
@admica, 생성자를 가리는 list변수 이름 에는 사용하지 마십시오 list. 'list'를 사용하는 경우 앞에 밑줄을 붙이십시오. 또한을 삭제하면 *내 코드가 깨졌습니다.
aaronasterling

19
당신이 경우에 [1,1,2,2] - [1,2]당신은 빈 목록을 얻을 것이다. [1,1,2,2] - [2]제공 [1,1]정말 목록 빼기되지 않도록 그것은 더처럼, "목록에서 목록 X 세트에서 요소가없는 Y " .
Alfred Zien

@AlfredZien 그가 말한 것
RetroCode

목록 이해 방법은 설정 차이 방법보다 느립니다 (제 예에서는).
redfiloux

1
@BarnabasSzabolcs : 그것은 변환하기 때문에 즉, 일을 저장하지 않습니다 yA를 set하기 전에 모든 (원작과 비슷한 비용 인) 검사. yset = set(y)listcomp 외부에서 수행 한 다음 test if item not in yset또는 중대 해킹으로 [item for yset in [set(y)] for item in x if item not in yset]중첩 된 listcomp를 악용 yset하여 하나의 라이너로 캐시해야합니다 . list(itertools.filterfalse(set(y).__contains__, x))인수 filterfalse가 한 번만 구성 되므로 적절하게 수행하는 약간 덜 추악한 한 줄짜리 솔루션이 사용 됩니다.
ShadowRanger

259

설정된 차이 사용

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

또는 x와 y 만 설정하면 변환을 수행 할 필요가 없습니다.


50
주문이 손실됩니다. 상황에 따라 중요하거나 중요하지 않을 수 있습니다.
aaronasterling

63
또한 유지 관리해야 할 수도있는 가능한 중복 항목을 잃게됩니다.
Opal

나는TypeError: unhashable type: 'dict'
Havnar

이것은 비교되는 목록이 큰 경우에 더 빠릅니다.
JqueryToAddNumbers

2
목록에있는 항목의 순서와 중복이 컨텍스트에 중요하지 않은 경우, 이는 훌륭한 답변이며 매우 읽기 쉽습니다.
와트 Iamsuri

37

이것은 "세트 빼기"연산입니다. 이를 위해 설정된 데이터 구조를 사용하십시오.

파이썬 2.7에서 :

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

산출:

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

1
list (set ([1,2,3,4,5])-set ([1,2,3])) = [4, 5] 그래서 먼저 설정할 목록을 뺀 다음 빼기 (또는 단방향 diff) ) 목록으로 돌아갑니다.
gseattle

2
x 세트의 원래 품목 순서를 유지하려면 좋지 않습니다.
Zahran

34

중복 및 주문 품목에 문제가있는 경우 :

[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
O(m * n)런타임 이지만 작동 합니다 (목록 작성에 부작용이있을 때마다 울 립니다 ). 당신은 그것을 사용하여 향상시킬 수 있습니다collections.Counter 얻기 위해 O(m + n)런타임.
ShadowRanger

나는 이것을 이해하는 데 어려움을 겪고 있습니다. 누군가가 설명 할 수 있습니까?
anushka

20

많은 사용 사례에서 원하는 답변은 다음과 같습니다.

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

이것은 aaronasterling의 답변quantumSoup의 답변 사이의 하이브리드 입니다.

aaronasterling의 버전은의 len(y)각 요소에 대한 항목 비교를 수행 x하므로 2 차 시간이 걸립니다. quantumSoup의 버전을 사용하는 세트는, 그래서 각 요소에 대해 하나의 일정 시간의 집합을 조회하지 x가 변환하기 때문에, -하지만를 모두 xy세트로, 그것은 당신의 요소의 순서를 잃는다.

y세트 로만 변환 x하고 순서를 반복 하면 선형 시간과 순서 보존이라는 두 가지 이점을 모두 누릴 수 있습니다. *


그러나 이것은 여전히 ​​quantumSoup의 버전에서 문제가 있습니다 : 요소를 해시 할 수 있어야합니다. 예를 들어, 다른 dicts 목록에서 dicts 목록을 빼려고하지만 뺄 목록이 큰 경우 어떻게해야합니까?

해시가 가능한 방식으로 값을 꾸밀 수 있다면 문제가 해결됩니다. 예를 들어, 값 자체가 해시 가능한 플랫 사전의 경우 :

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

유형이 조금 더 복잡한 경우 (예 : 종종 해시 가능한 JSON 호환 값 또는 값이 재귀 적으로 동일한 유형 인 목록 또는 딕트를 처리하는 경우)이 솔루션을 계속 사용할 수 있습니다. 그러나 일부 유형은 해시 가능한 것으로 변환 할 수 없습니다.


아이템이 해시 가능하지 않고 해시 가능하지만 비교 가능하다면 적어도 로그 선형 시간을 얻을 수 있습니다 ( O(N*log M), 이는 O(N*M)목록 솔루션 시간 보다 훨씬 좋지만 좋지는 않습니다. O(N+M)정렬하고 사용하여 설정된 솔루션 의 시간) 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)]

아이템이 해시 가능하거나 비교 가능하지 않으면 2 차 솔루션에 갇혀 있습니다.


* OrderedSet레시피 및 타사 모듈을 찾을 수 있는 한 쌍의 객체 를 사용하여이 작업을 수행 할 수도 있습니다. 그러나 나는 이것이 더 간단하다고 생각합니다.

** 집합 조회가 일정한 시간 인 이유는 값을 해시하고 해당 해시에 대한 항목이 있는지 확인하기 때문입니다. 값을 해시 할 수 없으면 작동하지 않습니다.


7

세트에서 값을 찾는 것이 목록에서 찾는 것보다 빠릅니다.

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

나는 이것이 다음보다 약간 더 잘 확장 될 것이라고 믿는다.

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

둘 다 목록의 순서를 유지합니다.


각 루프에서 캐시 set(y)하고 y새 세트 로 변환하지 않습니까? 그렇지 않으면 abarnert의 답변이 필요합니다 ys = set(y); [i for i in x if i not in ys].
Jacktose

2
일부 거친 테스트는 그 제안 if i not in set(y)의 25 %보다 더 오래 걸립니다 if i not in y(여기서 y목록입니다). 세트를 사전 변환하는 데 시간이 55 % 단축됩니다. 꽤 짧은 테스트 x하고 y있지만, 어떤 경우 차이는 더, 길이 발음을하셔야합니다.
Jacktose

1
@Jacktose : 그것은 반복 처리 및 해시에 있기 때문에 그래,이 솔루션은 더 많은 작업을 수행 마다 의 요소 y에 대한 모든 의 요소 x; 평등 비교가 해시 계산에 비해 실제로 비싸지 않으면, 이것은 항상 평범 해 item not in y집니다.
ShadowRanger

말이되는 @ShadowRanger. set 변환이 그 검사를 수행하는 데 훨씬 더 빠른 방법이라면 컴파일러는 항상 그 방법으로 검사를 수행한다고 생각할 것입니다.
Jacktose

5

목록에 중복 요소가 허용되면 컬렉션의 카운터를 사용할 수 있습니다.

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

x의 요소 순서를 유지해야하는 경우 :

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

순서가 없어지더라도 좋습니다. 이 고정은 약간 더 복잡하다 .
ShadowRanger

@ShadowRanger, 사실입니다. 그러나 조금.
Alain T.

걱정하지 마십시오. 캐싱 및 부작용으로 listcomps에서 떨릴 것입니다 (두 가지의 조합이 외부에서 보이는 부작용을 제거한다고 가정하지만). :-)
ShadowRanger

또한이 코드는 작성된대로 작동하지 않습니다. Counter.subtract제로 평가 요소를 제거 (하지 않는 --=수행하지만 subtract당신은 요소를 제거 멈추지 않을 것입니다, 그래서). 당신은 대체 할 것 not v in cnot c[v](안전하게 "zeroiness"를 통해 대한 반환을 테스트 할 수 있도록 반환, 존재하지 않는 요소에 대한 제로한다 not).
ShadowRanger

@ShadowRanger, 잘 잡아! 지금 고쳤습니다.
Alain T.

3

다른 솔루션에는 몇 가지 문제 중 하나가 있습니다.

  1. 순서를 유지하지 않거나
  2. 그들은 예 : 요소의 정확한 수를 제거하지 마십시오 x = [1, 2, 2, 2]그리고 y = [2, 2]그들은 변환 yA를 set, 그리고 하나 (떠나 일치하는 모든 요소를 제거 [1](떠나 각각의 고유 한 요소 중 하나에 한함)하거나 제거 [1, 2, 2]제거하기 위해 적절한 행동이 될 것이다), 2두 번을 퇴거[1, 2] 거나
  3. 그들은 할 O(m * n)최적의 솔루션이 할 수있는 일, O(m + n)일을

Alain은Counter 2 번과 3 번을 해결하기 위해 올바른 길을 가고 있었지만 그 솔루션은 주문을 잃을 것입니다. 순서를 유지하는 솔루션 (제거 할 n값에서 n반복 할 각 값 의 첫 번째 사본 list제거)은 다음과 같습니다.

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.

온라인으로 사용해보십시오!

각 요소 의 마지막 사본을 제거하려면 for루프를 변경하고 종료 후 즉시 for val in reversed(x):추가 out.reverse()하십시오.for .

가 구축 Counter되는 O(n)측면에서 y의 길이 반복하는가 x이다 O(n)의 측면에서 x길이의 ', 그리고 Counter회원 테스트 및 돌연변이는 O(1)동안 list.append상각하고 O(1)주어진 ( append이 될 수 O(n)있지만, 많은 append의 전반적인 큰-O의 평균 O(1)줄어들고 있기 때문에 그들 중 재 할당이 필요합니다)O(m + n) 입니다.

테스트를 통해 y제거되지 않은 요소가 있는지 확인하기 위해 테스트 할 수도 있습니다 x.

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

참고 :이 수행 해쉬로 값을 필요로하지만, 해쉬 객체를 필요로하지 않는 솔루션은 범용 아닌 하나 (셀 수 있습니다 예를 들어, int고정 길이 배열에들) 또는보다 더 많은 일을해야한다 O(m + n)(일 예를 들면 다음 가장 큰를 -O는 list고유 한 값 / 카운트 쌍을 정렬 하여 O(1) dict조회를 O(log n)이진 검색 으로 변경하는 것입니다. 단순한 고유하지 않은 값뿐만 아니라 개수와 함께 고유 한 값이 필요합니다. 그렇지 않으면 O(n)제거 하는 데 비용이 들기 때문에 정렬 된 요소 list).
ShadowRanger

2

이 시도.

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

이것을 달성하는 가장 쉬운 방법은 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

외모의 좋은 @aaronasterling 제공하는 대답은, 그러나, 목록의 기본 인터페이스와 호환되지 않습니다 : x = MyList(1, 2, 3, 4)x = MyList([1, 2, 3, 4]). 따라서 아래 코드는 더 파이썬 목록 친화적으로 사용할 수 있습니다.

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

예:

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

0

나는 이것이 더 빠르다고 생각한다.

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}

이것은 빼기가 아닙니다. 실제로 이것은 두 목록의 대칭적인 차이입니다.
Parth Chauhan

게다가 이것은리스트 안의 해시 가능한 객체들에 대해서만 작동합니다
zhukovgreen

-1

이 예에서는 두 목록을 뺍니다.

# 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
이것을 피하십시오, 그것은 O (N ^ 2)입니다
Alexander-Reinstate Monica
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.