Reputation: 113
I am new to Python programming. I want to rewrite the following code as a list comprehension:
lx = [1, 2, 3, 4, 5, 1, 2]
ly = [2, 5, 4]
lz = []
for x in lx:
if x in ly and x not in lz:
lz.append(x)
This will create a new list with common elements of lx
and ly
; but the condition x not in lz
depends on the list that is being built. How can this code be rewritten as a list comprehension?
Upvotes: 2
Views: 337
Reputation: 2400
If you don't want to use set, this can be another approach using list comprehension.
lx = [1, 2, 3, 4, 5, 1, 2]
ly = [2, 5, 4]
lz=[]
[lz.append(x) for x in lx if (x in ly and x not in lz)]
print(lz)
Upvotes: 0
Reputation: 51037
The correct answer here is to use sets, because (1) sets naturally have distinct elements, and (2) sets are more efficient than lists for membership tests. So the simple solution is list(set(lx) & set(ly))
.
However, sets do not preserve the order that elements are inserted in, so in case the order is important, here's a solution which preserves the order from lx
. (If you want the order from ly
, simply swap the roles of the two lists.)
def ordered_intersection(lx, ly):
ly_set = set(ly)
return [ly_set.remove(x) or x for x in lx if x in ly_set]
Example:
>>> ordered_intersection(lx, ly)
[2, 4, 5]
>>> ordered_intersection(ly, lx)
[2, 5, 4]
It works because ly_set.remove(x)
always returns None
, which is falsy, so ly_set.remove(x) or x
always has the value of x
.
The reason you cannot do this with a simpler list comprehension like lz = [... if x in lz]
is because the whole list comprehension will be evaluated before the resulting list is assigned to the variable lz
; so the x in lz
test will give a NameError
because there is no such variable yet.
That said, it is possible to rewrite your code to directly use a generator expression (which is somewhat like a list comprehension) instead of a for
loop; but it is bad code and you shouldn't do this:
def ordered_intersection_bad_dont_do_this(lx, ly):
lz = []
lz.extend(x for x in lx if x in ly and x not in lz)
return lz
This is not just bad because of repeatedly testing membership of lists; it is worse, because it depends on an unspecified behaviour of the extend
method. In particular, it adds each element one by one rather than exhausting the iterator first and then adding them all at once. The docs don't say that this is guaranteed to happen, so this bad solution won't necessarily work in other versions of Python.
Upvotes: 0
Reputation: 10482
You cannot do it that way in a list comprehension as you cannot compare against the list lz
that does not yet exist - assuming you are trying to avoid duplicates in the resulting list as in your example.
Instead, you can use the python set
which will enforce only a single instance of each value:
lz = set(x for x in lx if x in ly)
And if what you are really after is a set intersection (elements in common):
lz = set(lx) & set(ly)
UPDATE: As pointed out by @Błotosmętek in the comments - using the set will not retain the order of the elements as the set is, by definition, unordered. If the order of the elements is significant a different strategy will be necessary.
Upvotes: 3