Простий симулятор ДНК


18

Ваш код назавжди створить дуже просте зображення ASCII-мистецтва ДНК. Це буде приймати два числа як вхід у будь-якому бажаному форматі: як список, як аргументи до функції, на stdin тощо.

  • Інтервал Iз плаваючою комою в секундах між 0,0 та 1,0 (включно)
  • Рівень масштабування Zяк ціле число від 1 до 64 (включно)

Ваш код буде друкувати один рядок у stdout або його еквівалент кожні Iсекунди, створюючи нескінченний вихід, який виглядає приблизно так (для масштабу 4 рівня):

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

В Зокрема, наше уявлення ДНК є парою синусоид , з'єднаних дефісом, один із символів a, c, gі t, з іншого із символів A, C, Gі T. Якщо xце номер, який індексується 0, у рядку, який ми зараз друкуємо, то позиція символу на малій хвилі, що базується на 0, задається (sin(πx / Z) + 1) * Z, а у верхній великій хвилі задається (-sin(πx / Z) + 1) * Zобома закругленими (не закресленими) до найближчих ціле число. Детальніше:

  • У випадках, коли дві хвилі перекриваються, потрібно чергувати, яка хвиля знаходиться спереду, починаючи з великої хвилі. (Починаючи з малої хвилі, ми дамо нам подвійну спіраль, якої не існує !)
  • Ігноруючи випадок, A завжди пара з T і C завжди з G, як у реальній ДНК. Самі пари повинні бути обрані випадковим чином з рівномірним розподілом по чотирьох можливостям. Не має значення, чи вибір пар однаковий чи різний у послідовних прогонах вашого коду. Статистична якість ваших випадкових виборів не є проблемою, якщо вихід не має очевидного шаблону та періоду хоча б у мільярдах (хибні PRNG, такі як RANDU , чудово.)
  • У вас не повинно бути пробілів або прокладати кожен рядок до максимального положення хвиль на цьому рівні масштабування (у прикладі вище, дев'ять символів.) Рівень масштабування 1 може мати один необов'язковий додатковий простір з математичних причин.

Оскільки ДНК невелика, ваш код повинен бути якомога коротшим.

Більше прикладів:

Рівень масштабу 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
...

Рівень масштабу 2:

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

Збільшити рівень 1 (зверніть увагу на провідний пробіл):

 G
 a
 C
 t
...


9
"Оскільки ДНК невелика, ваш код повинен бути якомога коротшим". Дійсно?
TanMath

3
@TanMath Вам справді потрібна причина для Code-Golf? Запасні матеріали майже завжди такі нерозумні, просто йдіть з цим.
Патрік Робертс

@PatrickRoberts Я знаю, але я просто вказував, наскільки дурною причиною є багато гольфістів з кодом. Не сприймайте це занадто серйозно! ;)
TanMath

Що означає "випадково вибраний"? З RANDU все в порядку? Як щодо коротшої повторюваної послідовності?
KSFT

Відповіді:


4

Рубі, об. В 171 161 байт

Закріплення виводу для z = 1 коштувало 10 байт. Це особливий випадок: спіраль насправді шириною 3 символи, якщо ви дивилися на неї на 90 градусів, але, як ми дивимось її на 0 градусів, вона виглядає лише на 1 символ. нульові провідні місця на z = 1 більше не потрібні

Деяка економія за рахунок усунення дужок та множення y.abs на 2 перед усіченням під час обчислення кількості потрібних символів.

Нарешті, я уникнув цього include Math (необхідного для sinта PI), використовуючи складну арифметику чисел із потужностями числа i. Уявна частина комплексного числа еквівалентна sin x, за винятком того, що воно повторюється з періодом 4 замість періоду 2 * PI. Збереження для цієї зміни становило 1 або 0 байт.

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

Рубі, об. A 165 байт

Це набагато довше, ніж очікувалося. Є кілька потенційних можливостей для гольфу, які слід вивчити.

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

Прокоментував тестову програму

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]

Хороший вигляд! Одне незначне питання: є провідний простір для рівня масштабу 1. Також у вашій програмі тестування I=gets.to_iмає бути I=gets.to_f.
Лука

Ого! Ви праві, що Z = 1 - це особливий випадок. Це не було навмисно, а насправді суперечить правилам, наданим математикою, яку я надав. Я збираюся додати провідний простір для Z = 1, щоб зробити математику послідовною.
Лука

@Luke в загальних правилах не слід міняти, але насправді було протиріччя. Наскільки я можу сказати, інші відповіді теж не розглядали. Я згодом оновлю свою відповідь, оскільки так буде коротше.
Рівень річки Св.

@Luke оновлено, але це означає, що у мене є і провідний простір, і пробіл на Z = 1. Я розумію, що це відповідає духу того, що ви хочете, і тому добре, хоча це не суворо відповідно до фрази про пробіли проміжків та прикладу для Z = 1.
Рівень річки Св.

Ще раз, так, це добре. Вибачте за непорозуміння.
Лука

3

C, 294 289 285 283 281 270 265 237 218 байт

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

Або довша версія, яка аналізує вхід від основної:

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

Це досить тупа загальна реалізація, з деякими хитрощами printf. У неї є деякі недоліки, використовується синтаксис K&R для функції та покладається на ініціалізатори діапазону GCC, тому це не дуже стандартно. Також версія функції все ще використовує глобали, тому її можна назвати лише один раз!

Версія функції приймає 2 параметри; зачекати (в секундах) і збільшити. Ось вам, хто телефонує:

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

Виконати як:

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

Зламатися:

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

Вам дозволяється використовувати функцію замість main (), яка збереже вам байти strtodта atof.
Лука

@Luke Ах круто; Я побачу, наскільки це економить ...
Дейв

3

C, 569 402 361 байт

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

Зітхнув це досить швидко, тому я впевнений, що я можу зробити деякі інші речі, щоб зменшити свою оцінку, але я щасливий, що отримав цю програму для компіляції та роботи належним чином з першої спроби.

Версія для де-гольфу:

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

ОНОВЛЕННЯ: Я налаштував цикл, щоб надрукувати все в одному операторі друку, і використовував той факт, що змінні визначаються як int за замовчуванням, щоб голити деякі байти. UPDATE2: Дещо перейменування вар та деяке логічне скорочення для гоління ще кількох байтів.


Вам потрібно здобути GCC. Це Linux, але ви також можете запустити його на Windows за допомогою Cygwin. Змінні (якщо вони оголошені на початку програми або як аргументи функції) не потребують типу, вони вважаються int. Те саме з функціями. І я впевнений, що вам не знадобляться.
Рівень Св. Сен

1
Також у вас занадто багато printfs :-D. Або 1. використовуйте putchar, щоб надрукувати один символ за один раз, або 2. опрацюйте те, що ви хочете надрукувати, а потім надрукуйте все разом із вкладками. 3. розробити, як використовувати один printf з великим складним виразом у ньому. У будь-якому разі +1.
Рівень Св. Св.

Добре дякую за пропозиції! Я спробую зробити одну заяву про друк. Це гарна ідея, і я впевнений, що це покращить міркування. Я перероблю це, коли в мене є час. Дякуємо @steveverrill
Danwakeem

2

JavaScript (ES6) 241 244 227 222 231 байт

Це виглядало цікаво - я люблю мистецтво ASCII!
Щойно розпочато, все ще в процесі гольфу ...

(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: виявляється, я фактично не можу поставити його в eval () - інакше він не може отримати доступ до vars I і Z (тому додає 9 байт)

- збережено 6 байт завдяки користувачеві81655
- збережено 5 байт завдяки Дейву

Пояснення

(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
Ви можете зберегти ще 4 байти, використовуючи c^=!eзамість c+=a==b(дозволяє видалити %2чек пізніше). Також -m+2може бути 2-m!
Дейв

@Dave - спасибі! Ви не проти пояснити, що насправді робить c ^ ​​=! E? Я ніколи цього не бачив :)
квітня 1616

Це те саме, що c=c^(e==0); він застосовує XOR так само, як раніше було додано. Якщо ви не знайомі з XOR, це бітова операція: eXclusive АБО (wikipedia може це правильно пояснити)
Дейв
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.