Площа багатоніжжя, що перетинається


32

Розглянемо потенційно самопересічний багатокутник, визначений списком вершин у двовимірному просторі. Напр

{{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 значущих цифр. (Якщо ви даєте раціональні результати, вони, ймовірно, будуть точними, але я цього не можу вимагати, оскільки я не маю точних результатів для довідки.)

Ви повинні мати можливість вирішити кожен із наведених нижче тестових випадків на розумній настільній машині. (У цьому правилі є деяка свобода, тому використовуйте найкраще рішення. Якщо на моєму ноутбуці потрібно 20 секунд, я дам вам користь від сумнівів, якщо це займе хвилину, я не стану.) Я думаю, що це обмеження Потрібно бути дуже щедрим, але передбачається виключити підходи, коли ви просто дискредитуєте полігон на достатньо тонкій сітці і рахуєте, або використовуєте ймовірнісні підходи, як Монте-Карло. Будь хорошим спортсменом і не намагайся оптимізувати ці підходи, щоб у будь-якому випадку можна було дотримати обмеження. ;)

Ви не повинні використовувати жодні існуючі функції, пов'язані безпосередньо з багатокутниками.

Це код гольфу, тому виграє найкоротше подання (у байтах).

Припущення

  • Всі координати є цілими числами в діапазоні 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оператором. (Це насправді надзвичайно просте перетворення між сеператорами. }, {Просто стає lineto, і кома між x і y видаляється, а дужки відкривання та закриття замінюються статичним заголовком і колонтитулом ...)
AJMansfield

1
@AJMansfield Я зазвичай не проти використовувати зручні представлення списку, але використовую upathі linetoзвучить так, що ви насправді попередньо обробляєте дані. Тобто ви не приймаєте список координат, а власне багатокутник.
Мартін Ендер

1
@MattNoonan О, це хороший момент. Так, ви можете припустити, що.
Мартін Ендер

2
@Ray Хоча напрямок може впливати на кількість перетину, він лише колись збільшуватиметься чи зменшуватиметься на 2, зберігаючи паритет. Спробую знайти посилання. Для початку SVG використовує те саме визначення.
Мартін Ендер

1
Mathematica 12,0 має новий вбудований в функції для цього: CrossingPolygon.
алефальфа

Відповіді:


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.
алефальфа

Вибач за це. Гарне рішення
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

(Насправді, завдяки гольфу, програма проходить ще кілька ліній; це насправді не має значення, якщо ми проходимо хоча б ці лінії.) Тіло багатокутника між будь-якими двома послідовними лініями складається з вертикальних трапецій ( і трикутники, і відрізки ліній, як особливі випадки таких). Це має бути так, бо якби будь-яка з цих фігур мала додаткову вершину між двома основами, через цю точку між двома відповідними лініями була б ще одна вертикальна лінія. Сума площ усіх таких трапецій - це площа багатокутника.

Ось як ми знаходимо ці трапеції: Для кожної пари послідовних вертикальних ліній ми знаходимо відрізки кожної сторони багатокутника, які (належним чином) лежать між цими двома лініями (які можуть не існувати для деяких сторін). На наведеній вище ілюстрації це шість червоних сегментів при розгляді двох червоних вертикальних ліній. Зауважте, що ці відрізки не перетинаються належним чином (тобто вони можуть зустрічатися лише в їх кінцевих точках, повністю збігатися або взагалі не перетинатися, оскільки, знову ж таки, якщо вони належним чином перетиналися, між ними буде ще одна вертикальна лінія;) і тому є сенс поговорити про те, щоб замовити їх зверху вниз, що ми робимо. Згідно з непарним правилом, коли ми переходимо перший відрізок, ми знаходимося всередині полігону; як тільки ми перетнемо другий, ми вийшли; третій, знову ж таки; четвертий, вийшов; і так далі...

Загалом це алгоритм O ( n 3 log n ).


4
Це геніально! Я знав, що можу розраховувати на тебе за це. ;) (Можливо, ви захочете відповісти на це запитання на темі Stack Overflow.)
Martin Ender

@ MartinBüttner Продовжуйте надходити :)
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

Ідея полягає в тому, щоб перетягнути багатокутник на кожному перетині, що призведе до об'єднання багатокутників без пересічних країв. Потім ми можемо обчислити (підписану) область кожного з полігонів за допомогою формули шнурка Гаусса ( 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.