322

I want something like this:

>>> x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> y = [1, 3, 5, 7, 9]  
>>> y - x
# should return [2,4,6,8,0]
Mateen Ulhaq
  • 21,459
  • 16
  • 82
  • 123
daydreamer
  • 80,741
  • 175
  • 429
  • 691
  • @ezdazuzena this is not substraction. This is the difference between two lists. Your sharing is not a dublication of this question. – Celik Mar 05 '16 at 22:15
  • 2
    What should [2, 2] - [2] return? []? [2]? – McKay Jan 24 '17 at 20:08
  • @McKay [2,2] - [2] should return [2]. [2,2] - [1,2,2,3] should return [] – Robino Jul 04 '17 at 09:53
  • 1
    This question is about list subtraction but the accepted answer is closer to set subtraction. – Robino Jul 04 '17 at 09:57
  • 3
    What should [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] return, and why? Should it find the 232 in the middle and return 2142? or should it find the first each time and return 1242? Or something else? What I'm saying is that these are not obvious answers and depend on need. – McKay Jul 05 '17 at 15:07
  • Should it could prioritize, or exclusively use subsequences? – McKay Jul 05 '17 at 15:09

16 Answers16

442

Use a list comprehension:

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

If you want to use the - infix syntax, you can just do:

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

you can then use it like:

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

But if you don't absolutely need list properties (for example, ordering), just use sets as the other answers recommend.

aaronasterling
  • 65,653
  • 19
  • 122
  • 125
  • 11
    @admica, don't use `list` for variable names as it shadows the `list` constructor. If you do use 'list', please precede it with an underscore. Also, by dropping the `*`, you broke my code... – aaronasterling Jan 26 '12 at 23:56
  • 33
    If you do `[1,1,2,2] - [1,2]` you will get empty list. `[1,1,2,2] - [2]` gives `[1,1]` So it is not really list substraction, it is more like _"List from List **X** without elements from set **Y**"_. – Alfred Zien Feb 06 '16 at 10:25
  • @AlfredZien what he said – RetroCode Sep 15 '16 at 16:04
  • The list comprehension method is way slower (in my example) than the set difference method. – redfiloux Feb 26 '19 at 10:22
  • for larger y's: [item for item in x if item not in set(y)] – Barney Szabolcs Sep 04 '19 at 12:58
  • 1
    @BarnabasSzabolcs: That won't save a thing, because it will convert `y` to a `set` before *every* check (which is similar cost to original work). You'd need to either do `yset = set(y)` outside the listcomp, then test `if item not in yset`, or as an egregious hack, do `[item for yset in [set(y)] for item in x if item not in yset]` which abuses nested listcomps to cache the `yset` as a one-liner. A slightly less ugly one-liner solution that performs adequately would be to use `list(itertools.filterfalse(set(y).__contains__, x))` because the argument to `filterfalse` is only constructed once. – ShadowRanger Sep 05 '19 at 22:03
  • This works but keep in mind that iterations are slow. I prefer next solution with subtracting set from set which seems to work super fast. – R3qUi3M Feb 03 '22 at 15:24
343

Use set difference

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

Or you might just have x and y be sets so you don't have to do any conversions.

quantumSoup
  • 25,753
  • 9
  • 40
  • 57
43

if duplicate and ordering items are problem :

[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]
nguyên
  • 4,862
  • 5
  • 42
  • 44
  • 3
    This works, though it's `O(m * n)` runtime (and I cringe whenever a listcomp includes side-effects); [you can improve on it using `collections.Counter`](https://stackoverflow.com/a/57827145/364696) to get `O(m + n)` runtime. – ShadowRanger Sep 06 '19 at 18:50
  • I am having a hard time understanding this, can someone explain? – anushka Oct 23 '19 at 07:28
42

That is a "set subtraction" operation. Use the set data structure for that.

In Python 2.7:

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

Output:

>>> print x - y
set([0, 8, 2, 4, 6])
Santa
  • 11,123
  • 8
  • 49
  • 64
  • 1
    list(set([1,2,3,4,5]) - set([1,2,3])) = [4, 5] so that's lists each to set first, then subtract (or one-way diff) and back to list. – gseattle Jun 05 '17 at 08:22
  • 3
    Not good if you like to maintain original item order of the x set. – Zahran Aug 26 '17 at 17:16
25

For many use cases, the answer you want is:

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

This is a hybrid between aaronasterling's answer and quantumSoup's answer.

aaronasterling's version does len(y) item comparisons for each element in x, so it takes quadratic time. quantumSoup's version uses sets, so it does a single constant-time set lookup for each element in x—but, because it converts both x and y into sets, it loses the order of your elements.

By converting only y into a set, and iterating x in order, you get the best of both worlds—linear time, and order preservation.*


However, this still has a problem from quantumSoup's version: It requires your elements to be hashable. That's pretty much built into the nature of sets.** If you're trying to, e.g., subtract a list of dicts from another list of dicts, but the list to subtract is large, what do you do?

If you can decorate your values in some way that they're hashable, that solves the problem. For example, with a flat dictionary whose values are themselves hashable:

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

If your types are a bit more complicated (e.g., often you're dealing with JSON-compatible values, which are hashable, or lists or dicts whose values are recursively the same type), you can still use this solution. But some types just can't be converted into anything hashable.


If your items aren't, and can't be made, hashable, but they are comparable, you can at least get log-linear time (O(N*log M), which is a lot better than the O(N*M) time of the list solution, but not as good as the O(N+M) time of the set solution) by sorting and using 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)]

If your items are neither hashable nor comparable, then you're stuck with the quadratic solution.


* Note that you could also do this by using a pair of OrderedSet objects, for which you can find recipes and third-party modules. But I think this is simpler.

** The reason set lookups are constant time is that all it has to do is hash the value and see if there's an entry for that hash. If it can't hash the value, this won't work.

Community
  • 1
  • 1
abarnert
  • 334,953
  • 41
  • 559
  • 636
14

If the lists allow duplicate elements, you can use Counter from collections:

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

If you need to preserve the order of elements from x:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]
Alain T.
  • 34,859
  • 4
  • 30
  • 47
  • This is good, though it does lose ordering; fixing that [is a bit more complicated](https://stackoverflow.com/a/57827145/364696). – ShadowRanger Sep 06 '19 at 18:47
  • @ShadowRanger, it is indeed. but just a bit. – Alain T. Sep 06 '19 at 19:40
  • Don't mind me, I'm just going to shudder at listcomps with caching and side-effects (although I suppose the combination of the two removes the externally visible side-effects?). :-) – ShadowRanger Sep 06 '19 at 19:41
  • 1
    Also, this code won't work as written; `Counter.subtract` doesn't remove zero valued elements (`-` and `-=` do, but not `subtract`), so you'd never stop removing elements. You'd want to replace `not v in c` with `not c[v]` (which returns zero for non-existent elements, so you can safely test the return for "zeroiness" via `not`). – ShadowRanger Sep 06 '19 at 19:44
  • @ShadowRanger, Good catch! Fixed it now. – Alain T. Sep 06 '19 at 20:22
  • .elements() is unncessary. list((Counter(x) - Counter(y))) works for my case – Dipesh Sunrait Sep 05 '21 at 16:16
  • As long as you don't have duplicate values in x, it does indeed, but in that case, using a set would probably be more efficient: `[vx for ySet in [set(y)] for vx in x if vx not in ySet]` (preserving the order of x), or `list(set(x)-set(y))` if the order doesn't matter – Alain T. Sep 05 '21 at 16:22
10

I think the easiest way to achieve this is by using 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]
Loochie
  • 2,066
  • 10
  • 18
9

Looking up values in sets are faster than looking them up in lists:

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

I believe this will scale slightly better than:

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

Both preserve the order of the lists.

rudolfbyker
  • 1,481
  • 1
  • 15
  • 19
  • Will it cache `set(y)` and not convert `y` to a new set on each loop? Otherwise, you'd need abarnert's answer: `ys = set(y); [i for i in x if i not in ys]`. – Jacktose May 23 '19 at 23:02
  • 3
    Some rough testing suggests that `if i not in set(y)` takes 25% longer than `if i not in y` (where `y` is a list). Pre-converting the set takes 55% less time. Tested with pretty short `x` and `y`, but differences should get more pronounced with length, if anything. – Jacktose May 23 '19 at 23:17
  • 1
    @Jacktose: Yeah, this solution does more work, because it has to iterate and hash *every* element of `y` for *every* element of `x`; unless the equality comparison is really expensive relative to the hash computation, this will always lose to plain `item not in y`. – ShadowRanger Sep 06 '19 at 18:28
  • @ShadowRanger which makes sense. If set conversion was a reliably quicker way to do that check, you'd think the compiler would just always do the check that way. – Jacktose Sep 10 '19 at 15:54
8

The other solutions have one of a few problems:

  1. They don't preserve order, or
  2. They don't remove a precise count of elements, e.g. for x = [1, 2, 2, 2] and y = [2, 2] they convert y to a set, and either remove all matching elements (leaving [1] only) or remove one of each unique element (leaving [1, 2, 2]), when the proper behavior would be to remove 2 twice, leaving [1, 2], or
  3. They do O(m * n) work, where an optimal solution can do O(m + n) work

Alain was on the right track with Counter to solve #2 and #3, but that solution will lose ordering. The solution that preserves order (removing the first n copies of each value for n repetitions in the list of values to remove) is:

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.

Try it online!

To make it remove the last copies of each element, just change the for loop to for val in reversed(x): and add out.reverse() immediately after exiting the for loop.

Constructing the Counter is O(n) in terms of y's length, iterating x is O(n) in terms of x's length, and Counter membership testing and mutation are O(1), while list.append is amortized O(1) (a given append can be O(n), but for many appends, the overall big-O averages O(1) since fewer and fewer of them require a reallocation), so the overall work done is O(m + n).

You can also test for to determine if there were any elements in y that were not removed from x by testing:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts
ShadowRanger
  • 124,179
  • 11
  • 158
  • 228
  • 1
    Note: This *does* require the values to be hashable, but any solution that doesn't require hashable objects either isn't general purpose (e.g. can count `int`s into fixed length array) or has to do more than `O(m + n)` work (e.g. the next best big-O would be to make a sorted `list` of unique value/count pairs, changing `O(1)` `dict` lookups into `O(log n)` binary searches; you'd need unique values with their counts, not just sorted non-unique values, because otherwise you'd be paying `O(n)` costs to remove the elements from the sorted `list`). – ShadowRanger Sep 06 '19 at 18:47
  • I think this is the best answer so far, but for reference purposes I think it would be better if it was refactored into a function, as I presume typing out 5+ lines of code every time one wants to subtract two lists is a bit cumbersome. – Vladimir Vilimaitis May 06 '22 at 14:31
4

You could also try this if the values are unique anyway:

list(set(x) - set(y))
DokwoN
  • 49
  • 2
2

Try this.

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

We can use set methods as well to find the difference between two list

x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
y = [1, 3, 5, 7, 9]
list(set(x).difference(y))
[0, 2, 4, 6, 8]
Simas Joneliunas
  • 2,705
  • 15
  • 25
  • 32
1

The answer provided by @aaronasterling looks good, however, it is not compatible with the default interface of list: x = MyList(1, 2, 3, 4) vs x = MyList([1, 2, 3, 4]). Thus, the below code can be used as a more python-list friendly:

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

Example:

x = MyList([1, 2, 3, 4])
y = MyList([2, 5, 2])
z = x - y
smottt
  • 3,182
  • 11
  • 38
  • 43
0

This example subtracts two lists:

# 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))
0
from collections import Counter

y = Counter(y)
x = Counter(x)

print(list(x-y))
RiveN
  • 2,470
  • 8
  • 7
  • 23
abi
  • 9
  • 3
0
list1 = ['a', 'c', 'a', 'b', 'k'] 
list2 = ['a', 'a', 'a', 'a', 'b', 'c', 'c', 'd', 'e', 'f'] 
for e in list1: 
    try: 
        list2.remove(e) 
    except ValueError: 
        print(f'{e} not in list') 
list2 
# ['a', 'a', 'c', 'd', 'e', 'f']

This will change list2. if you want to protect list2 just copy it and use the copy of list2 in this code.

BARIS KURT
  • 352
  • 3
  • 12