正しい答えを返すコードはどれも私の本ではかなり素晴らしいです。素晴らしい。
ここにいくつかの提案があります:
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
。したがって、パラメトリック形式は線を表します。
線のパラメトリック形式が強力なのはなぜですか?
この事実をどのように活用できますか?
パラメトリック形式の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つの未知数(および)があります。これらの方程式をとについて解くことができます!(うまくいけば、線が交差しない場合、との解決策はありません。y
l1(t) = l2(s)
t
s
t
s
t
s
それでは、数学をやってみましょう:)
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
そして、それを行列方程式として書き直すことができます。
クラメルの公式を使用してt
、s
次のように解くことができます。
それから
クラメルの公式は数学的な観点からは有効ですが(コーディングは簡単です)、数値特性が不十分であることに注意してください( 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