Tensión en un gráfico, Parte I: una cuerda ondulada

21

Tracemos una función f (x) = sin (πx) + 0.5 sin (3πx) sobre el dominio [-3,3] . Podemos interpretar esto como una cuerda suelta sobre un tablero. Ahora vamos a clavar n clavos en el tablero en las posiciones (x 1 , y 1 ) a (x n , y n ) , donde x i ∈ (-3,3) e y i ∈ [-1,1] . Imagine que hay dos ojales al final de la cuerda, que están en las posiciones (-3,0) y (3,0). Ahora podemos tomar los extremos de la cuerda y pasar los ojales hasta que la cuerda esté tensa. Esto deformará nuestro gráfico en una función lineal por partes.

Algunas fotos pueden ayudar. Tome 8 clavos en (-2.8, -0.7), (-2.5, -0.9), (-1.2, .2), (-0.5, .8), (0.5, .4), (1.2, -0.9), (1.5, -0.6), (1.8, -0.8) . Los siguientes tres gráficos muestran el proceso descrito anteriormente:

ingrese la descripción de la imagen aquí

Para una versión más grande: haga clic con el botón derecho -> Abrir en una pestaña nueva

Y aquí hay una animación del ajuste de la cuerda si tiene alguna dificultad para visualizarla:

ingrese la descripción de la imagen aquí

El reto

Dada una lista de "clavos" (que no está necesariamente ordenada), trace esos clavos y la cuerda tensa si comienza con la forma de la función anterior f .

Puede escribir un programa o función y recibir información a través de STDIN, ARGV o argumento de función. Puede mostrar el resultado en la pantalla o guardar una imagen en un archivo.

Si el resultado está rasterizado, debe tener al menos 300 píxeles de ancho y 100 píxeles de alto. El rango de coordenadas de (-3, -1.1) a (3,1.1) debe cubrir al menos el 75% de la extensión horizontal y vertical de la imagen. Las escalas de longitud de x e y no tienen que ser las mismas. Debe mostrar las uñas (usando al menos 3x3 píxeles) y la cadena (al menos 1 píxel de ancho). Puede incluir o no los ejes.

Los colores son su elección, pero necesita al menos dos colores distinguibles: uno para el fondo y otro para las uñas y la cuerda (aunque pueden tener colores diferentes).

Puede suponer que todas las uñas están al menos a 10-5 unidades de distancia de f (para que no tenga que preocuparse por la inexactitud de punto flotante).

Este es el código de golf, por lo que gana la respuesta más corta (en bytes).

Más ejemplos

Aquí hay dos ejemplos más (más simples):

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

ingrese la descripción de la imagen aquí

(La cadena coincide con el eje 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}}

ingrese la descripción de la imagen aquí

¿Quieres otro desafío?

¡Aquí está la parte II!

Martin Ender
fuente
¿Podemos suponer que las uñas están ordenadas de izquierda a derecha?
Ell
@ Todo Ah, buena captura. Como no lo he especificado para empezar, no. Lo aclararé.
Martin Ender

Respuestas:

8

Pitón + 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()

Ejemplo

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

Ejemplo 1

Cómo funciona

Supongamos que sabemos que la cuerda tensa pasa a través de dos puntos A y B (siempre podemos comenzar con
A = (-3, 0) y B = (3, 0)) . Al tirar de la cuerda, "quiere" tomar la ruta más corta posible entre A y B , es decir, idealmente, el segmento AB . Sin embargo, si hay clavos en el área delimitada por la función ( sen πx + ... ) y AB , entonces al menos uno de ellos debe bloquear la cadena. En particular, los clavos más alejados de AB dentro de dicha área deben bloquear la cuerda. Por lo tanto, si C es este clavo, sabemos que la cuerda tensa debe pasarC , además de A y B . Ahora podemos repetir el proceso para los segmentos AC y CB , y continuar de esta manera hasta que finalmente no haya más uñas interpuestas. Figura 1

Este es un algoritmo binario de divide y vencerás con un escaneo lineal en cada paso, por lo que tiene una complejidad en el mejor de los casos de O (n log n) y la complejidad en el peor de los casos de O (n 2 ) .

Ana
fuente
Se produce un error si la lista de puntos está vacía. ¡Pero aparte de eso, el mío es obviamente inútil!
fiesta del
@feersum Buena captura. Fijo.
Ell
3

Python + pylab, 576 bytes

Algoritmo:

Interpreté el problema como encontrar la ruta más corta de (-3, 0) a (3, 0) de tal manera que un segmento de línea vertical que conecta un punto en la ruta a un punto en f (x) nunca cruza un clavo.

En cada x donde exista al menos un clavo, encuentre el límite superior mínimo y el límite inferior mayor dado por los clavos en esa x . Considere los puntos dados por estos límites más los puntos inicial y final como los vértices de un gráfico. Agregue un borde con el peso dado por la distancia euclidiana entre dos vértices si el segmento de línea entre ellos cae dentro de los límites superior e inferior para cada coordenada x interpuesta. Encuentra la ruta más corta en este gráfico.

Ejemplo con 27 puntos aleatorios:

(-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), 

ejemplo cojo

Golfed

Lo que aparece como varios espacios de sangría en el for j in R(i&~1)bucle debería ser una pestaña.

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

Sin golf

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
fuente
PyLab fue definitivamente una elección más inteligente :)
Ell