자체 교차 다각형 영역


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 ≤ 100, 0 ≤ 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연산자로 구문 분석 할 수 있습니다 . (실제로 seperator 사이의 매우 간단한 1 : 1 변환입니다. }, {그냥 lineto이고 x와 y 사이의 쉼표가 제거되고 여는 중괄호는 정적 머리글과 바닥
글로 바뀝니다

1
@AJMansfield 필자는 일반적으로 편리한 기본 목록 표현을 사용하지 않지만 실제로 입력을 사전 처리하는 것처럼 upathlineto소리를 사용 합니다. 즉, 좌표 목록이 아닌 실제 다각형을 사용하고 있습니다.
마틴 엔더

1
@MattNoonan 오, 좋은 지적입니다. 그렇습니다.
마틴 엔더

2
@Ray 방향이 교차 횟수에 영향을 줄 수 있지만 패리티를 유지하면서 2만큼만 증가 또는 감소합니다. 참조를 찾으려고 노력하겠습니다. 시작을 위해 SVG 는 동일한 정의를 사용합니다.
마틴 엔더

1
Mathematica 12.0에는이를위한 새로운 내장 기능이 있습니다 : CrossingPolygon.
alephalpha

답변:


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

@MickyT 예, 작동합니다. 반환 103/30되고 숫자 값은 3.43333입니다.
alephalpha

미안합니다. 좋은 해결책
MickyT

44

파이썬 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

(실제로 골프로 인해 프로그램은 몇 줄을 더 전달합니다. 최소한이 줄을 지나는 한 실제로는 중요하지 않습니다.) 연속 된 두 줄 사이의 다각형 몸체는 세로 사다리꼴로 구성됩니다 ( 특수한 경우 삼각형 및 선 세그먼트). 이러한 모양 중 하나라도 두 개의베이스 사이에 추가 꼭지점이 있으면 해당 점을 통과하는 두 개의 세로선 사이에 다른 수직선이있을 것입니다. 이러한 사다리꼴의 면적의 합은 다각형의 면적입니다.

이 사다리꼴을 찾는 방법은 다음과 같습니다. 연속 된 수직선 쌍마다,이 두 선 사이에 (적절하게) 존재하는 다각형의 각 변의 선분을 찾습니다 (일부 변에는 존재하지 않을 수 있음). 위의 그림에서 두 개의 빨간색 세로선을 고려할 때 6 개의 빨간색 세그먼트입니다. 이 세그먼트는 서로 올바르게 교차하지 않습니다 (즉, 끝점에서만 만나거나 완전히 일치하거나 전혀 교차하지 않을 수 있음). 다시 올바르게 교차하면 사이에 다른 수직선이 생길 수 있습니다. 따라서 위에서 아래로 주문하는 것에 대해 이야기하는 것이 합리적입니다. 짝수 홀수 규칙에 따르면, 첫 번째 세그먼트를 지나면 다각형 안에 있습니다. 우리가 두 번째 것을 건너면, 우리는 나갑니다. 세 번째는 다시; 넷째, 밖으로; 등등...

전반적으로 이것은 O ( n 3 log n ) 알고리즘입니다.


4
훌륭합니다! 나는 이것을 당신에게 의지 할 수 있다는 것을 알았습니다. ;) ( Stack Overflow 에서이 질문 에 대답하고 싶을 수도 있습니다 .)
Martin Ender

@ MartinBüttner Keep 'em coming :)
Ell

7
훌륭한 작업과 훌륭한 설명
MickyT

1
이것은 인상적인 답변입니다. 알고리즘을 직접 개발했거나이 문제에 대한 기존 작업이 있습니까? 기존 작품이 있다면 어디서 찾을 수 있는지 알려주십시오. 나는 이것을 해결하는 방법에 대해 전혀 몰랐다.
논리 기사

5
@CarpetPython 직접 개발했지만 이전에 해본 적이 없다면 매우 놀랐습니다.
Ell

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

교차점마다 다각형을 다시 연결하여 교차 모서리가없는 다각형을 결합하는 것이 좋습니다. 그런 다음 Gauss의 신발 끈 공식 ( http://en.wikipedia.org/wiki/Shoelace_formula )을 사용하여 각 다각형의 (서명 된) 영역을 계산할 수 있습니다 . 짝수 홀수 규칙은 교차가 변환 될 때 새 다각형의 영역이 이전 다각형에 대해 음수로 계산되도록 요구합니다.

예를 들어, 원래 질문의 다각형을 고려하십시오. 왼쪽 상단의 교차점은 한 지점에서만 만나는 두 개의 경로로 변환됩니다. 두 경로는 모두 시계 방향으로되어 있으므로 내부 경로가 외부 경로에 대해 -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.