Un semplice simulatore di DNA


18

Il tuo codice genererà per sempre una rappresentazione molto semplice di arte ASCII del DNA. Ci vorranno due numeri come input in qualsiasi formato desiderato: come un elenco, come argomenti per una funzione, su stdin, ecc.

  • Un intervallo Iin virgola mobile in secondi tra 0,0 e 1,0 (incluso)
  • Un livello di zoom Zcome numero intero compreso tra 1 e 64 (incluso)

Il tuo codice stamperà una riga su stdout o equivalente ogni Isecondo, producendo un output infinito simile a questo (per il livello di zoom 4):

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

Specificamente, la nostra rappresentazione di DNA è una coppia di onde sinusoidali collegati da trattini, uno costituita dai caratteri a, c, g, e t, l'altro dei caratteri A, C, G, e T. Se xè il numero con indice 0 della linea che stiamo attualmente stampando, la posizione in base a 0 del carattere nell'onda minuscola è data da (sin(πx / Z) + 1) * Z, e nell'onda maiuscola è data da (-sin(πx / Z) + 1) * Z, entrambe arrotondate (non pavimentate) al più vicino numero intero. Maggiori dettagli:

  • Nei casi in cui le due onde si sovrappongono, è necessario alternare quale onda si trova nella parte anteriore, iniziando con l'onda maiuscola. (Iniziare con l'onda minuscola ci darebbe una doppia elica che non esiste !)
  • Ignorando il caso, A si accoppia sempre con T e C si accoppia sempre con G, come nel DNA reale. Le coppie stesse dovrebbero essere scelte casualmente con una distribuzione uniforme sulle quattro possibilità. Non importa se la scelta delle coppie è uguale o diversa nelle successive esecuzioni del codice. La qualità statistica delle tue scelte casuali non è un problema fintanto che l'output non ha uno schema evidente e un periodo almeno in miliardi (i PRNG difettosi come RANDU vanno bene).
  • Non è necessario avere spazi finali o riempire tutte le linee fino alla posizione massima delle onde a quel livello di zoom (nell'esempio sopra, nove caratteri.) Il livello di zoom 1 può avere uno spazio di trascinamento aggiuntivo opzionale per motivi matematici.

Poiché il DNA è piccolo, il tuo codice dovrà essere il più breve possibile.

Altri esempi:

Livello di 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
...

Livello di zoom 2:

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

Livello di zoom 1 (notare lo spazio iniziale):

 G
 a
 C
 t
...


9
"Poiché il DNA è piccolo, il tuo codice dovrà essere il più breve possibile." Veramente?
TanMath,

3
@TanMath Hai davvero bisogno di un motivo per Code-Golf? I retroscena sono quasi sempre stupidi come questo, basta andare con esso.
Patrick Roberts,

@PatrickRoberts Lo so, ma stavo solo sottolineando quanto sia sciocca la ragione, molti golfisti di codice lo fanno. Non prenderlo troppo sul serio! ;)
TanMath il

Cosa significa "scelto casualmente"? RANDU è a posto? Che dire di una sequenza ripetitiva più breve?
KSFT

Risposte:


4

Rubino, Rev B 171 161 byte

La correzione dell'output per z = 1 costava 10 byte. È un caso speciale: l'elica ha una larghezza di 3 caratteri se l'hai guardata a 90 gradi, ma se la guardiamo a 0 gradi ha solo 1 carattere di larghezza. zero spazi iniziali su z = 1 non più necessari

Alcuni risparmi eliminando le parentesi e moltiplicando y.abs per 2 prima del troncamento quando si calcola il numero di caratteri necessari.

Infine, ho evitato il include Math(richiesto per sine PI) usando l'aritmetica numerica complessa con potenze del numero i. La parte immaginaria del numero complesso è equivalente a sin x, tranne per il fatto che si ripete con il periodo 4 anziché il periodo 2 * PI. Il salvataggio per questa modifica è stato di 1 o 0 byte.

->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}}

Rubino, Rev. A 165 byte

Questo è molto più lungo del previsto. Ci sono alcune potenziali opportunità di golf da esplorare.

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}}

Commentato nel programma di test

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]

Stai bene! Un problema minore: c'è uno spazio iniziale per il livello di zoom 1. Inoltre, nel tuo programma di test I=gets.to_idovrebbe esserci I=gets.to_f.
Luca

Ops! Hai ragione che Z = 1 è un caso speciale. Non era intenzionale ed è in realtà una contraddizione nelle regole date la matematica che ho fornito. Aggiungerò lo spazio iniziale per Z = 1 per rendere coerente la matematica.
Luca

@Luke in generale non dovrebbe essere cambiato, ma in effetti c'era una contraddizione. Per quanto ne so, neanche le altre risposte l'hanno presa in considerazione. In seguito aggiornerò la mia risposta, poiché sarà più breve in questo modo.
Level River St

@Luke aggiornato, ma significa che ho sia uno spazio iniziale che uno spazio finale su Z = 1. Capisco che sia secondo lo spirito di ciò che vuoi e quindi OK, anche se non è strettamente secondo la frase sugli spazi finali e l'esempio per Z = 1.
Level River St

Di nuovo, sì, va bene. Dispiace per la confusione.
Luca

3

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

#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 versione più lunga che analizza l'input da 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]);}

È un'implementazione generale piuttosto stupida, con alcuni trucchi di stampa inseriti. Ha alcune inclusioni mancanti, usa la sintassi K&R per la funzione e si basa sugli inizializzatori di gamma GCC, quindi questo non è molto standard. Inoltre, la versione della funzione utilizza ancora globali, quindi può essere chiamata una sola volta!

La versione della funzione accetta 2 parametri; attendere (in secondi) e ingrandire. Ecco un chiamante per questo:

#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;
}

Correre come:

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

Abbattersi:

// 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
        );
}

Puoi usare una funzione invece di main (), che ti salverà i byte di strtode atof.
Luca

@Luke Ah cool; Vedrò quanto salva ...
Dave,

3

C, 569 402 361 byte

#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);}}

Questo è abbastanza veloce, quindi sono sicuro che ci sono alcune altre cose che potrei fare per ridurre il mio punteggio, ma sono solo felice di aver compilato questo programma ed eseguirlo correttamente al primo tentativo.

Versione 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);
   }
}

AGGIORNAMENTO: ho adattato il ciclo per stampare tutto in un'unica istruzione di stampa e ho utilizzato il fatto che le variabili sono definite come int di default per radere alcuni byte. UPDATE2: alcuni rinominazioni var e alcuni accorciamenti logici per radere qualche byte in più.


Devi contattare GCC. È Linux ma puoi anche eseguirlo su Windows con Cygwin. Le variabili (se dichiarate all'inizio del programma o come argomenti di funzione) non necessitano di un tipo, si presume che siano int. Lo stesso con le funzioni. E sono abbastanza sicuro che non avrai bisogno di quelle inclusioni.
Level River St

1
Inoltre hai troppi printfs :-D. O 1. usa putchar per stampare un carattere alla volta o 2. risolvi ciò che vuoi stampare e poi stampa tutto con put. 3. capire come usare un singolo printf con una grande espressione complessa in esso. Comunque, +1.
Level River St

Va bene grazie per i suggerimenti! Proverò a fare una singola dichiarazione stampata. Questa è una buona idea e sono sicuro che migliorerebbe il mio punteggio. Lo regolerò quando avrò del tempo oggi. Grazie @steveverrill
Danwakeem,

2

JavaScript (ES6) 241 244 227 222 231 byte

Sembrava interessante - Adoro l'arte ASCII!
Appena iniziato, ancora in procinto di giocare a 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)

--- EDIT: risulta che in realtà non riesco a inserirlo in eval () - altrimenti non può accedere a vari I e Z (quindi aggiunge 9 byte)

- salvato 6 byte grazie a user81655
- salvato 5 byte grazie a Dave

Spiegazione

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

1
È possibile salvare altri 4 byte utilizzando c^=!einvece di c+=a==b(consente di rimuovere il %2segno di spunta in seguito). -m+2Potrebbe anche essere 2-m!
Dave,

@Dave - grazie! Ti dispiacerebbe spiegare cosa fa effettivamente c ^ =! E? Non l'ho mai visto prima :)
Aᴄʜᴇʀᴏɴғᴀɪʟ

È lo stesso di c=c^(e==0); applica un XOR nello stesso modo in cui in precedenza avevi un'aggiunta. Se non hai familiarità con XOR, è un'operazione bitwise: eXclusive OR (wikipedia può spiegarlo correttamente)
Dave,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.