自己交差ポリゴンの面積


32

2D空間内の頂点のリストによって定義される、潜在的に自己交差するポリゴンを考えてみましょう。例えば

{{0, 0}, {5, 0}, {5, 4}, {1, 4}, {1, 2}, {3, 2}, {3, 3}, {2, 3}, {2, 1}, {4, 1}, {4, 5}, {0, 5}}

このようなポリゴンの領域を定義する方法はいくつかありますが、最も興味深いのは偶奇規則です。平面内の任意の点を取り、その点から無限に(任意の方向に)線を引きます。その線が多角形を奇数回交差する場合、ポイントは多角形の領域の一部であり、多角形を偶数回交差する場合、ポイントは多角形の一部ではありません。上記のポリゴンの例では、輪郭と偶奇領域の両方があります。

アウトラインエリア

通常、ポリゴンは直交しません。面積を数えやすくするために、このような単純な例を選択しただけです。

この例の領域は17(他の定義または領域がもたらす可能性のあるものではない、24または33そうではない)です。

この定義では、多角形の面積はその巻き順とは無関係です。

チャレンジ

多角形を定義する整数座標を持つ頂点のリストが与えられたら、偶奇規則の下で面積を決定します。

関数またはプログラムを作成し、STDINまたは最も近い代替、コマンドライン引数または関数引数を介して入力を取得し、結果を返すか、STDOUTまたは最も近い代替に出力できます。

前処理されていない限り、任意の便利なリスト形式または文字列形式で入力を取得できます。

結果は、有効桁数が6桁(10進数)の浮動小数点数か、浮動小数点表現が有効桁数が6桁の有理数結果でなければなりません。(合理的な結果を生成する場合、それらは正確になる可能性が高いですが、参照用の正確な結果がないため、これを要求することはできません。)

適切なデスクトップマシンで、10秒以内に以下の各テストケースを解決できる必要があります。(このルールには多少の余裕がありますので、最善の判断をしてください。私のラップトップで20秒かかる場合、疑いの恩恵があります。1分かかる場合、私はしません)。非常に寛大なはずですが、十分に細かいグリッドでポリゴンを離散化してカウントするアプローチ、またはモンテカルロのような確率的アプローチを使用するアプローチを除外することになっています。優れたスポーツマンであり、いずれにしても制限時間を満たすことができるようにこれらのアプローチを最適化しようとしないでください。;)

ポリゴンに直接関連する既存の関数を使用しないでください。

これはコードゴルフであるため、最短の提出(バイト単位)が優先されます。

仮定

  • すべての座標は、範囲内の整数です0 ≤ x ≤ 1000 ≤ y ≤ 100
  • 少なくとも3、多くても50頂点があります。
  • 頂点が繰り返されることはありません。また、頂点が別のエッジにあることもありません。(ただし、リストには同一直線上の点がある場合があります。)

テストケース

{{0, 0}, {5, 0}, {5, 4}, {1, 4}, {1, 2}, {3, 2}, {3, 3}, {2, 3}, {2, 1}, {4, 1}, {4, 5}, {0, 5}}
17.0000

{{22, 87}, {6, 3}, {98, 77}, {20, 56}, {96, 52}, {79, 34}, {46, 78}, {52, 73}, {81, 85}, {90, 43}}
2788.39

{{90, 43}, {81, 85}, {52, 73}, {46, 78}, {79, 34}, {96, 52}, {20, 56}, {98, 77}, {6, 3}, {22, 87}}
2788.39

{{70, 33}, {53, 89}, {76, 35}, {14, 56}, {14, 47}, {59, 49}, {12, 32}, {22, 66}, {85, 2}, {2, 81},
 {61, 39}, {1, 49}, {91, 62}, {67, 7}, {19, 55}, {47, 44}, {8, 24}, {46, 18}, {63, 64}, {23, 30}}
2037.98

{{42, 65}, {14, 59}, {97, 10}, {13, 1}, {2, 8}, {88, 80}, {24, 36}, {95, 94}, {18, 9}, {66, 64},
 {91, 5}, {99, 25}, {6, 66}, {48, 55}, {83, 54}, {15, 65}, {10, 60}, {35, 86}, {44, 19}, {48, 43},
 {47, 86}, {29, 5}, {15, 45}, {75, 41}, {9, 9}, {23, 100}, {22, 82}, {34, 21}, {7, 34}, {54, 83}}
3382.46

{{68, 35}, {43, 63}, {66, 98}, {60, 56}, {57, 44}, {90, 52}, {36, 26}, {23, 55}, {66, 1}, {25, 6},
 {84, 65}, {38, 16}, {47, 31}, {44, 90}, {2, 30}, {87, 40}, {19, 51}, {75, 5}, {31, 94}, {85, 56},
 {95, 81}, {79, 80}, {82, 45}, {95, 10}, {27, 15}, {18, 70}, {24, 6}, {12, 73}, {10, 31}, {4, 29},
 {79, 93}, {45, 85}, {12, 10}, {89, 70}, {46, 5}, {56, 67}, {58, 59}, {92, 19}, {83, 49}, {22,77}}
3337.62

{{15, 22}, {71, 65}, {12, 35}, {30, 92}, {12, 92}, {97, 31}, {4, 32}, {39, 43}, {11, 40}, 
 {20, 15}, {71, 100}, {84, 76}, {51, 98}, {35, 94}, {46, 54}, {89, 49}, {28, 35}, {65, 42}, 
 {31, 41}, {48, 34}, {57, 46}, {14, 20}, {45, 28}, {82, 65}, {88, 78}, {55, 30}, {30, 27}, 
 {26, 47}, {51, 93}, {9, 95}, {56, 82}, {86, 56}, {46, 28}, {62, 70}, {98, 10}, {3, 39}, 
 {11, 34}, {17, 64}, {36, 42}, {52, 100}, {38, 11}, {83, 14}, {5, 17}, {72, 70}, {3, 97}, 
 {8, 94}, {64, 60}, {47, 25}, {99, 26}, {99, 69}}
3514.46

1
具体的には、リストを有効なPostScriptユーザーパスにする方法で区切り文字を置き換えたいので、1つのupath演算子で全体を解析できます。(実際には、セパレーター間の非常に単純な1:1変換です。}, {単にになりlineto、xとyの間のコンマが削除され、開始括弧と終了括弧が静的ヘッダーとフッターに置き換えられます...)
AJMansfield

1
@AJMansfield通常、便利でネイティブなリスト表現を使用しても構いませんが、実際に使用するupathと、lineto実際に入力を前処理しているように聞こえます。つまり、座標のリストを取得するのではなく、実際のポリゴンを取得します。
マーティンエンダー

1
@MattNoonanああ、それは良い点です。はい、あなたはそれを仮定するかもしれません。
マーティンエンダー

2
@Ray方向は交差の数に影響を与える可能性がありますが、パリティを維持しながら2だけ増加または減少します。参照を見つけようとします。最初に、SVGは同じ定義を使用します。
マーティンエンダー

1
Mathematica 12.0には、このための新しい組み込み関数がありますCrossingPolygon
alephalpha

回答:


14

Mathematica、247 225 222

p=Partition[#,2,1,1]&;{a_,b_}~r~{c_,d_}=Det/@{{a-c,c-d},{a,c}-b}/Det@{a-b,c-d};f=Abs@Tr@MapIndexed[Det@#(-1)^Tr@#2&,p[Join@@MapThread[{1-#,#}&/@#.#2&,{Sort/@Cases[{s_,t_}/;0<=s<=1&&0<=t<=1:>s]/@Outer[r,#,#,1],#}]&@p@#]]/2&

最初に交差点をポリゴンに追加し、次にエッジの一部を反転させてから、単純なポリゴンのように面積を計算できます。

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

例:

In[2]:= f[{{15, 22}, {71, 65}, {12, 35}, {30, 92}, {12, 92}, {97, 31}, {4, 32}, {39, 43}, {11, 40}, 
 {20, 15}, {71, 100}, {84, 76}, {51, 98}, {35, 94}, {46, 54}, {89, 49}, {28, 35}, {65, 42}, 
 {31, 41}, {48, 34}, {57, 46}, {14, 20}, {45, 28}, {82, 65}, {88, 78}, {55, 30}, {30, 27}, 
 {26, 47}, {51, 93}, {9, 95}, {56, 82}, {86, 56}, {46, 28}, {62, 70}, {98, 10}, {3, 39}, 
 {11, 34}, {17, 64}, {36, 42}, {52, 100}, {38, 11}, {83, 14}, {5, 17}, {72, 70}, {3, 97}, 
 {8, 94}, {64, 60}, {47, 25}, {99, 26}, {99, 69}}]

Out[2]= 3387239559852305316061173112486233884246606945138074528363622677708164\
 6419838924305735780894917246019722157041758816629529815853144003636562\
 9161985438389053702901286180223793349646170997160308182712593965484705\
 3835036745220226127640955614326918918917441670126958689133216326862597\
 0109115619/\
 9638019709367685232385259132839493819254557312303005906194701440047547\
 1858644412915045826470099500628074171987058850811809594585138874868123\
 9385516082170539979030155851141050766098510400285425157652696115518756\
 3100504682294718279622934291498595327654955812053471272558217892957057\
 556160

In[3]:= N[%] (*The numerical value of the last output*)

Out[3]= 3514.46

残念ながら、このロジックがすべての状況で機能するかどうかはわかりません。試せます{1,2},{4,4},{4,2},{2,4},{2,1},{5,3}か?3.433333333333309を使用してください。同様のロジックの使用を検討しました。
MickyT

@MickyTはい、動作します。戻り103/30、数値は3.43333です。
-alephalpha

ごめんなさい 良い解決策
MickyT

44

Python 2、 323 319バイト

exec u"def I(s,a,b=1j):c,d=s;d-=c;c-=a;e=(d*bX;return e*(0<=(b*cX*e<=e*e)and[a+(d*cX*b/e]or[]\nE=lambda p:zip(p,p[1:]+p);S=sorted;P=E(input());print sum((t-b)*(r-l)/2Fl,r@E(S(i.realFa,b@PFe@PFi@I(e,a,b-a)))[:-1]Fb,t@E(S(((i+j)XFe@PFi@I(e,l)Fj@I(e,r)))[::2])".translate({70:u" for ",64:u" in ",88:u".conjugate()).imag"})

次の形式で、STDINを介して頂点のリストを複素数として取得します

[  X + Yj,  X + Yj,  ...  ]

、結果をSTDOUTに書き込みます。

文字列の置換といくつかのスペースの後の同じコード:

def I(s, a, b = 1j):
    c, d = s; d -= c; c -= a;
    e = (d*b.conjugate()).imag;
    return e * (0 <= (b*c.conjugate()).imag * e <= e*e) and \
           [a + (d*c.conjugate()).imag * b/e] or []

E = lambda p: zip(p, p[1:] + p);
S = sorted;

P = E(input());

print sum(
    (t - b) * (r - l) / 2

    for l, r in E(S(
        i.real for a, b in P for e in P for i in I(e, a, b - a)
    ))[:-1]

    for b, t in E(S(
        ((i + j).conjugate()).imag for e in P for i in I(e, l) for j in I(e, r)
    ))[::2]
)

説明

入力ポリゴン(頂点を含む)の2つの辺の交差点ごとに、その点に垂直線を渡します。

図1

(実際、ゴルフのために、プログラムはさらにいくつかの行を渡します。少なくともこれらの行を渡す限り、それは実際には問題ではありません。)2つの連続する行の間のポリゴンの本体は、垂直台形(およびそれらの特殊なケースとしての三角形、および線分)。これらの形状のいずれかが2つのベースの間に追加の頂点を持っている場合、問題の2つのラインの間に、そのポイントを通る別の垂直ラインがあるため、そうでなければなりません。このようなすべての台形の面積の合計は、多角形の面積です。

これらの台形を見つける方法は次のとおりです:連続する垂直線の各ペアについて、これらの2つの線の間に(適切に)存在するポリゴンの各辺のセグメントを見つけます(一部の辺には存在しない場合があります)。上の図では、2つの赤い垂直線を考慮すると、これらは6つの赤いセグメントです。これらのセグメントは相互に適切に交差しないことに注意してください(つまり、それらが適切に交差すると別の垂直線があるため、完全に一致するかまったく交差しないので、終点でのみ交わることがあります)。ですから、上から下への順序付けについて話すのは理にかなっています。偶奇規則によれば、最初のセグメントを横断すると、ポリゴンの内側にいます。2番目のものを通過すると、外に出ます。3番目の、再び。4番目、アウト。等々...

全体的に、これはOn 3 log n)アルゴリズムです。


4
これは素晴らしいです!私はこれをあなたに頼ることができることを知っていました。;)(スタックオーバーフローでこの質問に答えることができます。)
マーティンエンダー

@MartinBüttnerKeep 'em coming :)
Ell

7
素晴らしい仕事と素晴らしい説明
-MickyT

1
これは印象的な答えです。アルゴリズムを自分で開発しましたか、またはこの問題に関する既存の作業はありますか?既存の作品がある場合、それを見つけることができる場所へのポインタをいただければ幸いです。私はこれにどう取り組むかわからなかった。
ロジックナイト

5
@CarpetPython私は自分で開発しましたが、これまでに行ったことがないのであれば、非常に驚​​かされるでしょう。
エル

9

ハスケル、549

私はこれを十分に下にゴルフできるようには見えませんが、概念は他の2つの答えとは異なっていたので、とにかくそれを共有すると思いました。O(N ^ 2)有理数演算を実行して面積を計算します。

import Data.List
_%0=2;x%y=x/y
h=sort
z f w@(x:y)=zipWith f(y++[x])w
a=(%2).sum.z(#);(a,b)#(c,d)=b*c-a*d
(r,p)?(s,q)=[(0,p)|p==q]++[(t,v t p r)|u t,u$f r]where f x=(d q p#x)%(r#s);t=f s;u x=x^2<x
v t(x,y)(a,b)=(x+t*a,y+t*b);d=v(-1)
s x=zip(z d x)x
i y=h.(=<<y).(?)=<<y
[]!x=[x];x!_=x
e n(a@(x,p):y)|x>0=(n!y,a):(e(n!y)$tail$dropWhile((/=p).snd)y)|0<1=(n,a):e n y
c[p]k=w x[]where((_,q):x)=e[]p;w((n,y):z)b|q==y=(k,map snd(q:b)):c n(-k)|0<1=w z(y:b);c[]_=[]
b(s,p)=s*a p
u(_,x)(_,y)=h x==h y
f p=abs$sum$map b$nubBy u$take(length p^2)$c[cycle$i$s p]1

例:

λ> f test''
33872395598523053160611731124862338842466069451380745283636226777081646419838924305735780894917246019722157041758816629529815853144003636562916198543838905370290128618022379334964617099716030818271259396548470538350367452202261276409556143269189189174416701269586891332163268625970109115619 % 9638019709367685232385259132839493819254557312303005906194701440047547185864441291504582647009950062807417198705885081180959458513887486812393855160821705399790301558511410507660985104002854251576526961155187563100504682294718279622934291498595327654955812053471272558217892957057556160
λ> fromRational (f test'')
3514.4559380388832

アイデアは、交差するたびにポリゴンを再配線し、エッジが交差しないポリゴンを結合することです。次に、Gaussの靴ひも式(http://en.wikipedia.org/wiki/Shoelace_formula)を使用して、各ポリゴンの(符号付き)面積を計算できます。偶奇規則では、交差点が変換されると、新しいポリゴンの面積は古いポリゴンに対して負の数としてカウントされることが要求されます。

たとえば、元の質問のポリゴンを考えてみましょう。左上の交差点は、1点でのみ交わる2つのパスに変換されます。2つのパスは両方とも時計回りに方向付けられているため、内側のパスが外側のパスに対して-1で重み付けされていると宣言しない限り、それぞれの面積は正になります。これは、アルファアルファのパス反転と同等です。

元の例から派生したポリゴン

別の例として、MickyTのコメントから多角形を考えてみましょう。

MickyTのコメントから派生したポリゴン

ここでは、一部のポリゴンは時計回りと反時計回りに向けられています。サインフリップオンクロッシングルールは、時計回りの領域が-1の余分な要素を拾い上げ、それらが領域にプラスの量を提供するようにします。

プログラムの仕組みは次のとおりです。

import Data.List  -- for sort and nubBy

-- Rational division, with the unusual convention that x/0 = 2
_%0=2;x%y=x/y

-- Golf
h=sort

-- Define a "cyclic zipWith" operation. Given a list [a,b,c,...z] and a binary
-- operation (@), z (@) [a,b,c,..z] computes the list [b@a, c@b, ..., z@y, a@z]
z f w@(x:y)=zipWith f(y++[x])w

-- The shoelace formula for the signed area of a polygon
a=(%2).sum.z(#)

-- The "cross-product" of two 2d vectors, resulting in a scalar.
(a,b)#(c,d)=b*c-a*d

-- Determine if the line segment from p to p+r intersects the segment from
-- q to q+s.  Evaluates to the singleton list [(t,x)] where p + tr = x is the
-- point of intersection, or the empty list if there is no intersection. 
(r,p)?(s,q)=[(0,p)|p==q]++[(t,v t p r)|u t,u$f r]where f x=(d q p#x)%(r#s);t=f s;u x=x^2<x

-- v computes an affine combination of two vectors; d computes the difference
-- of two vectors.
v t(x,y)(a,b)=(x+t*a,y+t*b);d=v(-1)

-- If x is a list of points describing a polygon, s x will be the list of
-- (displacement, point) pairs describing the edges.
s x=zip(z d x)x

-- Given a list of (displacement, point) pairs describing a polygon's edges,
-- create a new polygon which also has a vertex at every point of intersection.
-- Mercilessly golfed.
i y=h.(=<<y).(?)=<<y


-- Extract a simple polygon; when an intersection point is reached, fast-forward
-- through the polygon until we return to the same point, then continue.  This
-- implements the edge rewiring operation. Also keep track of the first
-- intersection point we saw, so that we can process that polygon next and with
-- opposite sign.
[]!x=[x];x!_=x
e n(a@(x,p):y)|x>0=(n!y,a):(e(n!y)$tail$dropWhile((/=p).snd)y)|0<1=(n,a):e n y

-- Traverse the polygon from some arbitrary starting point, using e to extract
-- simple polygons marked with +/-1 weights.
c[p]k=w x[]where((_,q):x)=e[]p;w((n,y):z)b|q==y=(k,map snd(q:b)):c n(-k)|0<1=w z(y:b);c[]_=[]

-- If the original polygon had N vertices, there could (very conservatively)
-- be up to N^2 points of intersection.  So extract N^2 polygons using c,
-- throwing away duplicates, and add up the weighted areas of each polygon.
b(s,p)=s*a p
u(_,x)(_,y)=h x==h y
f p=abs$sum$map b$nubBy u$take(length p^2)$c[cycle$i$s p]1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.