x = [8,2,3,4,5]
y = [6,3,7,2,1]
How to find out the first common element in two lists (in this case, \"2\") in a concise and elegant way? Any list
One liner, using next
to take the first item from a generator:
x = [8,2,3,4,5]
y = [6,3,7,2,1]
first = next((a for a in x if a in y), None)
Or more efficiently since set.__contains__
is faster than list.__contains__
:
set_y = set(y)
first = next((a for a in x if a in set_y), None)
Or more efficiently but still in one line (don't do this):
first = next((lambda set_y: a for a in x if a in set_y)(set(y)), None)
Using a for
loops with in
will result in a O(N^2)
complexity, but you can sort y
here and use binary search to improve the time complexity to O(NlogN)
.
def binary_search(lis,num):
low=0
high=len(lis)-1
ret=-1 #return -1 if item is not found
while low<=high:
mid=(low+high)//2
if num<lis[mid]:
high=mid-1
elif num>lis[mid]:
low=mid+1
else:
ret=mid
break
return ret
x = [8,2,3,4,5]
y = [6,3,7,2,1]
y.sort()
for z in x:
ind=binary_search(y,z)
if ind!=-1
print z
break
output:
2
Using the bisect
module to perform the same thing as above:
import bisect
x = [8,2,3,4,5]
y = [6,3,7,2,1]
y.sort()
for z in x:
ind=bisect.bisect(y,z)-1 #or use `ind=min(bisect.bisect_left(y, z), len(y) - 1)`
if ind!=-1 and y[ind] ==z:
print z #prints 2
break
I assume you want to teach this person Python, not just programming. Therefore I do not hesitate to use zip
instead of ugly loop variables; it's a very useful part of Python and not hard to explain.
def first_common(x, y):
common = set(x) & set(y)
for current_x, current_y in zip(x, y):
if current_x in common:
return current_x
elif current_y in common:
return current_y
print first_common([8,2,3,4,5], [6,3,7,2,1])
If you really don't want to use zip
, here's how to do it without:
def first_common2(x, y):
common = set(x) & set(y)
for i in xrange(min(len(x), len(y))):
if x[i] in common:
return x[i]
elif y[i] in common:
return y[i]
And for those interested, this is how it extends to any number of sequences:
def first_common3(*seqs):
common = set.intersection(*[set(seq) for seq in seqs])
for current_elements in zip(*seqs):
for element in current_elements:
if element in common:
return element
Finally, please note that, in contrast to some other solutions, this works as well if the first common element appears first in the second list.
I just noticed your update, which makes for an even simpler solution:
def first_common4(x, y):
ys = set(y) # We don't want this to be recreated for each element in x
for element in x:
if element in ys:
return element
The above is arguably more readable than the generator expression.
Too bad there is no built-in ordered set. It would have made for a more elegant solution.
Just for fun (probably not efficient), another version using itertools
:
from itertools import dropwhile, product
from operator import __ne__
def accept_pair(f):
"Make a version of f that takes a pair instead of 2 arguments."
def accepting_pair(pair):
return f(*pair)
return accepting_pair
def get_first_common(x, y):
try:
# I think this *_ unpacking syntax works only in Python 3
((first_common, _), *_) = dropwhile(
accept_pair(__ne__),
product(x, y))
except ValueError:
return None
return first_common
x = [8, 2, 3, 4, 5]
y = [6, 3, 7, 2, 1]
print(get_first_common(x, y)) # 2
y = [6, 7, 1]
print(get_first_common(x, y)) # None
It is simpler, but not as fun, to use lambda pair: pair[0] != pair[1]
instead of accept_pair(__ne__)
.