How to compare lists in python in subgroups

Question:

I’m new in python so any help or recomendation is appreciated.

What I’m trying to do is, having two lists (not necessarily inverted).

For instance:

l1 = [1,2,3,4,5]
l2 = [5,4,3,2,1]

Comparing them to return the common values, but not as anyone would normally do, which in this case, the return will be all the elements of the list, because they are the same, just inverted.
What I’m trying to compare is, the same thing but like in stages, or semi portions of the list, and check if there is any coincidence until there, if it is, return that element, if not, keep looking in the next group.

For instance:
the first iteration, would check (having the lists previously defined:

l1 = [1]
l2 = [5]
#is there any coincidence until there? -> false (keep looking)

2nd iteration:

l1 = [1, 2]
l2 = [5, 4]
#is there any coincidence until there? -> false (keep looking)

3rd iteration:

l1 = [1, 2, 3]
l2 = [5, 4, 3]
#is there any coincidence until there? -> true (returns 3, 
#which is the element where the coincidence was found, not necessarily 
#the same index in both lists)

Having in mind that it will compare the last element from the first list with all from the second till that point, which in this case will be just the first from the second list, if no matches, keep trying with the element immediately preceding the last from the first list with all from the second, and so on, returning the first item that matches.

Another example to clarify:

l1 = [1,2,3,4,5]
l2 = [3,4,5,6,7]

And the output will be 3

A tricky one:

l1 = [1,2,3,4]
l2 = [2,1,4,5]

1st iteration

l1 = [1]
l2 = [2]
# No output

2nd iteration

l1 = [1,2]
l2 = [2,1]
# Output will be 2

Since that element was found in the second list too, and the item that I’m checking first is the last of the first list [1,2], and looking if it is also in the sencond list till that point [2,1].

All of this for needing to implementate the bidirectional search, but I’m finding myself currently stuck in this step as I’m not so used to the for loops and list handling yet.

Asked By: Nacho Gomez

||

Answers:

you can compare the elements of the two lists in the same loop:

l1 = [1,2,3,4,5]
l2 = [5,4,3,2,1]
 
for i, j in zip(l1, l2):
  if i == j:
    print('true')
  else:
    print('false')
Answered By: Candido

It looks like you’re really asking: What is (the index of) the first element that l1 and l2 have in common at the same index?

The solution:

next((i, a) for i, (a, b) in enumerate(zip(l1, l2)) if a == b)

How this works:

  • zip(l1, l2) pairs up elements from l1 and l2, generating tuples
  • enumerate() gets those tuples, and keeps track of the index, i.e. (0, (1, 5), (1, (2, 4)), etc.
  • for i, (a, b) in .. generates those pairs of indices and value tuples
  • The if a == b ensures that only those indices and values where the values match are yielded
  • next() gets the next element from an iterable, you’re interested in the first element that matches the condition, so that’s what next() gets you here.

The working example:

l1 = [1, 2, 3, 4, 5]
l2 = [5, 4, 3, 2, 1]

i, v = next((i, a) for i, (a, b) in enumerate(zip(l1, l2)) if a == b)

print(f'index: {i}, value: {v}')  # prints "index: 2, value: 3"

If you’re not interested in the index, but just in the first value they have in common:

l1 = [1, 2, 3, 4, 5]
l2 = [5, 4, 3, 2, 1]

v = next(a for a, b in zip(l1, l2) if a == b)

print(v)  # prints "3"

Edit: you commented and updated the question, and it’s clear you don’t want the first match at the same index between the lists, but rather the first common element in the heads of the lists.

(or, possibly the first element from the second list that is in the first list, which user @AndrejKesely provided an answer for – which you accepted, although it doesn’t appear to answer the problem as described)

Here’s a solution that gets the first match from the first part of each list, which seems to match what you describe as the problem:

l1 = [1, 2, 3, 4, 5]
l2 = [5, 2, 6, 7, 8]

v = next(next(iter(x)) for n in range(max(len(l1), len(l2))) if (x := set(l1[:n+1]) & set(l2[:n+1])))
print(v)  # prints "2"

Note: the solution fails if there is no match at all, with a StopIteration. Using short-circuiting with any() that can be avoided:

x = None if not any((x := set(l1[:n+1]) & set(l2[:n+1])) for n in range(max(len(l1), len(l2)))) else next(iter(x))
print(x)

This solution has x == None if there is no match, and otherwise x will be the first match in the shortest heads of both lists, so:

l1 = [1, 2, 3, 4, 5]
l2 = [5, 2, 6, 7, 8]  # result 2

l1 = [1, 2, 3, 4, 5]
l2 = [5, 6, 7, 8]  # result 5

l1 = [1, 2, 3, 4, 5]
l2 = [6, 7, 8]  # result None

Note that also:

l1 = [1, 2, 3]
l2 = [4, 3, 2]  # result 2, not 3

Both 2 and 3 seem to be valid answers here, it’s not clear from your description why 3 should be favoured over 2?

If you do need that element of the two possible answers that comes first in l2, the solution would be a bit more complicated still, since the sets are unordered by definition, so changing the order of l1 and l2 in the answer won’t matter.

If you care about that order, this works:

x = None if not any(x := ((set(l1[:n//2+1+n%2]) & set(l2[:n//2+1]))) for n in range(max(len(l1), len(l2)) * 2)) else next(iter(x))

This also works for lists with different lengths, unlike the more readable answer by user @BenGrossmann. Note that they have some efficiency in reusing the constructed sets and adding one element at a time, which also allows them to remember the last element added to the set corresponding with the first list, which is why they also correctly favor 3 over 2 in [[1, 2, 3], [4, 3, 2]].

If the last answer is what you need, you should consider amending their answer (for example using zip_longest) to deal correctly with lists of different lengths, since it will be more efficient for longer lists, and is certainly more readable.

Taking the solution from @BenGrossman, but generalising it for any number of lists, with any number of elements, and favouring the ordering you specified:

from itertools import zip_longest

lists = [[1, 2, 3, 4, 5],
         [6, 7, 8, 5, 4]]

sets = [set() for _ in range(len(lists))]
for xs in zip_longest(*lists):
    for x, s in zip(xs, sets):
        s.add(x)
    if i := set.intersection(*sets):
        v = sorted([(lists[0].index(x), x) for x in i])[-1][1]
        break
else:
    v = None

print(v)

This works as described for all the examples, as well as for lists of unequal length, and will favour the elements that are farthest back in the first list (and thus earlier in the others).

Answered By: Grismar

The following can be made more efficient, but does work.

lists = [[1,2,3,4,5], # input to the script
         [5,4,3,2,1]]

sets = [set(), set()]
for a,b in zip(*lists):
    sets[0].add(a)
    sets[1].add(b)
    if sets[0]&sets[1]:
        print("first element in first overlap:")
        print(a)
        break
else:
    print("no overlap")

This results in the output

first element in first overlap:
3

Using lists = [[5,7,6],[7,5,4]] instead results in

first element in first overlap:
7
Answered By: Ben Grossmann
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.