L o o p I t

22

Uwaga: tytuł tego pytania powinien brzmieć „Loop It”, ale ponieważ tytuł musi mieć co najmniej 15 znaków, jest kilka niewidocznych spacji. Ta uwaga jest taka, że ​​można wyszukać wyzwanie.


Wyzwanie

Biorąc pod uwagę skończoną listę unikalnych punktów całkowitych na płaszczyźnie, znajdź wielokąt, którego wierzchołki są dokładnie tymi punktami, które się nie przecinają.

Detale

  • Jako dane wejściowe możesz wziąć np. Dwie listy z każdą współrzędną x i y lub listę par.
  • Lista wejściowa zawiera co najmniej 3 punkty.
  • Pamiętaj, że oznacza to, że nigdy nie ma unikalnego rozwiązania.
  • Można założyć, że lista danych wejściowych nie jest współliniowa (punkty nie mogą być zawarte w jednej linii), co oznacza, że ​​faktycznie istnieje taki nie przecinający się wielokąt.
  • Kąty na każdym wierzchołku są dowolne, obejmuje to 180 °.
  • Na wejście wartości długości n, dane wyjściowe powinny być permutacją (p1,p2,p3,...,pn)od (1,2,3,...,n)gdzie k-ty wpis pkreprezentuje ppunkt -ty z listy wejściowego. Oznacza to, że mamy linię od p1do p2, linię od p2do p3itp., A także linię od pndo p1. (Możesz także użyć wskaźników opartych na 0). Alternatywnie możesz po prostu wypisać listę punktów wejściowych we właściwej kolejności.

Przykłady

Powiedzmy, że mamy punkty [(0,0),(0,1),(1,0),(-1,0),(0,-1)]i chcemy przedstawić następującą ścieżkę:

wprowadź opis zdjęcia tutaj

Oznacza to, że wyprowadzilibyśmy listę [5,1,4,2,3]

Oto kilka sugestii do wypróbowania (polecam przyjrzenie się odpowiednim działkom, aby zweryfikować cele).

Triangle
[(0,0),(0,1),(1,0)]

S-Curve
[(0,0),(0,1),(0,2),(0,3),(0,4),(1,0),(2,0),(2,1),(2,2),(2,3),(2,4),(3,4),(4,0),(4,1),(4,2),(4,3),(4,4)]

L-Shape
[(4,0),(1,0),(3,0),(0,0),(2,0),(0,1)]

Menger Sponge
[(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),(18,1),(19,1),(20,1),(21,1),(22,1),(23,1),(24,1),(25,1),(26,1),(27,1),(1,2),(3,2),(4,2),(6,2),(7,2),(9,2),(10,2),(12,2),(13,2),(15,2),(16,2),(18,2),(19,2),(21,2),(22,2),(24,2),(25,2),(27,2),(1,3),(2,3),(3,3),(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),(17,3),(18,3),(19,3),(20,3),(21,3),(22,3),(23,3),(24,3),(25,3),(26,3),(27,3),(1,4),(2,4),(3,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(16,4),(17,4),(18,4),(19,4),(20,4),(21,4),(25,4),(26,4),(27,4),(1,5),(3,5),(7,5),(9,5),(10,5),(12,5),(16,5),(18,5),(19,5),(21,5),(25,5),(27,5),(1,6),(2,6),(3,6),(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(16,6),(17,6),(18,6),(19,6),(20,6),(21,6),(25,6),(26,6),(27,6),(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),(7,7),(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),(17,7),(18,7),(19,7),(20,7),(21,7),(22,7),(23,7),(24,7),(25,7),(26,7),(27,7),(1,8),(3,8),(4,8),(6,8),(7,8),(9,8),(10,8),(12,8),(13,8),(15,8),(16,8),(18,8),(19,8),(21,8),(22,8),(24,8),(25,8),(27,8),(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),(9,9),(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),(17,9),(18,9),(19,9),(20,9),(21,9),(22,9),(23,9),(24,9),(25,9),(26,9),(27,9),(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),(19,10),(20,10),(21,10),(22,10),(23,10),(24,10),(25,10),(26,10),(27,10),(1,11),(3,11),(4,11),(6,11),(7,11),(9,11),(19,11),(21,11),(22,11),(24,11),(25,11),(27,11),(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(19,12),(20,12),(21,12),(22,12),(23,12),(24,12),(25,12),(26,12),(27,12),(1,13),(2,13),(3,13),(7,13),(8,13),(9,13),(19,13),(20,13),(21,13),(25,13),(26,13),(27,13),(1,14),(3,14),(7,14),(9,14),(19,14),(21,14),(25,14),(27,14),(1,15),(2,15),(3,15),(7,15),(8,15),(9,15),(19,15),(20,15),(21,15),(25,15),(26,15),(27,15),(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(19,16),(20,16),(21,16),(22,16),(23,16),(24,16),(25,16),(26,16),(27,16),(1,17),(3,17),(4,17),(6,17),(7,17),(9,17),(19,17),(21,17),(22,17),(24,17),(25,17),(27,17),(1,18),(2,18),(3,18),(4,18),(5,18),(6,18),(7,18),(8,18),(9,18),(19,18),(20,18),(21,18),(22,18),(23,18),(24,18),(25,18),(26,18),(27,18),(1,19),(2,19),(3,19),(4,19),(5,19),(6,19),(7,19),(8,19),(9,19),(10,19),(11,19),(12,19),(13,19),(14,19),(15,19),(16,19),(17,19),(18,19),(19,19),(20,19),(21,19),(22,19),(23,19),(24,19),(25,19),(26,19),(27,19),(1,20),(3,20),(4,20),(6,20),(7,20),(9,20),(10,20),(12,20),(13,20),(15,20),(16,20),(18,20),(19,20),(21,20),(22,20),(24,20),(25,20),(27,20),(1,21),(2,21),(3,21),(4,21),(5,21),(6,21),(7,21),(8,21),(9,21),(10,21),(11,21),(12,21),(13,21),(14,21),(15,21),(16,21),(17,21),(18,21),(19,21),(20,21),(21,21),(22,21),(23,21),(24,21),(25,21),(26,21),(27,21),(1,22),(2,22),(3,22),(7,22),(8,22),(9,22),(10,22),(11,22),(12,22),(16,22),(17,22),(18,22),(19,22),(20,22),(21,22),(25,22),(26,22),(27,22),(1,23),(3,23),(7,23),(9,23),(10,23),(12,23),(16,23),(18,23),(19,23),(21,23),(25,23),(27,23),(1,24),(2,24),(3,24),(7,24),(8,24),(9,24),(10,24),(11,24),(12,24),(16,24),(17,24),(18,24),(19,24),(20,24),(21,24),(25,24),(26,24),(27,24),(1,25),(2,25),(3,25),(4,25),(5,25),(6,25),(7,25),(8,25),(9,25),(10,25),(11,25),(12,25),(13,25),(14,25),(15,25),(16,25),(17,25),(18,25),(19,25),(20,25),(21,25),(22,25),(23,25),(24,25),(25,25),(26,25),(27,25),(1,26),(3,26),(4,26),(6,26),(7,26),(9,26),(10,26),(12,26),(13,26),(15,26),(16,26),(18,26),(19,26),(21,26),(22,26),(24,26),(25,26),(27,26),(1,27),(2,27),(3,27),(4,27),(5,27),(6,27),(7,27),(8,27),(9,27),(10,27),(11,27),(12,27),(13,27),(14,27),(15,27),(16,27),(17,27),(18,27),(19,27),(20,27),(21,27),(22,27),(23,27),(24,27),(25,27),(26,27),(27,27)]
wada
źródło
Jeśli mamy 4 punkty O (0,0), A (1,0), B (0,1), C (0,2), to czy wielokąt OABC jest przecinający?
ngn
@ngn To dobry punkt, którego nie wziąłem pod uwagę! Będę musiał o tym pomyśleć. Jeśli masz jakiś argument za lub przeciw temu, daj mi znać.
flawr
@ngn Policzyłbym ten wielokąt jako przecinający się. Powodem jest to, że zdefiniowałbym wielokąt jako przecinający się, jeśli istnieje wspólny punkt dwóch krawędzi, który nie jest punktem końcowym.
flawr
@ flawr Muszę wycofać moją odpowiedź, wówczas nie powiedzie się, gdy istnieje wiele współliniowych punktów pod maksymalnym kątem od punktu odniesienia.
ngn

Odpowiedzi:

10

Mathematica 29 28 bajtów

FindShortestTour (16 bajtów) wykonuje lewę, ale zapewnia dodatkowe informacje, o które nie pytano (długość ścieżki i powrót do punktu początkowego).

Most@*Last@*FindShortestTour

daje tylko odpowiedź (-1 bajt dzięki @ user202729)

Aby wizualizować, użyj Graphics@Line[g[[%]]], gdzie %jest permutacja znaleziona powyżej, a g to oryginalna lista punktów.

Oto wizualizacja rozwiązania dla gąbki Menger: wprowadź opis zdjęcia tutaj

Oto rozwiązanie 1000 losowych punktów:

wprowadź opis zdjęcia tutaj

Kluczem jest tutaj świadomość, że najkrótsze rozwiązanie problemu sprzedawcy lub podróżującego sprzedawcy nigdy nie doprowadzi do przecięcia, gdy metryką jest odległość euklidesowa. Jednym z kroków do zlokalizowania rozwiązania i zapewnienia optymalności jest usunięcie takich skrzyżowań.

Kelly Lowder
źródło
6
Użyj algorytmu NP, aby rozwiązać problem P tylko dlatego, że jest on krótszy. +1 (???).
user202729
1
@*wydaje się oszczędzać bajt.
user202729
6

JavaScript (ES6), 365 341 bajtów

Bez żadnego wbudowanego okazało się to znacznie dłużej, niż się spodziewałem. Wiele bajtów jest zużywanych na wykrywanie kolinearnych nakładających się segmentów.

Pobiera dane wejściowe jako tablicę [x,y]współrzędnych. Zwraca permutację wejścia.

f=(a,p=[],o=([p,P],[q,Q],[r,R])=>Math.sign((S=[(p>q?r<q|r>p:r<p|r>q)|(P>Q?R<Q|R>P:R<P|R>Q),...S],Q-P)*(r-q)-(q-p)*(R-Q)))=>[...p,p[0]].some((A,i,P)=>P.some((C,j)=>j>i+1&&P[++j+!i]&&[E=o(A,B=P[i+1],C,S=[]),F=o(A,B,D=P[j]),G=o(C,D,A),H=o(C,D,B)].some(v=>!v&!S.pop())|E!=F&G!=H))?0:a[0]?a.some((_,i)=>r=f(b=[...a],p.concat(b.splice(i,1))))&&r:p

Próbny

Ten fragment rejestruje dane wyjściowe i rysuje odpowiednią ścieżkę w kanwie.

W jaki sposób?

Oto struktura głównej funkcji rekurencyjnej f () , pomijając na razie kod testujący skrzyżowanie:

f = (a, p = []) =>                    // a = array of points, p = current path
  [...p,                              // build a closed path array P[] by adding the first
         p[0]]                        // point at the end of p[]
  .some((A, i, P) =>                  // for each point A at position i in P:
    P.some((C, j) =>                  //   for each point C at position j in P:
      j > i + 1 &&                    //     test whether C is at least 2 positions after A
      P[++j +                         //     and C is not the last point
              !i] &&                  //     and i > 0 or C is not the penultimate point
      intersection(                   //     and there's an intersection between
        A, P[i + 1], C, P[j]          //     the segments (A, P[i + 1]) and (C, P[j + 1])
      )                               //     (j was incremented above)
    )                                 //   end of inner some()
  ) ?                                 // end of outer some(); if truthy:
    0                                 //   discard this path by stopping recursion
  :                                   // else:
    a[0] ?                            //   if there's at least one remaining point:
      a.some((_, i) =>                //     for each remaining point at position i:
        r = f(                        //       do a recursive call with:
          b = [...a],                 //         a copy b[] of a[] without a[i] and
          p.concat(b.splice(i, 1)))   //         the extracted point added to the path
      ) && r                          //     end of some(); return the result, if any
    :                                 //   else:
      p                               //     this is a valid path: return it

Poniżej znajduje się szczegół testu przecięcia () . Ta strona zawiera wyczerpujące objaśnienie zastosowanego algorytmu.

[                                     // build an array containing:
  E = o(A, B = P[i + 1], C, S = []),  //   E = test of (A, B, C) (+ initialization of S[])
  F = o(A, B, D = P[j]),              //   F = test of (A, B, D)
  G = o(C, D, A),                     //   G = test of (C, D, A)
  H = o(C, D, B)                      //   H = test of (C, D, B)
]                                     //
.some(v =>                            // the segments are collinear and overlapping if:
  !v &                                //   any value above is 0
  !S.pop()                            //   and the corresponding entry in S[] is falsy
) |                                   // the segments intersect if:
E != F & G != H                       //   E is not equal to F and G is not equal to H

Na koniec, oto definicja funkcji pomocniczej o () :

o = (                                             // given three points represented by
  [p, P], [q, Q], [r, R]                          // a lowercase letter for x
) =>                                              // and an uppercase letter for y:
  Math.sign(                                      //
    (                                             //   1) prepend to the array S[]
      S = [                                       //      a boolean which is true if the
        (p > q ? r < q | r > p : r < p | r > q) | //      segment (P, Q) would not contain
        (P > Q ? R < Q | R > P : R < P | R > Q),  //      the point R, assuming that the
        ...S                                      //      3 points are collinear
      ],                                          //
                                                  //   2) return the orientation of P, Q, R:
      Q - P                                       //        -1 = counterclockwise
    ) * (r - q) - (q - p) * (R - Q)               //         0 = collinear
  )                                               //        +1 = clockwise
Arnauld
źródło
... proszę wyjaśnienie?
user202729
1
@ user202729 (* wyciera rękę w czoło *) Gotowe!
Arnauld
5

APL (Dyalog Classic) , 42 38 bajtów

{⍋(⍪,(|z)ׯ1*⊢=⌈/)12z0j1⊥¨⍵-⍵[⊃⍋↑⍵]}

Wypróbuj online!

Dane wejściowe to lista par współrzędnych. Dane wyjściowe to permutacja oparta na 0.

jest lista punktów - argument do { }

⍵[⊃⍋↑⍵] jest najniższym punktem na lewo

⍵- tłumaczy wszystkie punkty, tak aby skrajnie lewy najniższy znajdował się u początku układu współrzędnych

0j1 urojona jednostka i = sqrt (-1)

0j1⊥¨ dekoduje współrzędne tak, jakby cyfry w systemie liczbowym i-base - tzn. zamienia (x, y) na liczbę zespoloną ix + y

z← Przypisać do z

12○oblicza argumenty liczb zespolonych, czyli kąty theta lub funkcję kołową APL 12

(⍪,(|z)ׯ1*⊢=⌈/)jest pociągiem, który oblicza maskę logiczną o maksymalnym kącie ( ⊢=⌈/), zamienia 0 1 w masce na 1 by1, podnosząc ¯1 do odpowiedniej mocy ( ¯1*), mnoży przez wartości liczb zespolonych |z, i łączy to po prawej stronie ( ,) wysokiej cienkiej 1-kolumnowej matrycy ( ) kątów.

klasa - zwraca permutację, która sortowałaby wiersze macierzy leksykograficznie w porządku rosnącym

ngn
źródło
@ user202729 zostaną posortowane według drugiego kryterium - odległości (tj. funkcji kołowej 10, czyli wielkości zespolonej)
ngn