5

I have two lists of the form:

lst1 = [(1.2, 4), (5, 8), (19, 21), (24.5, 26)]
lst2 = [(1, 3), (6.55, 14.871), (22, 23)]

The output I am looking to get is:

output = [(1.2, 3), (6.55, 8)]

Basically, I want the intersections between the ranges defined by the tuples across the two lists.

You can assume-

  1. the indices to be ordered within a given list. For example, in lst2:

    1 < 6.55 < 22
    
  2. the ranges to be valid (within a tuple, the startVal<=endEndVal). In lst2:

    1 < 3  and 6.55 < 14.871 and 22 < 23
    

What is an efficient way to achieve this?

4 Answers 4

2

I think the best way to do this is with a list comprehension, given that both lists are the same length.

in two lists for readability:

# get the min max ranges
a = [(max(i[0], j[0]),min(i[1],j[1])) for i,j in zip(lst1, lst2)]
# check that min is smaller than max
a = [(i,j) for (i,j) in a if i < j]

or in one list:

a = [(i,j) for (i,j) in [(max(i[0], j[0]),min(i[1],j[1])) for i,j in zip(lst1, lst2)] if i < j]
Sign up to request clarification or add additional context in comments.

8 Comments

For the input lst1 = [(1.2, 4), (5, 8), (19, 21), (24.5, 26)] and lst2 = [(1, 3), (6.55, 14.871), (22, 25)], the expected output is [(1.2, 3), (6.55, 8), (24.5, 25)]. But the output of your program is [(1.2, 3), (6.55, 8)]
No, in the question it is specified: "The output I am looking to get is: output = [(1.2, 3), (6.55, 8)]". The last range is not valid and therefore should not be in the output.
Yes, but lst1[3] and lst2[2] intersect, and their intersection is (24.5, 25)
@Linda the problem with your code is that it only compares i-th element of tuples (since you use zip). So if lst1 had (0,0) as first element instead, it will compare it with (1,3) and skip comparison between (1.2,4) with (1,3) and skip all the other points also - giving you an empty list as the solution, which is not correct
@RickyKim I see, I think I misunderstood the question. I thought the aim was a pair-wise comparison.
|
2

Using itertools and heapq.merge:

lst1 = [(1.2, 4), (5, 8), (19, 21), (24.5, 26)]
lst2 = [(1, 3), (6.55, 14.871), (22, 25)]

from heapq import merge
from itertools import tee, groupby

m1, m2 = tee(merge(lst1, lst2, key=lambda k: k[0]))
next(m2, None)

out = []
for v, g in groupby(zip(m1, m2), lambda k: k[0][1] < k[1][0]):
    if not v:
        l = [*g][0]
        out.append((max(i[0] for i in l), min(i[1] for i in l)))

print(out)

Prints:

[(1.2, 3), (6.55, 8), (24.5, 25)]

3 Comments

This does not find intersection by comparing the two lists but does it by merging which I'm not sure if that's what OP wants. For example, input of lst1 = [(1.2, 4),(1, 3), (5, 8), (19, 21), (24.5, 26)] and lst2 = [(6.55, 14.871), (22, 25)] still outputs the same result, but you might want [(6.55, 8), (24.5, 25)] as output instead.
sry I meant lst1 = [(1, 3), (1.2, 4), (5, 8), (19, 21), (24.5, 26)]
@RickyKim Yeah, in this case my script returns [(1.2, 3), (6.55, 8), (24.5, 25)], but it depends on op's input. Maybe ops lists don't have overlapping intervals.
1

A solution using iterators. I use a while loop which stays active until both iterators running on the lists are exausthed.

lst1 = [(1.2, 4), (5, 8), (19, 21), (24.5, 26)]
lst2 = [(1, 3), (6.55, 14.871), (22, 23)]

itr1 = iter(lst1)
itr2 = iter(lst2)
on1 = True
on2 = True

rng1 = next(itr1)
rng2 = next(itr2)
res = []

while on1 or on2:
    ll = max(rng1[0], rng2[0])
    rr = min(rng1[1], rng2[1])
    if ll < rr:
        res.append((ll, rr))

    if on1 and on2:
        if rng1[0] < rng2[0]:
            try:
                rng1 = next(itr1)
            except StopIteration:
                on1 = False
        else:
            try:
                rng2 = next(itr2)
            except StopIteration:
                on2 = False
    elif on1:
        try:
            rng1 = next(itr1)
        except StopIteration:
            on1 = False
    elif on2:
        try:
            rng2 = next(itr2)
        except StopIteration:
            on2 = False

if len(res) > 1 and res[-1] == res[-2]:
    res.pop(-1)
print(res)

Using your sample input, this prints: [(1.2, 3), (6.55, 8)]

Comments

0
  1. make a simple function to get an intersection range for two range.
    def get_intersect(r1, r2):
            left = max(r1[0], r2[0])
            right = min(r1[1], r2[1])
            if left>right:
                return None
            return (left,right)
  1. and get all intersections though the double loop
    for i1 in lst1:
        for i2 in lst2:
            ia = get_intersect(i1, i2)
            if ia!=None:
                print(ia)

It would be faster, if you add some conditions in the loop.

1 Comment

"It would be faster, if you add some conditions in the loop." - then you should add them?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.