问题
I have a fairly basic question here. I want to find if two lines on a 1-D plane intersect. I know of two simple ways to solve this, but I wanted to know if Python has a more elegant way to solve this?
Ex:
x = [1, 10] # 1 = begin, 10 = end
y = [15, 20]
z = [5, 12]
#Method 1: Works. Is quick. Lots of typing.
def is_intersect_1(a, b):
bool_check = False
if a[0] <= b[0] <= a[1] or \
a[0] <= b[1] <= a[1] or \
b[0] <= a[0] <= b[1] or \
b[0] <= a[1] <= b[1]:
bool_check = True
return bool_check
is_intersect_1(x,y) # False
is_intersect_1(x,z) # True
#Method 2: Quicker to write. Simpler to read. Uses more memory and is slower.
def is_intersect_2(a, b):
bool_check = False
if set(range(a[0], a[1]+1)).intersection(set(range(b[0], b[1])):
bool_check = True
return bool_check
is_intersect_2(x,y) # False
is_intersect_2(x,z) # True
回答1:
Although not Python-centric per se, here's an elegant way of solving the problem.
The central idea is that if the two intervals aren't completely disjoint, then they must intersect, so all you have to do is check for that condition.
class Interval(object):
""" Representation of a closed interval from 'a' to 'b'. """
def __init__(self, a, b):
self.a, self.b = (a, b) if a < b else (b, a) # make a min & b max
def intersects(self, other):
return self.b >= other.a and self.a <= other.b
def __str__(self):
return '[{0.a:>{w}}, {0.b:>{w}}]'.format(self, w=2)
testcases = ((Interval(1, 5), Interval(11, 14)), # xxxxx
# xxxxx
(Interval(1, 9), Interval( 7, 15)), # xxxxxxxxx
# xxxxxxxxx
(Interval(5, 9), Interval( 1, 15)), # xxxxx
# xxxxxxxxxxxxxxx
(Interval(0, 15), Interval( 5, 9))) # xxxxxxxxxxxxxxx
# xxxxx
for I1, I2 in testcases:
print('{} {:^7} intersect with {}'.format(
I1, "does" if I1.intersects(I2) else "doesn't", I2))
Output:
[ 1, 5] doesn't intersect with [11, 14]
[ 1, 9] does intersect with [ 7, 15]
[ 5, 9] does intersect with [ 1, 15]
[ 0, 15] does intersect with [ 5, 9]
回答2:
I haven't attempted to measure the performance, but I think this is clearer and likely to be faster - it trades the "or", potentially, of two additional "ternary comparisons" for two comparisons (min and max):
>>> x = [1,10]
>>> y = [20,15]
>>> z = [5,12]
>>> def intersects (a, b):
... c = [min (b), max(b)]
... return (c[0] < a[0] < c[1]) or (c[0] < a[1] < c[1])
...
>>> intersects (x, y)
False
>>> intersects (x, z)
True
a intersects b if either end is within b. In the function, c just assures that we know which end of b is which. It would work equally well swapping the treatment of b for a.
Measuring the performance would require running a suite of all possible permutations of the specification of the second line, and the choice of intersection of either end, or neither.
Edited from here. I cranked up an ipython notebook to test the performance. The first method in the initial post is in fact faster based on a sample of intervals generated at random in the range -100 to 100. Mine made the comparison in 827 microseconds per loop through the 1000 comparisons versus 527.
Unfortunately, the testing showed that the first method in the post fails.
[59, -35] [89, -9] False
f = intersects2
for x in w:
print (v, x, f(x, v))
[59, -35] [89, -9] False
[59, -35] [76, 89] False
回答3:
This question turned out to be more interesting than I expected. To me, the original solution just looked too complicated. I don't trust complicated, so I tried my own hand at a solution, which was simpler, and easily provably correct. I should have left the comparison at less than or equal, instead of just less than. After playing around to compare the speeds of the two, I accidentally, on a test of only two conditions, found that the original solution was flawed. I also found out that, though my solution - with the correction just mentioned - was slower than the proposed solution.
The proposed solution fails 5 of 24 possible cases. I leave it to the author to correct his function. I have not even tried to determine where his error occurred. But I offer the following function, to be used for testing.
There are tools that test code coverage. This problem and the solutions are interesting in that the coverage to test fully the solution need to be at less than the line level in granularity.
In the following code, pass the proposed function to test_intersection. It will throw an exception if even one of the 24 possible cases fails. My solution, and the modification proposed using a tuple internally, pass all 24. The original solution fails 5 of the cases. After posting this, I realize that there are some additional cases that could be added. ([3,4], [3,7]), ([3,7], [3,7]), ([4,7], [3,7]) as well as the variants in which the "intervals" of the lines are backward.
def test_intersection (f):
assert not f ([1,2], [3,7])
assert f ([1,3], [3,7])
assert f ([1,4], [3,7])
assert f ([4,5], [3,7])
assert f ([4,8], [3,7])
assert f ([7,9], [3,7])
assert not f ([8,9], [3,7])
assert not f ([2,1], [3,7])
assert f ([3,1], [3,7])
assert f ([4,1], [3,7])
assert f ([5,4], [3,7])
assert f ([8,4], [3,7])
assert f ([9,7], [3,7])
assert not f ([9,8], [3,7])
assert not f ([1,2], [7,3])
assert f ([1,3], [7,3])
assert f ([1,4], [7,3])
assert f ([4,5], [7,3])
assert f ([4,8], [7,3])
assert f ([7,9], [7,3])
assert not f ([8,9], [7,3])
assert not f ([2,1], [7,3])
assert f ([3,1], [7,3])
assert f ([4,1], [7,3])
assert f ([5,4], [7,3])
assert f ([9,7], [7,3])
assert not f ([9,8], [7,3])
来源:https://stackoverflow.com/questions/29043456/intersect-of-two-lines-by-begin-and-end-points