Un simple simulador de ADN

18

Su código va a generar una representación muy simple del ADN en el arte ASCII, para siempre. Tomará dos números como entrada en cualquier formato que desee: como una lista, como argumentos para una función, en stdin, etc.

  • Un intervalo de punto flotante Ien segundos entre 0.0 y 1.0 (inclusive)
  • Un nivel de zoom Zcomo entero de 1 a 64 (inclusive)

Su código imprimirá una línea en stdout o su equivalente cada Isegundo, produciendo una salida infinita que se parece a esto (para el nivel de zoom 4):

    A
 T-----a
G-------c
 G-----c
    g
 t-----A
a-------T
 c-----G
    T
 A-----t
C-------g
...

Específicamente, nuestra representación de ADN es un par de ondas sinusoidales conectados por guiones, uno que consiste en los caracteres a, c, g, y t, el otro de los personajes A, C, G, y T. Si xes el número indexado en 0 de la línea que estamos imprimiendo actualmente, la posición basada en 0 del carácter en la onda minúscula viene dada por (sin(πx / Z) + 1) * Z, y en la onda mayúscula está dada por (-sin(πx / Z) + 1) * Z, ambos redondeados (sin piso) al más cercano entero. Más detalles:

  • En los casos en que las dos ondas se superponen, debe alternar qué onda está en el frente, comenzando con la onda en mayúscula. (¡Comenzar con la onda en minúscula nos daría una doble hélice que no existe !)
  • Ignorando el caso, A siempre se empareja con T y C siempre se empareja con G, como en el ADN real. Los pares mismos deben elegirse al azar con una distribución uniforme sobre las cuatro posibilidades. No importa si la elección de los pares es igual o diferente en ejecuciones sucesivas de su código. La calidad estadística de sus elecciones aleatorias no es un problema siempre que el resultado no tenga un patrón obvio y un período de al menos miles de millones (los PRNG defectuosos como RANDU están bien).
  • No debe tener espacios finales o rellenar cada línea hasta la posición máxima de las ondas en ese nivel de zoom (en el ejemplo anterior, nueve caracteres). El nivel de zoom 1 puede tener un espacio adicional adicional opcional por razones matemáticas.

Debido a que el ADN es pequeño, su código deberá ser lo más corto posible.

Más ejemplos:

Nivel de zoom 8:

        T
     C-----g
  A-----------t
 C-------------g
G---------------c
 T-------------a
  T-----------a
     T-----a
        c
     g-----C
  t-----------A
 g-------------C
a---------------T
...

Nivel de zoom 2:

  A
T---a
  c
g---C
  G
A---t
  c
a---T
...

Nivel de zoom 1 (tenga en cuenta el espacio inicial):

 G
 a
 C
 t
...
Luke
fuente
Relacionado.
Martin Ender
99
"Debido a que el ADN es pequeño, su código deberá ser lo más corto posible". De Verdad?
TanMath
3
@TanMath ¿Realmente necesitas una razón para Code-Golf? Las historias de fondo son casi siempre tontas como esta, solo ve con eso.
Patrick Roberts el
@PatrickRoberts Lo sé, pero solo estaba señalando cuán tonta es la razón, muchos golfistas de código lo hacen. ¡No te lo tomes demasiado en serio! ;)
TanMath
¿Qué significa "elegido al azar"? ¿Está bien RANDU? ¿Qué pasa con una secuencia repetitiva más corta?
KSFT

Respuestas:

4

Ruby, Rev B 171 161 bytes

La fijación de la salida para z = 1 cuesta 10 bytes. Es un caso especial: la hélice tiene realmente 3 caracteres de ancho si la miras a 90 grados, pero si la observamos a 0 grados, solo tiene 1 carácter de ancho. cero espacios iniciales en z = 1 ya no son necesarios

Algunos ahorros al eliminar los corchetes y al multiplicar y.abs por 2 antes del truncamiento al calcular la cantidad de caracteres necesarios.

Finalmente, evité el include Math(requerido para siny PI) usando aritmética de números complejos con potencias del número i. La parte imaginaria del número complejo es equivalente a sen x, excepto que se repite con el período 4 en lugar del período 2 * PI. El ahorro para este cambio fue de 1 o 0 bytes.

->z,i{x=0
loop{y=z*("i".to_c**x).imag
s=(?-*(y.abs*2)).center z*2+1
s[z-y+0.5]='TGAC'[r=rand(4)]
x!=0&&s[z+y+0.5]='actg'[r]
puts s
sleep i
x+=2.0/z
x>3.99&&x=0}}

Ruby, Rev A 165 bytes

Esto es mucho más largo de lo esperado. Hay algunas oportunidades potenciales de golf para explorar.

include Math
->z,i{x=0
loop{y=z*sin(x)
s=('--'*(y.abs+h=0.5)).center(z*2+1)
s[z+h-y]='TGAC'[r=rand(4)]
x!=0&&s[z+h+y]='actg'[r]
puts s
sleep(i)
x+=PI/z
x>6.28&&x=0}}

Comentó en el programa de prueba

include Math
f=->z,i{x=0
  loop{y=z*sin(x)
    s=('--'*(y.abs+h=0.5)).center(z*2+1)  #make a space-padded string of z*2+1 characters, containing enough - signs
    s[z+h-y]='TGAC'[r=rand(4)]            #insert random capital letter, saving index in r
    x!=0&&s[z+h+y]='actg'[r]              #insert small letter. This will normally go on top of the capital as it is done second, but supress for x=0 to make helix
    puts s
    sleep(i)
    x+=PI/z                               #increment x
    x>6.28&&x=0                           #reset x if equal to 2*PI (this proofs against loss of floating point precision, making correct output truly infinite.)
  }
}

Z=gets.to_i
I=gets.to_f
f[Z,I]
Level River St
fuente
¡Luciendo bien! Un problema menor: hay un espacio líder para el nivel de zoom 1. Además, en su programa de prueba I=gets.to_idebería ser I=gets.to_f.
Lucas
Whoops! Tienes razón en que Z = 1 es un caso especial. Eso no fue intencional y en realidad es una contradicción en las reglas dadas las matemáticas que proporcioné. Voy a agregar el espacio inicial para Z = 1 para que las matemáticas sean consistentes.
Lucas
@Luke en general las reglas no deberían cambiarse, pero de hecho había una contradicción. Por lo que puedo decir, las otras respuestas tampoco lo han considerado. Actualizaré mi respuesta más tarde, entonces, ya que será más corto de esa manera.
Level River St el
@Luke actualizó, pero significa que tengo un espacio inicial y un espacio final en Z = 1. Entiendo que está de acuerdo con el espíritu de lo que desea y, por lo tanto, está bien, aunque no está estrictamente de acuerdo con la redacción sobre los espacios finales y el ejemplo de Z = 1.
Level River St el
Vaya otra vez, sí, está bien. Perdón por la confusion.
Lucas
3

C, 294 289 285 283 281 270 265 237 218 bytes

#include<math.h>
o,i,p,r;char*c="acgtTGCA",d[256]={[0 ...254]='-'};P(w,z)float w;{for(;;poll(0,0,r=w*1e3))p=fabs(sinf(M_PI*i++/z))*z+.5,r=rand()&3,o^=4*!p,printf(p?"%*c%s%c\n":"%*c\n",z-p+1,c[r+o],d+256-p*2,c[r+4-o]);}

O la versión más larga que analiza la entrada de main:

#include<stdlib.h>
#include<math.h>
o,i,p,r;char*c="acgtTGCA",d[256]={[0 ...254]='-'};main(n,v)char**v;{for(;n=strtod(v[2],0);poll(0,0,n=atof(v[1])*1e3))p=fabs(sinf(M_PI*i++/n))*n+.5,r=rand()&3,o^=4*!p,printf(p?"%*c%s%c\n":"%*c\n",n-p+1,c[r+o],d+256-p*2,c[r+4-o]);}

Es una implementación general bastante tonta, con algunos trucos de printf. Le faltan algunas funciones, utiliza la sintaxis K&R para la función y se basa en los inicializadores de rango de GCC, por lo que esto no es muy estándar. Además, la versión de la función todavía usa globales, por lo que solo se puede llamar una vez.

La versión de la función toma 2 parámetros; esperar (en segundos) y hacer zoom. Aquí hay una llamada para ello:

#include <stdlib.h>
int main( int argc, const char *const *argv ) {
    if( argc != 3 ) {
        printf( "Usage: %s <delay> <zoom>\n", argv[0] );
        return EXIT_FAILURE;
    }
    const float delay = atof( argv[1] );
    const int zoom = strtod( argv[2], 0 );
    if( delay < 0 || zoom <= 0 ) {
        printf( "Invalid input.\nUsage: %s <delay> <zoom>\n", argv[0] );
        return EXIT_FAILURE;
    }
    P( delay, zoom );
    return EXIT_SUCCESS;
}

Correr como:

./dna <delay> <zoom>
./dna 0.5 8

Descompostura:

// Globals initialise to 0
o,                                 // Ordering (upper/lower first)
i,                                 // Current iteration
p,                                 // Current indent
r;                                 // Current random value
char*c="acgtTGCA",                 // The valid letters
    d[256]={[0 ...254]='-'};       // Line of dashes (for printing)
main(n,v)char**v;{                 // K&R-style main definition (saves 2 bytes)
    // n will be used for Zoom, random number & casting delay
    for(
        ;n=strtod(v[2],0);         // Store zoom
        poll(0,0,n=atof(v[1])*1e3) // After each loop, use poll to delay
                                   // (Use variable to cast delay to int)
    )
        p=fabs(sinf(M_PI*i++/n))*n+.5,   // Calculate separation / 2
        r=rand()&3,                      // Pick random number [0-4)
        o^=4*!p,                         // Reverse order if crossing
        printf(p                         // Print... if not crossing:
                ?"%*c%s%c\n"             //  indent+character+dashes+character
                :"%*c\n",                //  Else indent+character
                n-p+1,                   // Width of indent + 1 for char
                c[r+o],                  // First character
                d+256-p*2,               // Dashes
                c[r+4-o]                 // Second character
        );
}
Dave
fuente
Puede usar una función en lugar de main (), que le ahorrará los bytes de strtody atof.
Lucas
@Luke Ah genial; Veré cuánto ahorra ...
Dave
3

C, 569 402 361 bytes

#include<stdlib.h>
u,l,r,m,n,Z,I,y=0,x=0;main(c,char**v){Z = atoi(v[1]);I=atof(v[2])*1000000;srand(time(0));char *a="ACGTtgca";while(1){r=rand()%4;usleep(I);double s=sin(3.14*x++/Z);u=floor(((-1*s+1)*Z)+0.5);l=floor(((s+1)*Z)+0.5);m=(u<l)?u:l;n=u<l?l:u;char z[n+1];memset(z,' ',n);z[l]=a[r+4];z[u]=a[r];for(y=m+1;y<n;y++)z[y]='-';z[n+1]='\0';printf("%s\n",z);}}

Agité esto bastante rápido, así que estoy seguro de que hay otras cosas que podría hacer para disminuir mi puntaje, pero estoy feliz de que este programa se compile y se ejecute correctamente en el primer intento.

Versión de golf:

#include<stdio.h>
#include<math.h>
#include<unistd.h>
#include<time.h>
#include<stdlib.h>
u,l,r,m,n,Z,I,y=0,x=0;
main(c,char**v){
   Z = atoi(v[1]);
   I=atof(v[2])*1000000;
   srand(time(0));
   char *a="ACGTtgca";
   while(1){
      r=rand()%4;
      usleep(I);
      double s=sin(3.14*x++/Z);
      u=floor(((-1*s+1)*Z)+0.5);
      l=floor(((s+1)*Z)+0.5);
      m=(u<l)?u:l;
      n=u<l?l:u;
      char z[n+1];
      memset(z,' ',n);
      z[l]=a[r+4];
      z[u]=a[r];
      for(y=m+1;y<n;y++)z[y]='-';
      z[n+1]='\0';
      printf("%s\n",z);
   }
}

ACTUALIZACIÓN: Ajusté el bucle para imprimir todo en una declaración de impresión y usé el hecho de que las variables se definen como int por defecto para eliminar algunos bytes. ACTUALIZACIÓN2: algunos cambios de nombre de var y algunos acortamiento lógico para reducir algunos bytes más.

Danwakeem
fuente
Necesitas hacerte con GCC. Es Linux, pero también puede ejecutarlo en Windows con Cygwin. Las variables (si se declaran al comienzo del programa o como argumentos de función) no necesitan un tipo, se supone que son int. Lo mismo con las funciones. Y estoy bastante seguro de que no necesitará esos incluye.
Level River St el
1
También tienes demasiados printfs :-D. Ya sea 1. use putchar para imprimir un carácter a la vez o 2. calcule lo que desea imprimir y luego imprímalo todo con put. 3. descubra cómo usar un solo printf con una gran expresión compleja. De todos modos, +1.
Level River St el
Bien gracias por las sugerencias! Intentaré hacer una sola declaración impresa. Esa es una buena idea y estoy seguro de que mejoraría mi puntaje. Regolfé esto cuando tenga algo de tiempo hoy. Gracias @steveverrill
Danwakeem
2

JavaScript (ES6) 241 244 227 222 231 bytes

Esto parecía interesante: ¡me encanta el arte ASCII!
Acabo de empezar, todavía en el proceso de jugar al golf ...

(I,Z)=>{c=i=0,setInterval(_=>{with(Math)m=sin(PI*i++/Z),a=round(++m*Z),b=round((2-m)*Z),r=random()*4|0,D="TGAC"[r],d="actg"[r],e=a-b,c^=!e,p=" ".repeat(a>b?b:a)+(c?D:d)+"-".repeat(e?abs(e)-1:0)+(e?a>b?d:D:""),console.log(p)},I*1e3)

--- EDITAR: resulta que en realidad no puedo ponerlo en eval (); de lo contrario, no puede acceder a los valores I y Z (por lo que agrega 9 bytes)

- guardado 6 bytes gracias a user81655
- guardado 5 bytes gracias a Dave

Explicación

(I,Z)=>{
  c=i=0,                                // clear vars
  setInterval(_=>{                      // repeat

    with(Math)                         
      m=sin(PI*i++ / Z),                // calculate waves
      a=round(++m * Z),
      b=round((2-m) * Z),
      r=random()*4|0,                   // get random amino-acids
      D="TGAC"[r],
      d="actg"[r],
      e=a-b,
      c^=!e,                            // alternate upper/lowercase
      p=                                // prepare output
        " ".repeat(
          a>b ? b : a
        )+(
          c ? D : d
        )+

        "-".repeat(
          e ? abs(e)-1 : 0
        )+(
          e ? a>b ? d : D : ""
        ),

      console.log(p)                    // return output
  },I*1e3)                              // repeat for every 'I' seconds
}
Aᴄʜᴇʀᴏɴғᴀɪʟ
fuente
1
Puede guardar otros 4 bytes utilizando en c^=!elugar de c+=a==b(le permite eliminar la %2verificación más adelante). También -m+2podría ser 2-m!
Dave
@Dave - gracias! ¿Te importaría explicar qué hace realmente c ^ =! E? Nunca he visto eso antes :)
Aᴄʜᴇʀᴏɴғᴀɪʟ
Es lo mismo que c=c^(e==0); aplica un XOR de la misma manera que anteriormente tenía una adición. Si no está familiarizado con XOR, es una operación bit a bit: eXclusive OR (wikipedia puede explicarlo correctamente)
Dave