Tensão em um gráfico, parte I: uma corda ondulada

21

Vamos traçar uma função f (x) = sin (πx) + 0,5 sin (3πx) sobre o domínio [-3,3] . Podemos interpretar isso como um fio solto deitado em um quadro. Agora vamos colocar n pregos no quadro nas posições (x 1 , y 1 ) a (x n , y n ) , onde x i ∈ (-3,3) e y i ∈ [-1,1] . Imagine que existem dois ilhós no final da corda, que estão nas posições (-3,0) e (3,0). Agora podemos pegar as pontas do barbante e puxar através dos ilhós até que o barbante esteja esticado. Isso deforma nosso gráfico em uma função linear por partes.

Algumas fotos podem ajudar. Faça 8 unhas em (-2,8, -0,7), (-2,5, -0,9), (-1,2, .2), (-0,5, 0,8), (0,5, .4), (1,2, -0,9), (1,5, -0,6), (1,8, -0,8) . Os três gráficos a seguir mostram o processo descrito acima:

insira a descrição da imagem aqui

Para versão maior: Clique com o botão direito do mouse -> Abrir em uma nova guia

E aqui está uma animação do aperto da corda, se você tiver alguma dificuldade para visualizá-la:

insira a descrição da imagem aqui

O desafio

Dada uma lista de "pregos" (que não é necessariamente classificada), plote essas unhas e a corda esticada se ela começar da forma da função acima f .

Você pode escrever um programa ou função e receber entradas via STDIN, ARGV ou argumento de função. Você pode exibir o resultado na tela ou salvar uma imagem em um arquivo.

Se o resultado for rasterizado, ele precisará ter pelo menos 300 pixels de largura e 100 pixels de altura. O intervalo de coordenadas de (-3, -1,1) a (3,1.1) deve cobrir pelo menos 75% da extensão horizontal e vertical da imagem. As escalas de comprimento de x e y não tem que ser o mesmo. Você precisa mostrar as unhas (usando pelo menos 3x3 pixels) e a corda (com pelo menos 1 pixel de largura). Você pode ou não incluir os eixos.

As cores são a sua escolha, mas você precisa de pelo menos duas cores distintas: uma para o fundo e outra para as unhas e a corda (essas podem ter cores diferentes).

Você pode assumir que todas as unhas estão a pelo menos 10 a 5 unidades de distância de f (para que você não precise se preocupar com imprecisão de ponto flutuante).

Isso é código de golfe, então a resposta mais curta (em bytes) vence.

Mais exemplos

Aqui estão mais dois exemplos (mais simples):

{{-2.5, 1}, {-1.5, -1}, {-0.5, 1}, {0.5, -1}, {1.5, 1}, {2.5, -1}}

insira a descrição da imagem aqui

(A sequência coincide com o eixo x ).

{{-2.7, -0.5}, {-2.3, -0.5}, {-1.7, 0.5}, {-1.3, 0.5}, {-0.7, -0.5}, {-0.3, -0.5}, {0.5, 1}, {1.5, -1}, {2.5, 1}}

insira a descrição da imagem aqui

Quer outro desafio?

Aqui está a parte II!

Martin Ender
fonte
Podemos assumir que as unhas estão ordenadas da esquerda para a direita?
Ell
@ Ell Ah, boa captura. Desde que eu não o especifiquei, não. Eu vou esclarecer isso.
Martin Ender

Respostas:

8

Python + Pycairo, 727 708 608, + PyLab, 383

from pylab import*
def f(N):
 def P(u,w,N):
    T=lambda v,p:(C(v-u,p-u)>0)==(C(w-v,p-v)>0)==(C(u-w,p-w)>0);M=[(i,n)for i,n in enumerate(N)if T(V([n[0],sin(pi*n[0])+sin(3*pi*n[0])/2]),n)]
    if M:i,n=max(M,key=lambda n:C(n[1]-u,w-u)**2);M=P(u,n,N[:i])+[n]+P(n,w,N[i+1:])
    return M
 V=array;C=cross;a=V([3,0]);plot(*zip(*([-a]+P(-a,a,map(V,sorted(N)))+[a])));N and scatter(*zip(*N));show()

Exemplo

f([(-2.8,-0.7),(-2.5,-0.9),(-1.2,0.2),(-0.5,0.8),(0.5,0.4),(1.2,-0.9),(1.5, -0.6),(1.8, -0.8)])

Exemplo 1

Como funciona

Suponha que sabemos que a corda esticada passa por dois pontos A e B (sempre podemos começar com
A = (-3, 0) e B = (3, 0) .) Quando puxamos a corda, ela "quer" o caminho mais curto possível entre A e B , ou seja, idealmente, o segmento AB . No entanto, se houver pregos na área delimitada pela função ( sin πx + ... ) e AB , pelo menos um deles deverá bloquear a sequência. Em particular, as unhas mais distantes de AB dentro da referida área devem bloquear o fio. Portanto, se C é esse prego, sabemos que a corda esticada deve passar atravésC , em adição a um e B . Agora podemos repetir o processo para os segmentos AC e CB , e continuar dessa maneira até que não haja mais unhas intermediárias. figura 1

Esse é um algoritmo binário de dividir e conquistar com uma varredura linear em cada etapa, portanto, possui uma complexidade de melhor caso de O (n log n) e complexidade de pior caso de O (n 2 ) .

Ell
fonte
Erro se a lista de pontos estiver vazia. Mas fora isso, o meu é obviamente impossível!
feersum
@feersum Boa captura. Fixo.
Ell
3

Python + pylab, 576 bytes

Algoritmo:

Eu interpretei o problema como encontrar o caminho mais curto de (-3, 0) a (3, 0), de modo que um segmento de linha vertical conectando um ponto no caminho a um ponto em f (x) nunca cruze um prego.

Em cada x, onde existe pelo menos uma unha, encontre o menor limite superior e o maior limite inferior dado pelas unhas naquele x . Considere os pontos dados por esses limites mais os pontos inicial e final como os vértices de um gráfico. Adicione uma aresta com o peso dado pela distância euclidiana entre dois vértices se o segmento de linha entre eles estiver dentro dos limites superior e inferior para cada coordenada x intermediária. Encontre o caminho mais curto neste gráfico.

Exemplo com 27 pontos aleatórios:

(-0.367534, -0.722751), (-0.710649, -0.701412), (1.593101, -0.484983), (1.771199, 0.681435), (-1.878764, -0.491436), (-0.061414, 0.628570), (-0.326483, -0.512950), (0.877878, 0.858527), (1.256189, -0.300032), (1.528120, -0.606809), (-1.343850, -0.497832), (1.078216, 0.232089), (0.930588, -0.053422), (-2.024330, -0.296681), (-2.286014, 0.661657), (-0.009816, 0.170528), (2.758464, 0.099447), (-0.957686, 0.834387), (0.511607, -0.428322), (-1.657128, 0.514400), (1.507602, 0.507458), (-1.469429, -0.239108), (0.035742, 0.135643), (1.194460, -0.848291), (2.345420, -0.892100), (2.755749, 0.061595), (0.283293, 0.558334), 

exemplo coxo

Golfe

O que aparece como vários espaços de recuo no for j in R(i&~1)loop deve ser realmente uma guia.

from pylab import*
P=((3,0),(-3,0))+input()
X=sorted(set(zip(*P)[0]))
l=len(X)*2
if l>4:scatter(*zip(*P[2:]))
f=lambda x:sin(pi*x)+sin(3*pi*x)/2
B=[[max([-9]+[p[1]for p in P if x==p[0]and p[1]<f(x)]),min([9]+[p[1]for p in P if x==p[0]and p[1]>f(x)])]for x in X]
b=zeros(l);b[2:]=inf
v=list(b)
R=range
for i in R(l):
 for j in R(i&~1):
    A=B[j/2][j&1];D,d=B[i/2][i&1]-A,X[i/2]-X[j/2];K=1;c=b[j]+norm((d,D))
    for k in R(j/2+1,i/2):C=A+D/d*(X[k]-X[j/2]);K&=C<B[k][1];K&=C>B[k][0]
    if(c<b[i])&K:b[i]=c;v[i]=j,(X[j/2],A)
l-=2
s=P[:1]
while l/2:l,p=v[l];s+=(p,)
plot(*zip(*s))
show()

Ungolfed

from pylab import*
P = input()
Xn,Yn = zip(*P)
X = set(Xn+(3,-3))
f = lambda x:sin(pi*x)+sin(3*pi*x)/2
ylb = {x: max([-9]+[p[1] for p in P if p[0] == x and p[1] < f(x)]) for x in X}
yub = {x: min([9]+[p[1] for p in P if p[0] == x and p[1] > f(x)]) for x in X}
ylb[-3] = yub[3] = ylb[3] = 0
X = sorted(X)
l = len(X)
best = zeros((l,2))
best[1:] = inf
prev = [ [0,0] for i in range(l) ]
for i in range(l): # calculate min path to X[i] lb or ub
  for ib in 0,1:
    for j in range(i): # point to come from
      for jb in 0,1:
          Y2, Y1 = (ylb, yub)[ib][X[i]], (ylb, yub)[jb][X[j]]
          dy,dx = Y2 - Y1, X[i] - X[j]
          if all([Y1 + dy/dx*(x - X[j]) < yub[x] and Y1 + dy/dx*(x - X[j]) > ylb[x] for x in X[j+1:i]]):
             c = best[j][jb] + (dy**2+dx**2)**.5
             if c < best[i][ib]:
                 best[i][ib] = c
                 prev[i][ib] = j, jb, (X[j], Y1)
j, jb = l-1,0
pts = [(3,0)]
while j:
    j, jb, p = prev[j][jb]
    pts += [p]
plot(*zip(*pts))
scatter(Xn,Yn)
show()
feersum
fonte
Pylab foi definitivamente uma escolha mais inteligente :)
Ell