Tension sur un graphique, partie I: une chaîne ondulée


21

Représentons une fonction f (x) = sin (πx) + 0,5 sin (3πx) sur le domaine [-3,3] . Nous pouvons interpréter cela comme une chaîne lâche posée sur une planche. Maintenant, enfonçons n clous dans la planche aux positions (x 1 , y 1 ) à (x n , y n ) , où x i ∈ (-3,3) et y i ∈ [-1,1] . Imaginez qu'il y ait deux œillets à la fin de la chaîne, c'est-à-dire aux positions (-3,0) et (3,0). Nous pouvons maintenant prendre les extrémités de la ficelle et tirer à travers les œillets jusqu'à ce que la ficelle soit tendue. Cela déformera notre graphique en une fonction linéaire par morceaux.

Certaines photos pourraient aider. Prenez 8 clous à (-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) . Les trois graphiques suivants montrent le processus décrit ci-dessus:

entrez la description de l'image ici

Pour une version plus grande: clic droit -> Ouvrir dans un nouvel onglet

Et voici une animation du serrage des cordes si vous avez du mal à le visualiser:

entrez la description de l'image ici

Le défi

Étant donné une liste de "clous" (qui n'est pas nécessairement triée), tracez ces clous et la chaîne tendue si elle part de la forme de la fonction ci-dessus f .

Vous pouvez écrire un programme ou une fonction et prendre une entrée via STDIN, ARGV ou un argument de fonction. Vous pouvez soit afficher le résultat à l'écran, soit enregistrer une image dans un fichier.

Si le résultat est tramé, il doit avoir une largeur d'au moins 300 pixels et une hauteur de 100 pixels. La plage de coordonnées de (-3, -1,1) à (3,1.1) doit couvrir au moins 75% de l'étendue horizontale et verticale de l'image. Les échelles de longueur de x et y ne doivent pas nécessairement être les mêmes. Vous devez montrer les ongles (en utilisant au moins 3x3 pixels) et la chaîne (au moins 1 pixel de large). Vous pouvez ou non inclure les axes.

Les couleurs sont votre choix, mais vous avez besoin d'au moins deux couleurs distinctes: une pour l'arrière-plan et une pour les ongles et la chaîne (celles-ci peuvent cependant avoir des couleurs différentes).

Vous pouvez supposer que tous les clous sont à au moins 10 à 5 unités de f (de sorte que vous n'avez pas à vous soucier de l'inexactitude en virgule flottante).

Il s'agit du code golf, donc la réponse la plus courte (en octets) l'emporte.

Plus d'exemples

Voici deux autres exemples (plus simples):

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

entrez la description de l'image ici

(La chaîne coïncide avec l' axe des 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}}

entrez la description de l'image ici

Envie d'un autre défi?

Voici la partie II!


Peut-on supposer que les ongles sont triés de gauche à droite?
Ell

@Ell Ah, bonne prise. Puisque je ne l'ai pas spécifié pour commencer, non. Je vais clarifier cela.
Martin Ender

Réponses:


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

Exemple

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

Exemple 1

Comment ça marche

Supposons que nous savons que la chaîne tendue passe par deux points A et B (nous pouvons toujours commencer par
A = (-3, 0) et B = (3, 0) .) Lorsque nous tirons la chaîne, elle "veut" prendre le chemin le plus court possible entre A et B , c'est-à-dire, idéalement, le segment AB . Cependant, s'il y a des clous dans la zone délimitée par la fonction ( sin πx + ... ) et AB , alors au moins l'un d'eux doit bloquer la chaîne. En particulier, le ou les clous les plus éloignés de AB dans ladite zone doivent bloquer la chaîne. Par conséquent, si C est ce clou, nous savons que la chaîne tendue doit passer à traversC , en plus de A et de B . Nous pouvons maintenant répéter le processus pour les segments AC et CB , et continuer ainsi jusqu'à ce qu'il n'y ait plus de clous intermédiaires. Figure 1

Il s'agit d'un algorithme de division et de conquête binaire avec un balayage linéaire à chaque étape, il a donc une complexité dans le meilleur des cas de O (n log n) et dans le pire des cas de O (n 2 ) .


Il commet une erreur si la liste de points est vide. Mais à part ça, la mienne est évidemment sans espoir!
feersum

@feersum Bonne prise. Fixé.
Ell

3

Python + pylab, 576 octets

Algorithme:

J'ai interprété le problème comme trouvant le chemin le plus court de (-3, 0) à (3, 0) de telle sorte qu'un segment de ligne verticale reliant un point sur le chemin à un point sur f (x) ne croise jamais un clou.

À chaque x où existe au moins un clou, trouvez la borne inférieure la plus haute et la borne inférieure la plus grande données par les clous à ce x . Considérez les points donnés par ces bornes ainsi que les points de départ et d'arrivée comme étant les sommets d'un graphique. Ajoutez une arête avec un poids donné par la distance euclidienne entre deux sommets si le segment de ligne entre eux tombe dans les limites supérieure et inférieure pour chaque coordonnée x intermédiaire. Trouvez le chemin le plus court sur ce graphique.

Exemple avec 27 points aléatoires:

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

exemple boiteux

Golfé

Ce qui apparaît comme plusieurs espaces d'indentation dans la for j in R(i&~1)boucle devrait en fait être un onglet.

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

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

PyLab était définitivement un choix plus intelligent :)
Ell
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.