L o o p I t

22

Nota: O título desta pergunta deve ser "Loop It", mas como o título precisa ter pelo menos 15 caracteres, existem alguns espaços invisíveis. Esta nota é tal que o desafio pode ser pesquisado.


Desafio

Dada uma lista finita de pontos integrais únicos no plano, encontre um polígono cujos vértices são exatamente aqueles pontos que não se intersectam.

Detalhes

  • Como entrada, você pode fazer, por exemplo, duas listas com as coordenadas x e y ou uma lista de pares.
  • A lista de entrada contém pelo menos 3 pontos.
  • Observe que isso significa que nunca há uma solução única.
  • Pode-se supor que a lista de entradas não seja co-linear (os pontos não podem estar contidos em uma linha), isso significa que realmente existe um polígono que não se intercepta.
  • Os ângulos em cada vértice são arbitrários, incluindo 180 °.
  • Para uma entrada de comprimento n, a saída deve ser uma permutação (p1,p2,p3,...,pn)de (1,2,3,...,n)onde a k-ésima entrada pkrepresenta o p-ésimo ponto na lista de entradas. Isso significa que temos uma linha de p1para p2, uma linha de p2para p3etc, bem como uma linha de pnpara p1. (Você também pode usar os índices baseados em 0). Como alternativa, você pode simplesmente exibir a lista de pontos de entrada na ordem correta.

Exemplos

Digamos que temos os pontos [(0,0),(0,1),(1,0),(-1,0),(0,-1)]e queremos representar o seguinte caminho:

insira a descrição da imagem aqui

Isso significa que exibiríamos a lista [5,1,4,2,3]

Aqui estão mais algumas sugestões para tentar (recomendo examinar os gráficos correspondentes para verificar os objetivos).

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)]
flawr
fonte
Se temos 4 pontos O (0,0), A (1,0), B (0,1), C (0,2), o polígono OABC é auto-interceptável?
NGN
@ngn Esse é um bom ponto que eu não considerei! Vou ter que pensar sobre isso. Se você tiver algum argumento a favor ou contra isso, entre em contato.
flawr
@ngn Eu contaria esse polígono como auto-interseção. O motivo é que eu definiria um polígono para se auto-interceptar se houver um ponto comum de duas arestas que não seja um ponto final.
flawr
@ flawr Devo retirar minha resposta, pois ela falha quando há vários pontos co-lineares no ângulo máximo do ponto de referência.
NGN

Respostas:

10

Mathematica 29 28 Bytes

FindShortestTour (16 bytes) faz o truque, mas fornece algumas informações estranhas não solicitadas (o comprimento do caminho e um retorno ao ponto de partida).

Most@*Last@*FindShortestTour

dá apenas a resposta (-1 byte graças a @ user202729)

Para visualizar, use Graphics@Line[g[[%]]], onde %está a permutação encontrada acima eg é a lista de pontos original.

Aqui está a visualização da solução para a Menger Sponge: insira a descrição da imagem aqui

Aqui está uma solução em 1000 pontos aleatórios:

insira a descrição da imagem aqui

A chave aqui é saber que a solução mais curta do problema do vendedor ambulante ou do viajante nunca produzirá cruzamentos quando a distância euclidiana for usada como métrica. Uma das etapas para localizar uma solução e garantir a otimização é remover essas interseções.

Kelly Lowder
fonte
6
Use o algoritmo NP para resolver o problema P apenas porque é mais curto. +1 (???).
usar o seguinte comando
1
@*parece salvar um byte.
usar o seguinte comando
6

JavaScript (ES6), 365 341 bytes

Sem nenhum built-in, isso acabou sendo muito mais longo do que eu esperava. Muitos bytes são gastos na detecção de segmentos colineares sobrepostos.

Recebe a entrada como uma matriz de [x,y]coordenadas. Retorna uma permutação da entrada.

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

Demo

Esse trecho registra a saída e desenha o caminho correspondente em uma tela.

Quão?

Aqui está a estrutura da principal função recursiva f () , deixando de lado o código de teste de interseção por enquanto:

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

Abaixo está o detalhe do teste de interseção () . Esta página fornece uma explicação abrangente sobre o algoritmo usado.

[                                     // 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

Finalmente, aqui está a definição da função auxiliar 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
fonte
... explicação por favor?
precisa saber é o seguinte
1
@ user202729 (* passa a mão na testa *) Concluído!
precisa saber é o seguinte
5

APL (Dyalog Classic) , 42 38 bytes

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

Experimente online!

Entrada é uma lista de pares de coordenadas. A saída é uma permutação baseada em 0.

é a lista de pontos - o argumento para { }

⍵[⊃⍋↑⍵] é o ponto mais baixo mais à esquerda

⍵- converte todos os pontos para que o menor à esquerda fique na origem do sistema de coordenadas

0j1 a unidade imaginária i = sqrt (-1)

0j1⊥¨ decodifica as coordenadas como se dígitos em um sistema de números base-i - ou seja, transforma (x, y) em um número complexo ix + y

z← atribuir a z

12○calcula os argumentos dos números complexos, também conhecidos como ângulos teta ou função circular APL 12

(⍪,(|z)ׯ1*⊢=⌈/)é um trem que calcula uma máscara booleana de onde os ângulos estão no máximo ( ⊢=⌈/), transforma o 0 1 na máscara em 1 ¯1, elevando ¯1 à potência correspondente ( ¯1*), multiplicando pelas magnitudes dos números complexos |z, e concatena isso à direita ( ,) de uma matriz alta e fina de 1 coluna ( ) dos ângulos.

grade - retorna a permutação que classificaria as linhas da matriz lexicograficamente em ordem crescente

ngn
fonte
@ user202729 eles serão classificados de acordo com o segundo critério - distância (função circular 10, ou seja, magnitude complexa)
ngn