Un perro en una cadena


31

Estoy mirando por la ventana de mi ático hacia el patio de mi vecino. Tienen un perro encadenado a un poste en el centro del patio. El perro corre por el patio pero siempre está al final de su cadena, por lo que termina dejando una huella en la tierra. Normalmente, esta pista sería perfectamente circular, pero mis vecinos tienen otros postes en su patio donde la cadena del perro queda atrapada. Cada vez que la cadena del perro golpea un poste, el perro comienza a girar alrededor del nuevo poste con cualquier longitud de cadena que quede como su radio. Dado que los polos, el perro y la cadena tienen un ancho cero (mis vecinos son matemáticos), la cadena puede enrollarse indefinidamente alrededor de un poste sin que el radio del círculo se acorte. El perro también puede pasar a través de la cadena (simplemente no su collar) si la cadena está en su camino. Después de observar esta rareza por un tiempo, decido escribir un código para simular el perro de mi vecino. El código tomará las ubicaciones de un poste central, al que está encadenado el perro, las ubicaciones de los otros postes en el patio de mis vecinos, la longitud de la cadena y la ubicación inicial del perro, y mostrará un diagrama que indica el camino donde el perro ha desgastado la hierba. Puede suponer que cualquier combinación de lo siguiente es constante (y por lo tanto no los toma como entrada):

  • Ubicación del poste al que está encadenado el perro.

  • Longitud de la cadena

  • Lugar de inicio del perro

El sol está saliendo, por lo que el espacio en el piso de mi ático iluminado por la ventana se está reduciendo, lo que me da cada vez menos espacio para escribir mi código. Intente minimizar el recuento de bytes de su código para que tenga espacio para redactarlo en mi piso del ático.

Casos de prueba

Aquí supongo que el perro comienza 3 unidades al sur desde el polo al que está encadenado (el punto rojo), ubicado en 0,0. He indicado dónde están los polos con puntos para mayor claridad, no necesita incluirlos en su salida.

Poles at 1,2 -1,2

Prueba 1

Poles at 0,.5

Prueba 2

Poles at 0,1 1,1 -2,1 -1,-.5

Prueba 3

Poles at 0,1 1,1

Prueba 4


¿Para qué es la salida {0,-.5}?
Kritixi Lithos

@ KritixiLithos Es la salida de {0,.5}volteado verticalmente sin el círculo más grande. El perro esencialmente comienza atrapado en el segundo poste.
Wheat Wizard

Como resultado de problemas de punto flotante, mi programa dibuja un círculo alrededor (1,1) en el último caso de prueba (la longitud de la cadena es 99.99999). ¿Esta bien?
Kritixi Lithos

El perro corre tanto en sentido horario como en sentido antihorario, pero ¿desde un punto fijo?
user202729

3
"El sol está saliendo, el espacio en el piso de mi ático iluminado por la ventana se está reduciendo, lo que me da cada vez menos espacio para escribir mi código" +1 solo por esto
Leo

Respuestas:


11

Python 3 usando matplotlib, 457 bytes

from cmath import*
from matplotlib import pyplot as g,patches as i
def x(p):
 p+=[0];d=180/pi;a=2;h=g.gca();h.set_xlim(-5,5);h.set_ylim(-5,5)
 while a:
  a-=1;c=0;y=3;z=-pi/2
  while 1:
   s=[n for n in p if abs(n-c)<=y and n!=c]
   if not s:h.add_patch(i.Arc((c.real,c.imag),y*2,y*2));break
   n=[max,min][a](s,key=lambda n:(z-phase(n-c))%(2*pi));l,r=polar(n-c);h.add_patch(i.Arc((c.real,c.imag),y*2,y*2,[z,r][a]*d,0,[r-z,z-r][a]*d));y-=l;z=r;c=n
 g.show()

Como sus vecinos son matemáticos, he asumido que el jardín de su vecino ocupa el dominio complejo y, por lo tanto, cualquier coordenada de objetos en el jardín son números complejos. Para utilizar esta función, debe pasarle una lista de números complejos que indiquen la ubicación de los postes en el jardín de su vecino. Se ha elegido la representación predeterminada del sistema de coordenadas, donde a la derecha hay números reales positivos y hacia arriba son números imaginarios positivos. Esto significa que los ejemplos se convierten en:

x([2j+1,2j-1])
x([.5j])
x([1j,1+1j,-2+1j,-1-.5j])
x([1j,1+1j])

Además, el programa asume lo siguiente: la correa está atada al punto 0, la correa tiene 3 unidades de largo y el área de trazado es 10 por 10 centrada alrededor de 0. Para estos parámetros, los resultados coinciden exactamente con los ejemplos, y así es como se ve el resultado (para el ejemplo final):

x ([1j, 1 + 1j])

El algoritmo es bastante simple, solo requiere uno condicional para diferenciar la búsqueda en sentido horario y en sentido antihorario. El estado del algoritmo está definido por el punto de rotación actual y la orientación / longitud restante de la correa cuando llega al punto de rotación actual. Funciona de la siguiente manera:

  • Filtre los puntos del conjunto de colisión que estén más lejos del punto de rotación actual que la longitud de correa restante, así como el punto de rotación actual.
  • Si este conjunto está vacío, dibuje un círculo con el radio de la longitud restante de la correa alrededor de este punto cuando se haya alcanzado el final de este brazo.
  • Determine el punto donde la diferencia de fase entre el vector de diferencia y la orientación de la correa es mínima / máxima. Este es el siguiente punto que la correa golpeará en sentido horario / antihorario respectivamente.
  • Dibuje el arco basado en estos vectores, tome la longitud de la correa, reste la magnitud de la distancia y establezca la orientación de la correa a la orientación del vector de diferencia. Actualice el punto de rotación y continúe desde el principio.

Este algoritmo se realiza primero en el sentido de las agujas del reloj, después de lo cual se restablece el estado y se ejecuta en el sentido contrario a las agujas del reloj. La simplicidad del algoritmo significa que aproximadamente la mitad del bytecount del programa se gasta en las funciones de dibujo. Si las rutinas de dibujo se eliminaran, eliminaría 218 bytes del tamaño del programa.

La siguiente es una versión no protegida que también contiene código de depuración, que también muestra puntos y colisiones de correa:

from cmath import pi, rect, polar, phase
from matplotlib import pyplot, patches
def x_ungolfed(points):
    degrees = 180/pi # conversions

    # add the center point to the collision points
    points.append(0.0)

    # configure plot area
    axes=pyplot.gca()
    axes.set_xlim(-5,5)
    axes.set_ylim(-5,5)

    # plot the points
    x, y =zip(*((p.real, p.imag) for p in points))
    axes.scatter(x, y, 50, "b")

    # first iteration is clockwise, second counterclockwise
    clockwise = 2
    while clockwise:
        clockwise -= 1

        # initial conditions
        center = 0 + 0j;
        leash_size = 3
        leash_angle = -pi / 2

        # initial leash plot
        leash_start = rect(leash_size, leash_angle)
        axes.plot([center.real, leash_start.real], [center.imag, leash_start.imag], "r")

        # search loop
        while 1:
            # find possible collission candidates
            candidates = [n for n in points if abs(n - center) <= leash_size and n != center]
            # if we reached the end, draw a circle
            if not candidates:
                axes.add_patch(patches.Arc(
                    (center.real, center.imag), 
                    leash_size*2, leash_size*2
                ))
                break
            # find the actual collision by comparing the phase difference of the leash angle vs the difference between the candidate and the current node
            new = (min if clockwise else max)(candidates, key=lambda n: (leash_angle - phase(n - center)) % (2 * pi))

            # convert the difference to polar coordinates
            distance, new_angle = polar(new - center)
            # draw the arc
            if clockwise:
                axes.add_patch(patches.Arc(
                    (center.real, center.imag),
                    leash_size * 2, leash_size * 2,
                    new_angle * degrees,
                    0,
                    (leash_angle-new_angle) * degrees
                ))
            else:
                axes.add_patch(patches.Arc(
                    (center.real, center.imag),
                    leash_size * 2, leash_size * 2,
                    leash_angle * degrees,
                    0,
                    (new_angle - leash_angle) * degrees
                ))
            # draw intermediate lines
            edge = rect(leash_size, new_angle) + center
            axes.plot([center.real, edge.real], [center.imag, edge.imag], "g")

            # perform updates: decrease remaining leash size, set new leash angle, move rotation center to the collision
            leash_size -= distance
            leash_angle = new_angle
            center = new

    # show the graph
    pyplot.show()

La salida que produce se ve así:

Igual que la imagen anterior pero con más líneas.


¡+1 por una gran explicación y por haberme jugado casi el doble! <s> Dios, envidio esas construcciones </s>
Kritixi Lithos

7

Procesamiento 3, 815 833 835 876 879 bytes

Ahorró dos bytes gracias a @ZacharyT al eliminar paréntesis innecesarios

void settings(){size(600,600);}int i,w,x,n;float l,d,t,a,f,g,m,R,U;float[][]N,T;float[]S,p;void s(float[][]t){N=new float[t.length+1][2];N[0][0]=N[0][1]=i=0;for(float[]q:t)N[++i]=q;translate(w=300,w);noFill();pushMatrix();f(N,0,-w,w,1,0);popMatrix();f(N,0,-w,w,0,0);}float p(float a,float b){for(a+=PI*4;a>b;)a-=PI*2;return a;}void f(float[][]P,float x,float y,float L,int c,int I){l=2*PI;d=i=0;S=null;for(;i<P.length;i++){float[]p=P[i];g=atan2(y,x);m=atan2(p[1],p[0]);if(p(f=(c*2-1)*(g-m),0)<l&(t=dist(0,0,p[0],p[1]))<=L&I!=i){l=p(f,0);S=new float[]{g,m};d=t;n=i;}}if(S==null)ellipse(0,0,2*(L-d),2*(L-d));else{arc(0,0,L*2,L*2,p(S[c],S[1-c]),S[1-c]);R=cos(a=S[1]);U=sin(a);translate(d*R,d*U);T=new float[P.length][2];for(int i=0;i<T.length;T[i][1]=P[i][1]-d*U,i++)T[i][0]=P[i][0]-d*R;f(T,(L-d)*R,(L-d)*U,L-d,c,n);}}

Ejecute este programa así:

void setup() {
    s(new float[][]{{0,100},{100,100},{-200,100},{-100,-50}});
}

(la función stoma unfloat[][] ). Esto es esencialmente el caso de prueba # 3, pero multiplicado por 100 para ajustarse a la ventana.

Varias cosas a tener en cuenta:

  • el programa NO dibuja polos
  • las imágenes aparecen invertidas porque en el sistema de coordenadas de Processing, el eje y positivo baja
  • Debido a que Processing usa flotadores, los cálculos no son muy precisos, por lo que puede ver esto en las imágenes. Le pregunté al OP si estos errores de punto flotante importan.
  • el tamaño de la ventana es de 600 píxeles por 600 píxeles
  • coordenadas de entrada muy pequeñas alterarán el programa porque la pila pushMatrix()y la popMatrix()operación solo pueden contener 32 matrices.
  • el perro comienza en (0, -300) y la cadena comienza en 300 píxeles de largo
  • las imágenes a continuación se han minimizado para mayor comodidad

Salida de muestra para el caso de prueba anterior.

ingrese la descripción de la imagen aquí

Si desea ver la salida prettificada, agregue esta línea justo después de la translate(w,w);función in s.

background(-1);scale(1,-1);fill(255,0,0);ellipse(0,0,25,25);fill(0);for(float[]q:N)ellipse(q[0],q[1],25,25);

Y esto nos da este resultado:

circulo

Sin golfos f()y explicación

(contiene código de depuración también)

void f(float[][]points, float x, float y, float len, int c, int pindex) {
    print(asd+++")");
    float closest = 2*PI;
    float d=0,t;
    float[]stuff = null;
    int index = 0;
    for(int i=0;i<points.length;i++) {
        if(pindex != i) {
            float[]p = points[i];
            float originAngle = atan2(y, x);
            float tempAngle = atan2(p[1], p[0]);
            //println(x,y,p[0],p[1]);
            float diff = c<1?tempAngle-originAngle:originAngle-tempAngle;
            println("@\t"+i+"; x=\t"+x+"; y=\t"+y+"; tx=\t"+p[0]+"; ty=\t",p[1], diff, originAngle, tempAngle);
            if(p(diff) < closest && (t=dist(0,0,p[0],p[1])) < len) {
                println("+1");
                closest = p(diff);
                stuff = new float[]{originAngle, tempAngle};
                d=t;
                index = i;
            }
        }
    }
    if(stuff == null) {
        ellipse(0,0,2*(len-d),2*(len-d));
        println("mayday");
    } else {
        println("d angles",d,p(stuff[c],stuff[1-c],c), stuff[1-c]);
        //println(points[0]);
        arc(0, 0, len*2, len*2, p(stuff[c],stuff[1-c],c), stuff[1-c]);
        float angle = stuff[1];
        translate(d*cos(angle), d*sin(angle));
        println("Translated", d*cos(angle), d*sin(angle));
        println("angle",angle);
        float[][]temp=new float[points.length][2];
        for(int i=0;i<temp.length;i++){
            temp[i][0]=points[i][0]-d*cos(angle);
            temp[i][1]=points[i][1]-d*sin(angle);
            println(temp[i]);
        }
        println(d*sin(angle));
        pushMatrix();
        println();
        f(temp, (len-d)*cos(angle), (len-d)*sin(angle), (len-d), c, index);
        popMatrix();
        //f(temp, (len-d)*cos(angle), (len-d)*sin(angle), (len-d), 0, index);
    }
}

En pocas palabras, el programa envía dos "buscadores", uno va en sentido antihorario y el otro en sentido horario. Cada uno de estos buscadores encuentra el poste más cercano y dibuja un arco si la cadena es lo suficientemente larga, de lo contrario dibuja un círculo. Una vez que dibuja un arco, envía a otro buscador a ese polo y el proceso continúa. f()contiene el proceso de cada buscador. Una explicación más detallada vendrá tan pronto como juegue más.


¿Necesitas los parens alrededor del último L-d?
Zacharý

@ ZacharyT No sé cómo me perdí eso, gracias.
Kritixi Lithos

5

LOGOTIPO, 305 298 297 293 bytes

Prueba el código en FMSLogo.

Definir una función draw(golfizada comod ) que, dada la entrada como una lista de coordenadas de polo (por ejemplodraw [[0 100] [100 100] [-200 100] [-100 -50][0 0]] , dibujará en la pantalla el resultado).

Requisitos:

  1. Longitud inicial de la cuerda = 300 píxeles. (como 3 píxeles es demasiado pequeño)
  2. [0 0]debe incluirse en la lista de postes. Si el código de depuración (dibujar polos) está activado, entonces[0 0] debe ser el último elemento.
  3. El perro comienza en coordenadas x=0, y=-300 (como en la descripción del problema)

Posibles optimizaciones:

  1. -1 byte si no se requiere que el caso excepcional (el perro choca contra un poste) sea matemáticamente correcto al reemplazarlo >=por>

Código de golf:

to f
op(if ?=pos 360 modulo :m*(180+heading-towards ?)360)
end
to x :m[:1 300]
home
forever[make 2 filter[:1>=u ?](sort :p[(u ?)<u ?2])invoke[pd
arc -:m*f :1
pu
if 360=f[stop]make 1 :1-u ?
lt :m*f
setpos ?]reduce[if f<invoke[f]?2[?][?2]]:2]
end
to d :p
copydef "u "distance
foreach[1 -1]"x
end

Código no oculto ( ;comienza un comentario en línea (utilizado para la explicación) e :inicia un nombre de variable):

to f
    op ifelse ? = pos 360 modulo :m*(180 + heading - towards ?) 360
end

to x
    home
    foreach :poles [pu setpos ? pd circle 5] ; debug code
    make "length 300 ; initial length of rope
    forever [
        make "tmp filter [:length >= distance ?] ; floating point error makes > and >= similar,  ~
            ; but >= is correct mathematically ~
            (sort :poles [(distance ?) < distance ?2])
         ; the last = longest element will be rotated
        invoke [
            pd
            arc -:m*f :length
            pu
            if 360=f [stop]
            make "length :length - distance ?
            lt :m*f
            setpos ?
        ] reduce [
            if f < invoke[f]?2 [?] [?2]
        ] :tmp ; apply to use ? instead of :pos
    ]
end

to draw :poles
    foreach [1 -1] [[m]
        x
    ]
end

1

Python 2 + PIL, 310 bytes

from PIL import Image
from cmath import*
I,_,X,P=Image.new('1',(300,300),'white'),abs,polar,input()
def r(s):
 a,C,l=0,0,3
 while _(a)<99:
  c=C+l*exp(1j*a);I.load()[c.real*30+150,150-c.imag*30]=0
  for p in P+[0]:
   N,E=X(C-c);n,e=X(C-p)
   if n<=N and _(E-e)<.1:l-=_(p-C);C=p
  a+=s
r(.01)
r(-.01)
I.show()

El script lee la lista de puntos de stdin como una lista de números complejos.

printf '[complex(0,0.5)]' | python2 snippet.py

ingrese la descripción de la imagen aquí

printf '[complex(0,1), complex(1,1)]' | python2 snippet.py

ingrese la descripción de la imagen aquí

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.