Diện tích của đa giác tự giao


32

Xem xét một đa giác có khả năng tự giao nhau, được xác định bởi một danh sách các đỉnh trong không gian 2D. Ví dụ

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

Có một số cách để xác định diện tích của một đa giác như vậy, nhưng cách thú vị nhất là quy tắc chẵn. Lấy bất kỳ điểm nào trong mặt phẳng, vẽ một đường thẳng từ điểm ra đến vô cực (theo bất kỳ hướng nào). Nếu đường thẳng đó vượt qua đa giác một số lần lẻ, thì điểm đó là một phần của khu vực của đa giác, nếu nó vượt qua đa giác một số lần chẵn thì điểm đó không phải là một phần của đa giác. Đối với đa giác ví dụ ở trên, đây là cả phác thảo cũng như khu vực chẵn của nó:

Đề cươngKhu vực

Đa giác sẽ không nói chung là trực giao. Tôi chỉ chọn một ví dụ đơn giản như vậy để dễ đếm khu vực hơn.

Khu vực của ví dụ này là 17(không 24hoặc 33như các định nghĩa hoặc khu vực khác có thể mang lại).

Lưu ý rằng theo định nghĩa này, diện tích của đa giác không phụ thuộc vào thứ tự quanh co của nó.

Các thách thức

Đưa ra một danh sách các đỉnh có tọa độ nguyên xác định một đa giác, xác định diện tích của nó theo quy tắc chẵn.

Bạn có thể viết một hàm hoặc chương trình, lấy đầu vào qua STDIN hoặc thay thế gần nhất, đối số dòng lệnh hoặc đối số hàm và trả về kết quả hoặc in nó sang STDOUT hoặc thay thế gần nhất.

Bạn có thể lấy đầu vào ở bất kỳ định dạng danh sách hoặc chuỗi tiện lợi nào, miễn là nó chưa được xử lý trước.

Kết quả phải là số dấu phẩy động, chính xác đến 6 chữ số có nghĩa (thập phân) hoặc kết quả hợp lý có biểu diễn dấu phẩy động chính xác đến 6 chữ số có nghĩa. (Nếu bạn tạo ra kết quả hợp lý, họ có thể sẽ chính xác, nhưng tôi không thể yêu cầu điều này, vì tôi không có kết quả chính xác để tham khảo.)

Bạn phải có thể giải quyết từng trường hợp thử nghiệm bên dưới trong vòng 10 giây trên máy tính để bàn hợp lý. (Có một chút chậm trễ trong quy tắc này, vì vậy hãy sử dụng phán đoán tốt nhất của bạn. Nếu mất 20 giây trên máy tính xách tay của tôi, tôi sẽ cho bạn lợi ích của sự nghi ngờ, nếu mất một phút, tôi sẽ không.) Tôi nghĩ giới hạn này nên rất hào phóng nhưng nên loại trừ các cách tiếp cận trong đó bạn chỉ cần loại bỏ đa giác trên một lưới và số đếm đủ tốt, hoặc sử dụng các phương pháp xác suất như Monte Carlo. Hãy là một người chơi thể thao giỏi và đừng cố gắng tối ưu hóa các phương pháp này để bạn có thể đáp ứng giới hạn thời gian. ;)

Bạn không được sử dụng bất kỳ chức năng hiện có liên quan trực tiếp đến đa giác.

Đây là mã golf, vì vậy bài nộp ngắn nhất (tính bằng byte) sẽ thắng.

Giả định

  • Tất cả các tọa độ là số nguyên trong phạm vi 0 ≤ x ≤ 100, 0 ≤ y ≤ 100.
  • Sẽ có ít nhất 3và nhiều nhất là 50các đỉnh.
  • Sẽ không có bất kỳ đỉnh lặp đi lặp lại. Không có bất kỳ đỉnh sẽ nằm trên một cạnh khác. (Tuy nhiên, có thể có các điểm cộng tuyến trong danh sách.)

Các trường hợp thử nghiệm

{{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
Cụ thể, tôi muốn thay thế các dấu phân cách theo cách làm cho danh sách trở thành Đường dẫn người dùng PostScript hợp lệ, để tôi có thể phân tích toàn bộ nội dung bằng một upathtoán tử. (Đây thực sự là một chuyển đổi 1: 1 cực kỳ đơn giản giữa các bộ tách biệt. }, {Chỉ cần trở thành linetovà dấu phẩy giữa x và y được loại bỏ và dấu ngoặc mở và đóng được thay thế bằng một tiêu đề và chân trang tĩnh ...)
AJMansfield

1
@AJMansfield Tôi thường không phiền khi sử dụng các biểu diễn danh sách gốc thuận tiện, nhưng sử dụng upathlinetoâm thanh như bạn thực sự đang xử lý đầu vào. Tức là bạn không lấy danh sách tọa độ mà là đa giác thực tế.
Martin Ender

1
@MattNoonan Ồ, đó là một điểm tốt. Có bạn có thể cho rằng.
Martin Ender

2
@Ray Mặc dù hướng có thể ảnh hưởng đến số lượng giao cắt, nhưng nó sẽ chỉ tăng hoặc giảm 2 lần, duy trì tính chẵn lẻ. Tôi sẽ cố gắng tìm một tài liệu tham khảo. Để bắt đầu, SVG sử dụng định nghĩa tương tự.
Martin Ender

1
Mathematica 12.0 có chức năng tích hợp mới cho việc này : CrossingPolygon.
alephalpha

Câu trả lời:


14

Toán học, 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&

Đầu tiên thêm các điểm giao nhau vào đa giác, sau đó đảo ngược một số cạnh, sau đó diện tích của nó có thể được tính giống như một đa giác đơn giản.

nhập mô tả hình ảnh ở đây

Thí dụ:

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

Thật không may, tôi không chắc logic này sẽ hoạt động trong mọi tình huống. Bạn có thể thử {1,2},{4,4},{4,2},{2,4},{2,1},{5,3}không Bạn nên đi ra với 3.433333333333309. Tôi nhìn vào bằng cách sử dụng một logic tương tự.
MickyT

@MickyT Vâng, nó hoạt động. Nó trả về 103/30, và giá trị số là 3.43333.
alephalpha

Xin lỗi vì điều đó. Giải pháp tốt
MickyT

44

Con trăn 2 323 319 byte

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"})

Lấy danh sách các đỉnh qua STDIN dưới dạng số phức, dưới dạng sau

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

và viết kết quả vào STDOUT.

Cùng mã sau khi thay thế chuỗi và một số khoảng cách:

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

Giải trình

Đối với mỗi điểm giao nhau của hai cạnh của đa giác đầu vào (bao gồm cả các đỉnh), hãy vượt qua một đường thẳng đứng mặc dù điểm đó.

Hình 1

(Trên thực tế, do chơi golf, chương trình vượt qua một vài dòng nữa; nó không thực sự quan trọng, miễn là chúng ta vượt qua ít nhất các dòng này.) Phần thân của đa giác giữa hai dòng liên tiếp bao gồm các hình thang thẳng đứng ( và hình tam giác, và các đoạn đường, như trường hợp đặc biệt của những cái đó). Nó phải là trường hợp, vì nếu bất kỳ hình nào trong số các hình này có một đỉnh bổ sung giữa hai cơ sở, sẽ có một đường thẳng đứng khác đi qua điểm đó, giữa hai đường trong câu hỏi. Tổng diện tích của tất cả các hình thang như vậy là diện tích của đa giác.

Dưới đây là cách chúng tôi tìm thấy các hình thang này: Đối với mỗi cặp đường thẳng đứng liên tiếp, chúng tôi tìm thấy các đoạn của mỗi bên của đa giác (đúng) nằm giữa hai đường này (có thể không tồn tại cho một số cạnh). Trong hình minh họa trên, đây là sáu đoạn màu đỏ, khi xem xét hai đường thẳng đứng màu đỏ. Lưu ý rằng các phân đoạn này không giao nhau đúng cách (nghĩa là chúng chỉ có thể gặp nhau ở điểm cuối, hoàn toàn trùng khớp hoặc không giao nhau, vì, một lần nữa, nếu chúng giao nhau đúng cách sẽ có một đường thẳng đứng khác ở giữa;) và vì vậy thật hợp lý khi nói về việc sắp xếp chúng từ trên xuống dưới, mà chúng ta làm. Theo quy tắc chẵn lẻ, một khi chúng ta vượt qua đoạn đầu tiên, chúng ta sẽ ở trong đa giác; một khi chúng ta vượt qua cái thứ hai, chúng ta ra ngoài; cái thứ ba, một lần nữa; thứ tư, ra ngoài; và v.v.

Nhìn chung, đây là một thuật toán O ( n 3 log n ).


4
Thật là tuyệt vời! Tôi biết tôi có thể tin tưởng vào bạn cho điều này. ;) (Bạn có thể muốn trả lời câu hỏi này trên Stack Overflow.)
Martin Ender

@ MartinBüttner Hãy tiếp tục đến :)
Ell

7
Công việc tuyệt vời và một lời giải thích tuyệt vời
MickyT 11/03/2015

1
Đây là một câu trả lời ấn tượng. Bạn đã tự phát triển thuật toán hay đã có công việc về vấn đề này chưa? Nếu có công việc hiện tại, tôi sẽ đánh giá cao một con trỏ đến nơi tôi có thể tìm thấy nó. Tôi không có ý tưởng về cách giải quyết điều này.
Hiệp sĩ logic

5
@CarpetPython Tôi đã tự mình phát triển nó, nhưng tôi rất ngạc nhiên nếu nó chưa được thực hiện trước đó.
Ell

9

Haskell, 549

Có vẻ như tôi không thể đánh cái này đủ xa, nhưng khái niệm này khác với hai câu trả lời khác nên tôi nghĩ rằng dù sao tôi cũng chia sẻ nó. Nó thực hiện các phép toán hợp lý O (N ^ 2) để tính diện tích.

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

Thí dụ:

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

Ý tưởng là để nối lại đa giác ở mỗi lần giao nhau, dẫn đến sự kết hợp các đa giác không có các cạnh giao nhau. Sau đó, chúng ta có thể tính diện tích (đã ký) của từng đa giác bằng công thức dây giày của Gauss ( http://en.wikipedia.org/wiki/Shoelace_formula ). Quy tắc chẵn lẻ yêu cầu khi chuyển đổi chéo, diện tích của đa giác mới được tính tương đối âm so với đa giác cũ.

Ví dụ, hãy xem xét đa giác trong câu hỏi ban đầu. Giao cắt ở phía trên bên trái được chuyển đổi thành hai đường chỉ gặp nhau tại một điểm; cả hai đường dẫn đều được định hướng theo chiều kim đồng hồ, vì vậy các khu vực của mỗi đường dẫn sẽ dương trừ khi chúng ta tuyên bố rằng đường dẫn bên trong có trọng số -1 so với đường dẫn bên ngoài. Điều này tương đương với đảo ngược đường dẫn của alphaalpha.

Đa giác có nguồn gốc từ ví dụ ban đầu

Một ví dụ khác, hãy xem xét đa giác từ nhận xét của MickyT:

Đa giác có nguồn gốc từ nhận xét của MickyT

Ở đây, một số đa giác được định hướng theo chiều kim đồng hồ và một số ngược chiều kim đồng hồ. Quy tắc vượt qua dấu hiệu lật đảm bảo rằng các khu vực theo chiều kim đồng hồ lấy thêm hệ số -1, khiến chúng đóng góp một số tiền tích cực cho khu vực.

Đây là cách chương trình hoạt động:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.