19
\$\begingroup\$

Your code is going to generate a very simple ASCII-art representation of DNA, forever. It will take two numbers as input in any format you want: as a list, as arguments to a function, on stdin, etc.

  • A floating-point interval I in seconds between 0.0 and 1.0 (inclusive)
  • A zoom level Z as an integer from 1 to 64 (inclusive)

Your code will print one line to stdout or its equivalent every I seconds, producing an infinite output that looks something like this (for zoom level 4):

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

Specifically, our representation of DNA is a pair of sine waves connected by hyphens, one consisting of the characters a, c, g, and t, the other of the characters A, C, G, and T. If x is the 0-indexed number of the line we're currently printing, the 0-based position of the character in the lowercase wave is given by (sin(πx / Z) + 1) * Z, and in the uppercase wave is given by (-sin(πx / Z) + 1) * Z, both rounded (not floored) to the nearest integer. Further details:

  • In cases where the two waves overlap, you need to alternate which wave is in the front, starting with the uppercase wave. (Starting with the lowercase wave would give us a double helix that does not exist!)
  • Ignoring case, A always pairs with T and C always pairs with G, as in real DNA. The pairs themselves should be randomly chosen with a uniform distribution over the four possibilities. It does not matter if the choice of pairs is the same or different on successive runs of your code. The statistical quality of your random choices is not an issue as long as the output has no obvious pattern and a period at least in the billions (flawed PRNGs like RANDU are fine.)
  • You must either have no trailing spaces or pad every line to the maximum position of the waves at that zoom level (in the example above, nine characters.) Zoom level 1 may have one optional additional trailing space for mathematical reasons.

Because DNA is small, your code will need to be as short as possible.

More examples:

Zoom level 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
...

Zoom level 2:

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

Zoom level 1 (note the leading space):

 G
 a
 C
 t
...
\$\endgroup\$
7
  • \$\begingroup\$ Related. \$\endgroup\$ Jan 4, 2016 at 16:24
  • 9
    \$\begingroup\$ "Because DNA is small, your code will need to be as short as possible." Really? \$\endgroup\$
    – TanMath
    Jan 4, 2016 at 19:06
  • 3
    \$\begingroup\$ @TanMath Do you really need a reason to Code-Golf? The backstories are almost always silly like this, just go with it. \$\endgroup\$ Jan 4, 2016 at 19:47
  • \$\begingroup\$ @PatrickRoberts I know, but I was just pointing out how silly the reason is, a many code-golfers do. Don't take it too seriously! ;) \$\endgroup\$
    – TanMath
    Jan 4, 2016 at 19:49
  • \$\begingroup\$ What does "randomly chosen" mean? Is RANDU okay? What about a shorter repeating sequence? \$\endgroup\$
    – KSFT
    Jan 4, 2016 at 21:42

4 Answers 4

4
\$\begingroup\$

Ruby, Rev B 171 161 bytes

Fixing the output for z=1 cost 10 bytes. It's a special case: the helix is really 3 characters wide if you looked at it at 90 degrees, but as we look at it at 0 degrees it looks only 1 character wide. zero leading spaces on z=1 no longer required

Some savings by eliminating brackets and by multiplying y.abs by 2 before truncation when calculating the number of - characters needed.

Finally, I avoided the include Math (required for sin and PI) by using complex number arithmetic with powers of the number i. The imaginary part of the complex number is equivalent to sin x, except that it repeats with period 4 instead of period 2*PI. Saving for this change was either 1 or 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

This is way longer than expected. There are a few potential golfing opportunities to be explored.

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

Commented in test program

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]
\$\endgroup\$
6
  • \$\begingroup\$ Looking good! One minor issue: there's a leading space for zoom level 1. Also, in your test program I=gets.to_i should be I=gets.to_f. \$\endgroup\$
    – Luke
    Jan 4, 2016 at 23:51
  • \$\begingroup\$ Whoops! You're right that Z=1 is a special case. That wasn't intentional and is actually a contradiction in the rules given the math I provided. I'm going to add the leading space for Z=1 to make the math consistent. \$\endgroup\$
    – Luke
    Jan 5, 2016 at 13:51
  • \$\begingroup\$ @Luke in general rules shouldn't be changed, but indeed there was a contradiction. As far as I can tell, the other answers haven't considered it either. I'll update my answer later, then, as it will be shorter that way. \$\endgroup\$ Jan 5, 2016 at 14:13
  • \$\begingroup\$ @Luke updated, but it means I have both a leading space and a trailing space on Z=1. I understand that's according to the spirit of what you want and therefore OK, though its not strictly according to the phrasing about trailing spaces and the example for Z=1. \$\endgroup\$ Jan 5, 2016 at 21:18
  • \$\begingroup\$ Whoops again, yes that's fine. Sorry for the confusion. \$\endgroup\$
    – Luke
    Jan 5, 2016 at 23:39
3
\$\begingroup\$

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

Or the longer version which parses input from 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]);}

It's a pretty dumb overall implementation, with some printf tricks thrown in. It has some missing includes, uses K&R syntax for the function, and relies on GCC's range initialisers, so this isn't very standard. Also the function version still uses globals, so it can only be called once!

The function version takes 2 parameters; wait (in seconds) and zoom. Here's a caller for it:

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

Run as:

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

Breakdown:

// 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
        );
}
\$\endgroup\$
2
  • \$\begingroup\$ You're allowed to use a function instead of main(), which will save you the bytes of strtod and atof. \$\endgroup\$
    – Luke
    Jan 5, 2016 at 14:56
  • \$\begingroup\$ @Luke Ah cool; I'll see how much that saves... \$\endgroup\$
    – Dave
    Jan 5, 2016 at 15:05
3
\$\begingroup\$

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

Whipped this up pretty quick so I am sure there are some other things I could do to decrease my score but I am just happy I got this program to compile and run properly on the first attempt.

De-golf version:

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

UPDATE: I adjusted the loop to print everything in one print statement and used the fact that variables are defined as int by default to shave some bytes. UPDATE2: Some var renaming and some logic shortening to shave a few more bytes.

\$\endgroup\$
3
  • \$\begingroup\$ You need to get hold of GCC. It's Linux but you can also run it on windows with Cygwin. Variables (if declared at the beginning of the program or as function arguments) don't need a type, they are assumed to be int. The same with functions. And I'm pretty sure you won't need those includes. \$\endgroup\$ Jan 5, 2016 at 10:21
  • 1
    \$\begingroup\$ Also you have too many printfs :-D. Either 1. use putchar to print one character at a time or 2. work out what you want to print and then print it all with puts. 3. work out how to use a single printf with a big complex expression in it. Anyway, +1. \$\endgroup\$ Jan 5, 2016 at 10:25
  • \$\begingroup\$ Okay thanks for the suggestions! I will try and make a single print statement. That is a good idea and I am sure it would improve my score. I will regolf this when I have some time today. Thanks @steveverrill \$\endgroup\$
    – Danwakeem
    Jan 5, 2016 at 14:56
2
\$\begingroup\$

JavaScript (ES6) 241 244 227 222 231 bytes

This looked interesting - I love ASCII art!
Just started, still in the process of golfing it...

(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: turns out I can't actually put it in eval() - otherwise it can't access vars I and Z (so adds 9 bytes)

- saved 6 bytes thanks to user81655
- saved 5 bytes thanks to Dave

Explanation

(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
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ You could save another 4 bytes by using c^=!e instead of c+=a==b (lets you remove the %2 check later). Also -m+2 could be 2-m! \$\endgroup\$
    – Dave
    Jan 6, 2016 at 12:41
  • \$\begingroup\$ @Dave - thanks! Would you mind explaining what c^=!e actually does? I've never seen that before :) \$\endgroup\$ Jan 6, 2016 at 22:49
  • \$\begingroup\$ It's the same as c=c^(e==0); it applies an XOR in the same way you previously had an addition. If you're unfamiliar with XOR, it's a bitwise operation: eXclusive OR (wikipedia can explain it properly) \$\endgroup\$
    – Dave
    Jan 6, 2016 at 23:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.