自相交多边形的面积


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个有效数字(十进制),或者是一个有理结果,其浮点表示形式精确到6个有效数字。(如果您得出合理的结果,它们可能会是准确的,但是我不能要求这样做,因为我没有确切的结果可供参考。)

您必须能够在合理的台式机上在10秒内解决以下每个测试用例。(此规则有一定的余地,因此请使用您的最佳判断。如果在我的笔记本电脑上花费20秒,我会带给您疑问的好处,如果花费一分钟,我不会。)我认为这个限制应该非常大方,但是应该排除那些仅在足够细的网格上离散多边形并计数的方法,或者使用诸如蒙特卡洛这样的概率方法。做一个好运动员,不要试图优化这些方法,以使您无论如何都可以满足时间限制。;)

您不得使用任何与多边形直接相关的现有功能。

这是代码高尔夫球,因此最短的提交(以字节为单位)获胜。

假设条件

  • 所有的坐标都在范围内的整数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用户路径的方式替换定界符,因此我可以使用一个upath运算符来解析整个内容。(实际上,分隔符之间是一个非常简单的1:1转换。}, {只是变为lineto,并且删除了x和y之间的逗号,并且将开括号和闭括号替换为静态的页眉和页脚...)
AJMansfield

1
@AJMansfield我通常不介意使用方便的本机列表表示形式,但是使用upathlineto听起来好像您实际上是在预处理输入。也就是说,您不是要获取坐标列表,而是要获取实际的多边形。
马丁·恩德

1
@MattNoonan哦,这很重要。是的,您可能会认为。
马丁·恩德

2
@Ray虽然方向可能会影响交叉点的数量,但只会增加或减少2,保持奇偶性。我将尝试查找参考。首先,SVG使用相同的定义。
马丁·恩德

1
Mathematica 12.0为此提供了一个新的内置功能:CrossingPolygon
alephalpha

Answers:


14

数学,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 2015年

@MickyT是的,它有效。返回103/30,数值为3.43333
alephalpha

对于那个很抱歉。好的解决方案
MickyT 2015年

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]
)

说明

对于输入多边形两侧(包括顶点)的每个相交点,使一条垂直线穿过该点。

图1

(实际上,由于打高尔夫球,该程序又传递了几行;只要我们至少传递了这些行,就没有关系。)任意两行之间的多边形的主体由垂直梯形组成(和三角形以及线段,以此类推)。情况必须如此,因为如果这些形状中的任何一个在两个底边之间都有一个附加顶点,那么在所讨论的两条线之间将有一条穿过该点的垂直线。所有这些梯形的面积之和就是多边形的面积。

查找梯形的方法如下:对于每对连续的垂直线,我们找到(正确)位于这两条线之间的多边形每一边的线段(某些边可能不存在)。在上图中,考虑两条红色垂直线时,它们是六个红色部分。请注意,这些线段没有正确地相交(即,它们可能仅在它们的端点相遇,完全重合或根本不相交,因为再一次,如果它们正确地相交,它们之间将存在另一条垂直线;)因此,有必要讨论从上到下对其进行排序的做法。根据奇数规则,一旦我们越过第一个线段,便位于多边形内部;一旦我们越过第二个,我们就出去了;第三个,再次 第四,出去;等等...

总体而言,这是On 3 log n)算法。


4
这太棒了!我知道我可以依靠你。;)(您可能想在Stack Overflow上回答这个问题。)
Martin Ender 2015年

@MartinBüttner陪他们来了:)
埃尔

7
伟大的工作和出色的解释
MickyT 2015年

1
这是一个令人印象深刻的答案。您是自己开发算法还是针对此问题进行了工作?如果有现成的作品,希望能找到可以找到它的指针。我不知道如何解决这个问题。
逻辑骑士

5
@CarpetPython我自己开发的,但是如果以前没有做过,我会感到非常惊讶。
2015年

9

哈斯克尔(549)

看起来我不能打得足够远,但是这个概念与其他两个答案不同,因此我认为我还是会分享它。它执行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

想法是在每个交叉点重新连接多边形,从而形成没有相交边的多边形并集。然后,我们可以使用高斯的鞋带公式(http://en.wikipedia.org/wiki/Shoelace_formula)计算每个多边形的(带符号)面积。奇偶规则要求转换交叉时,新多边形的面积相对于旧多边形为负数。

例如,考虑原始问题中的多边形。左上角的交叉点将转换为两条仅在一个点相遇的路径。这两个路径均沿顺时针方向定位,因此除非我们声明内部路径相对于外部路径的权重为-1,否则每个区域的面积均为正。这等效于alphaalpha的路径反转。

源自原始示例的多边形

再举一个例子,考虑一下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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.