4

次の交差線の例を考えてみましょう。

l1 = ((20,5),(40,20))
l2 = ((20,20),(40,5))
l3 = ((30,30),(30,5)) # vertical line 

交差点のx、yを計算するために、次のコードを開発しました(理論的な詳細を参照)

def gradient(l):
    """Returns gradient 'm' of a line"""
    m = None
    # Ensure that the line is not vertical
    if l[0][0] != l[1][0]:
        m = (1./(l[0][0]-l[1][0]))*(l[0][1] - l[1][1])
        return m

def parallel(l1,l2):
    if gradient(l1) != gradient(l2):
        return False
    return True


def intersect(l):
    """Returns intersect (b) of a line using the equation of
    a line in slope and intercepet form (y = mx+b)"""
    return l[0][1] - (gradient(l)*l[0][0])


def line_intersection(l1,l2):
    """Returns the intersection point (x,y) of two line segments. Returns False
    for parallel lines"""
    # Not parallel
    if not parallel(l1,l2):
        if gradient(l1) is not None and gradient(l2) is not None:
            x = (1./(gradient(l1) - gradient(l2))) * (intersect(l2) - intersect(l1))
            y = (gradient(l1)*x) + intersect(l1)
        else:
            if gradient(l1) is None:
                x = l1[0][0]
                y = (gradient(l2)*x) + intersect(l2)
            elif gradient(l2) is None:
                x = l2[0][0]
                y = (gradient(l1)*x) + intersect(l1)
        return (x,y)
    else:
        return False

セッション例:

>>> line_intersection(l1,l2)
(30.0, 12.5)
>>> line_intersection(l2,l3)
(30, 12.5)

長さが制限されていて、実際には交差しない可能性がある線分の場合に、効率的なスタイルでコードを改善したいと思います。

l1 = ((4,4),(10,10)) 
l2 = ((11,5),(5,11))
l3 = ((11,5),(9,7))

line_intersection(l1,l2) #valid
(8.0, 8.0)
line_intersection(l1,l3) # they don't cross each other
(8.0, 8.0)
line_intersection(l2,l3) #line parallel
False

私のエレガントでない解決策は次のとおりです。

def crosses(l1,l2):
    if not parallel(l1,l2):
        x = line_intersection(l1,l2)[0]
        xranges = [max(min(l1[0][0],l1[1][0]),min(l2[0][0],l2[1][0])),min(max(l1[0][0],l1[1][0]),max(l2[0][0],l2[1][0]))]
        if min(xranges) <= x <= max(xranges):
            return True
        else:
            return False
    else:
        return False


crosses(l1,l2)
True
crosses(l2,l3)
False

Pythonで関数のスタイルを改善できるかどうかを探しています

4

1 に答える 1

20

正しい答えを返すコードはどれも私の本ではかなり素晴らしいです。素晴らしい。

ここにいくつかの提案があります:

def parallel(l1,l2):
    if gradient(l1) != gradient(l2):
        return False
    return True

次のように書くことができます

def parallel(l1,l2):
    return gradient(l1) == gradient(l2)

同様に、

if min(xranges) <= x <= max(xranges):
    return True
else:
    return False

次のように書くことができます

return min(xranges) <= x <= max(xranges)

可能な限り整数のインデックス付け、特に。のようなダブルレベルの整数のインデックス付けは避けてくださいl1[0][0]

単語や変数名は、整数のインデックスよりも読みやすく、理解しやすいです。

整数インデックスを回避する1つの方法は、「タプルアンパック」を使用することです。

(x1, y1), (x2, y2) = l1

そして、にl1[0][0]なりx1ます。gradientこれにより、および関数でのコードの可読性が向上する可能性がありcrossesます。


線が平行な場合は2つあります。線が同一線上にない場合、それらは交差しません。しかし、線が同一線上にある場合、それらはどこでも交差します。

言うのは正確ではないようです

line_intersection(line, line)

False線が同一線上にあるときです。問題の行がまったく同じ行である場合は、さらに間違っているように見えます(そのようなことが可能である場合:))。

None線が同一線上にある場合、および線が平行であるが同一線上にない場合は、任意の交点を返すことをお勧めします。


フロートが等しいかどうかを比較するときに発生する可能性のあるバグがあります。

    In [81]: 1.2 - 1.0 == 0.2
    Out[81]: False

これはPythonのバグではなく、floatの内部表現によって引き起こされる問題であり、任意の言語で実行されるすべての浮動小数点計算に影響します。以下のように、floatの同等性を比較しようとするコードにバグが発生する可能性があります。

def parallel(l1,l2):
    if gradient(l1) == gradient(l2): ...

したがって、フロートの同等性を比較する代わりに、2つのフロートがある程度の許容範囲内で互いに近くにあるかどうかをテストするのが最善の方法です。例えば、

def near(a, b, rtol=1e-5, atol=1e-8):
    # Essentially borrowed from NumPy 
    return abs(a - b) < (atol + rtol * abs(b))

def parallel(l1,l2):
    if near(gradient(l1), gradient(l2)): ...

PEP8スタイルガイドによると

文字「l」(小文字のel)、「O」(大文字のoh)、または「I」(大文字のeye)を単一文字の変数名として使用しないでください。

一部のフォントでは、これらの文字は数字の1および0と区別できません。

l1だから私が提案する代わりにline1


さて、@ georgeが指摘したように、コードが特殊なケースとして垂直線を処理する場所がいくつかあります(if gradient is None。)線のパラメトリック形式を使用すると、すべての線を同じ方法で処理できます。計算が簡単になるため、コードも簡単になります。

線上の2つの点がわかっている場合、(x1, y1)および(x2, y2)、線のパラメトリック形式は次のようになります。

l(t) = (x1, y1)*(1-t) + (x2, y2)*t

ここtで、はスカラーです。変化するにつれてt、あなたはライン上で異なるポイントを獲得します。パラメトリック形式に関するいくつかの関連する事実に注意してください。

  • の場合t = 1、右側の最初の(x2, y2)項が削除されるため、が残ります。

  • の場合t = 0、右側の2番目(x1, y1)*(1-0) = (x1, y1)の項が削除されるため、が残ります。

  • 方程式の右辺は、に線形に依存しtます。t**2に項やその他の非線形依存性はありませんtしたがって、パラメトリック形式は線を表します。


線のパラメトリック形式が強力なのはなぜですか?

  • 線分(x1、y1)から(x2、y2)内のポイントは、 t0から1(両端を含む)の値に対応します。の他のすべての値はt 、線分の外側のポイントに対応します。

  • パラメトリック形式に関する限り、垂直線は特別なものではないことにも注意してください。無限の傾斜を心配する必要はありません。すべての行を同じように扱うことができます


この事実をどのように活用できますか?

パラメトリック形式の2つの線がある場合:

l1(t) = (x1, y1)*(1-t) + (x2, y2)*t

l2(s) = (u1, v1)*(1-s) + (u2, v2)*s

(x1、y1、x2、y2、u1、v1、u2、v2を与えられた定数と考えてください)、線は次の場合に交差します

l1(t) = l2(s)

さて、l1(t)は二次元の点です。l1(t) = l2(s)は2次元の方程式です。に組み込まれている-coordinatexの方程式と-coordinateの方程式があります。したがって、実際には2つの方程式と2つの未知数(および)があります。これらの方程式をとについて解くことができます!(うまくいけば、線が交差しない場合、との解決策はありません。yl1(t) = l2(s)tststs


それでは、数学をやってみましょう:)

l1(t) = (x1, y1) + (x2-x1, y2-y1)*t
l2(s) = (u1, v1) + (u2-u1, v2-v1)*s

l1(t) = l2(s)2つのスカラー方程式を意味します。

x1 + (x2-x1)*t = u1 + (u2-u1)*s
y1 + (y2-y1)*t = v1 + (v2-v1)*s

(x2-x1)*t - (u2-u1)*s = u1-x1
(y2-y1)*t - (v2-v1)*s = v1-y1

そして、それを行列方程式として書き直すことができます。

ここに画像の説明を入力してください

クラメルの公式を使用してts次のように解くことができます。

ここに画像の説明を入力してください

それから

ここに画像の説明を入力してください

ここに画像の説明を入力してください

クラメルの公式は数学的な観点からは有効ですが(コーディングは簡単です)、数値特性が不十分であることに注意してください( GEPPとクラメルの公式も参照)。深刻なアプリケーションの場合は、LU分解またはLAPACK(NumPyから入手可能)を使用します。


したがって、次のようにコーディングできます。

def line_intersection(line1, line2):
    """
    Return the coordinates of a point of intersection given two lines.
    Return None if the lines are parallel, but non-collinear.
    Return an arbitrary point of intersection if the lines are collinear.

    Parameters:
    line1 and line2: lines given by 2 points (a 2-tuple of (x,y)-coords).
    """
    (x1,y1), (x2,y2) = line1
    (u1,v1), (u2,v2) = line2
    (a,b), (c,d) = (x2-x1, u1-u2), (y2-y1, v1-v2)
    e, f = u1-x1, v1-y1
    # Solve ((a,b), (c,d)) * (t,s) = (e,f)
    denom = float(a*d - b*c)
    if near(denom, 0):
        # parallel
        # If collinear, the equation is solvable with t = 0.
        # When t=0, s would have to equal e/b and f/d
        if near(float(e)/b, float(f)/d):
            # collinear
            px = x1
            py = y1
        else:
            return None
    else:
        t = (e*d - b*f)/denom
        # s = (a*f - e*c)/denom
        px = x1 + t*(x2-x1)
        py = y1 + t*(y2-y1)
    return px, py


def crosses(line1, line2):
    """
    Return True if line segment line1 intersects line segment line2 and 
    line1 and line2 are not parallel.
    """
    (x1,y1), (x2,y2) = line1
    (u1,v1), (u2,v2) = line2
    (a,b), (c,d) = (x2-x1, u1-u2), (y2-y1, v1-v2)
    e, f = u1-x1, v1-y1
    denom = float(a*d - b*c)
    if near(denom, 0):
        # parallel
        return False
    else:
        t = (e*d - b*f)/denom
        s = (a*f - e*c)/denom
        # When 0<=t<=1 and 0<=s<=1 the point of intersection occurs within the
        # line segments
        return 0<=t<=1 and 0<=s<=1

def near(a, b, rtol=1e-5, atol=1e-8):
    return abs(a - b) < (atol + rtol * abs(b))

line1 = ((4,4),(10,10)) 
line2 = ((11,5),(5,11))
line3 = ((11,5),(9,7))
line4 = ((4,0),(10,6)) 

assert all(near(a,b) for a,b in zip(line_intersection(line1,line2), (8.0, 8.0)))
assert all(near(a,b) for a,b in zip(line_intersection(line1,line3), (8.0, 8.0)))
assert all(near(a,b) for a,b in zip(line_intersection(line2,line3), (11, 5)))

assert line_intersection(line1, line4) == None # parallel, non-collinear
assert crosses(line1,line2) == True
assert crosses(line2,line3) == False    
于 2013-03-09T04:20:23.543 に答える