Kendiliğinden Kesişen Poligonun Alanı


32

2B uzayda bir köşe listesiyle tanımlanan potansiyel olarak kendinden kesişen bir çokgen düşünün. Örneğin

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

Böyle bir poligonun alanını tanımlamanın birkaç yolu vardır, ancak en ilginç olanı tek kuraldır. Düzlemdeki herhangi bir noktayı alarak, noktadan sonsuza kadar (herhangi bir yönde) bir çizgi çizin. Bu çizgi çokgeni çok sayıda geçerse, nokta çokgenin alanının bir parçasıdır, çokgeni eşit sayıda geçerse, nokta çokgenin bir parçası değildir. Yukarıdaki örnek poligon için, burada hem anahat hem de tek alan vardır.

taslakalan

Poligon genel olarak ortogonal olmayacaktır. Alanı saymayı kolaylaştırmak için sadece bu kadar basit bir örnek seçtim.

Bu örneğin bir alandır 17(değil 24veya 33diğer tanımlar ya da alan verim olabilir gibi).

Bu tanım uyarınca çokgenin alanının sarma düzeninden bağımsız olduğunu unutmayın.

Meydan okuma

Bir çokgeni tanımlayan tamsayı koordinatlarına sahip bir köşe listesi göz önüne alındığında, alanını tek kural uyarınca belirleyin.

STDIN veya en yakın alternatif, komut satırı argümanı veya işlev argümanı yoluyla giriş alarak bir işlev veya program yazabilir ve sonucu döndürür veya STDOUT veya en yakın alternatife yazdırabilirsiniz.

Ön işleme alınmadığı sürece herhangi bir uygun liste veya dize biçiminde girdi alabilirsiniz.

Sonuç, kayan nokta sayısı, 6 anlamlı (ondalık) basamağa kadar doğru ya da kayan nokta gösterimi 6 önemli basamak için doğru olan rasyonel bir sonuç olmalıdır. (Eğer rasyonel sonuçlar verirseniz, büyük olasılıkla kesin olacaklardır, ancak referans için kesin sonuçlara sahip olmadığım için buna ihtiyacım yok.)

Makul bir masaüstü makinesinde, aşağıdaki test durumlarının her birini 10 saniye içinde çözebilmelisiniz. (Bu kuralda bazı boşluklar vardır, bu yüzden en iyi kararınızı kullanın. Dizüstü bilgisayarımda 20 saniye sürerse, size şüphenin avantajını vereceğim, eğer bir dakika sürerse, olmaz.) Çok cömert olmalı, ancak poligonu yeterince ince bir ızgara üzerinde saygısızlaştırdığınız ve saydığınız ya da Monte Carlo gibi olasılıklı yaklaşımları kullandığınız yaklaşımları dışlamak gerekiyor. İyi bir sporcu olun ve bu yaklaşımları optimize etmeye çalışmayın ki zaten zaman sınırını karşılayabilirsiniz. ;)

Doğrudan çokgenlerle ilgili var olan işlevleri kullanmamalısınız.

Bu kod golf, yani en kısa gönderme (bayt cinsinden) kazanır.

Varsayımlar

  • Tüm koordinatlar aralıktaki tamsayılardır 0 ≤ x ≤ 100,0 ≤ y ≤ 100 .
  • En az 3ve en fazla 50köşeler olacak.
  • Tekrarlanan herhangi bir köşe olmayacak. Her iki köşe de başka bir kenarda yer almaz. (Yine de listede ortak noktalar olabilir .)

Test Kılıfları

{{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
Özellikle, sınırlayıcıları listeyi geçerli bir PostScript Kullanıcı Yolu yapacak şekilde değiştirmek istiyorum, böylece her şeyi bir işleçle ayrıştırabilirim upath. (Aslında, ayırıcılar arasındaki son derece basit bir 1: 1 dönüşümdür. }, {Yeni olur linetove x ile y arasındaki virgül silinir ve açılış ve kapanış parantezleri statik bir başlık ve altbilgiyle değiştirilir ...)
AJMansfield

1
@AJMansfield Genellikle uygun, yerli liste temsillerini kullanarak, fakat kullanmaktan çekinmezler upathve linetoaslında girişini önişleme konum gibi sesler. Yani, koordinatların listesini değil, gerçek bir poligonu alıyorsunuz.
Martin Ender

1
@MattNoonan Oh, bu iyi bir nokta. Evet, bunu varsayabilirsin.
Martin Ender

2
@Ray Yön geçişlerin sayısını etkileyebilir, ancak pariteyi koruyarak ancak 2 kat artacaktır. Bir referans bulmaya çalışacağım. Başlangıç ​​için, SVG aynı tanımı kullanır.
Martin Ender

1
Mathematica 12.0 yeni sahiptir yerleşik bunun için işlevi: CrossingPolygon.
alephalpha

Yanıtlar:


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&

İlk önce çokgene kesişme noktalarını ekleyin, sonra bazı kenarları ters çevirin, ardından alanı basit bir çokgen gibi hesaplanabilir.

görüntü tanımını buraya girin

Örnek:

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

Ne yazık ki bu mantığın tüm durumlar için çalışacağından emin değilim. Deneyebilir {1,2},{4,4},{4,2},{2,4},{2,1},{5,3}misin 3.433333333333309 ile çıkmalısın. Benzer bir mantık kullanmaya baktım.
MickyT

@MickyT Evet, çalışıyor. Döndü 103/30ve sayısal değer 3.43333.
alephalpha

Bunun için üzgünüm. İyi çözüm
MickyT

44

Python 2, 323 319 bayt

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

Aşağıdaki biçimde, STDIN aracılığıyla karmaşık sayılar gibi bir köşe listesi alır.

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

, ve sonucu STDOUT'a yazar.

Dize değiştirme ve bazı boşluklardan sonra aynı kod:

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

açıklama

Giriş çokgenin iki tarafının (köşeler dahil) her bir kesişme noktası için, bu noktadan dikey bir çizgi geçirin.

Şekil 1

(Aslında, golf oynadığından dolayı, program birkaç satır daha geçer; en azından bu satırları geçtikten sonra bu hiç önemli değil.) İki ardışık satır arasındaki poligon gövdesi dikey yamuklardan oluşuyor ( ve özel durumlar olarak üçgenler ve çizgi parçaları. Durum bu olmalı, çünkü eğer bu şekillerden herhangi biri iki taban arasında ek bir tepe noktası varsa, bu nokta boyunca söz konusu iki satır arasında başka bir dikey çizgi olacaktır. Tüm bu yamukların alanlarının toplamı çokgenin alanıdır.

İşte bu yamukları nasıl bulacağımız: Her ardışık dikey çizgi çifti için, poligonun her bir tarafının (iki taraf arasında bulunamayan) bu iki çizgi arasında (düzgün şekilde) kalan kısımlarını buluyoruz. Yukarıdaki şekilde, bunlar iki kırmızı dikey çizgi göz önüne alındığında altı kırmızı segmenttir. Bu bölümlerin birbirleriyle düzgün şekilde kesişmediğini unutmayın (yani, yalnızca son noktalarında buluşabilir, tamamen çakışabilir veya hiç kesişmeyebilir, çünkü bir kez daha, eğer kesişmişlerse doğru şekilde kesiştikleri bir başka dikey çizgi olacaktır;) ve bu yüzden onları aşağıdan yukarıya doğru sıralamaktan bahsetmek mantıklı. Tek kuralına göre, ilk bölümü geçtikten sonra çokgenin içindeyiz; ikinciyi geçtikten sonra dışarı çıkarız; üçüncü, yine; dördüncü, dışarı; ve bunun gibi...

Genel olarak, bu ise O ( n, 3 günlük , n ) algoritması.


4
Bu harika! Bunun için sana güvenebileceğimi biliyordum. ;) ( Bu soruyu Stack Overflow'ta cevaplamak isteyebilirsiniz .)
Martin Ender

@ MartinBüttner Gelmeye devam et :)
Ell

7
Harika iş ve harika bir açıklama
MickyT

1
Bu etkileyici bir cevap. Algoritmayı kendiniz mi geliştirdiniz, yoksa bu sorunla ilgili çalışmalar var mı? Eğer mevcut bir çalışma varsa, nerede bulabileceğime dair bir işaretçi isterim. Bununla nasıl baş edeceğimi bilemedim.
Mantık Şövalye

5
@CarpetPython Kendim geliştirdim, ancak daha önce yapılmadıysa çok şaşırırdım.
Ell

9

Haskell, 549

Bunu yeterince golf oynayabilir gibi görünmüyor, ancak konsept diğer iki cevaptan farklıydı, ben de yine de paylaşacağımı düşündüm. Alanı hesaplamak için O (N ^ 2) rasyonel işlemlerini gerçekleştirir.

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

Örnek:

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

Buradaki fikir, poligonu her geçişte yeniden kablolamak, böylece kesişen kenarları olmayan bir çokgen birliği elde etmek. Daha sonra Gauss'un ayakkabı bağı formülünü kullanarak ( http://en.wikipedia.org/wiki/Shoelace_formula ) her poligonun (imzalanmış) alanını hesaplayabiliriz . Tek kural, bir geçit dönüştürüldüğünde, yeni poligonun alanının eski poligona göre negatif olarak sayılmasını gerektirir.

Örneğin, orijinal sorudaki çokgeni göz önünde bulundurun. Sol üstteki geçiş sadece bir noktada toplanan iki yola dönüştürülür; iki yolun her ikisi de saat yönünde yönlendirilir, bu nedenle, iç yolun dış yola göre -1 ile ağırlıklandırıldığını bildirmedikçe, her birinin alanları pozitif olacaktır. Bu, alfa yolunun tersine çevrilmesine eşdeğerdir.

Orijinal örnekten türetilen çokgenler

Başka bir örnek olarak, MickyT'nin yorumundaki poligonu düşünün:

MickyT'nin yorumundan elde edilen çokgenler

Burada, çokgenlerin bazıları saat yönünde, bazıları saat yönünün tersine yönlendirilir. Üzerine geçme işareti kuralı, saat yönünde yönlendirilen bölgelerin -1 değerinde fazladan bir faktör toplamasını sağlar ve bu da bölgeye pozitif miktarda katkıda bulunmalarını sağlar.

İşte program nasıl çalışıyor:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.