Найшвидший сортування в BrainF ***


15

Після впровадження QuickSort у BrainF *** , я зрозумів, що це, мабуть, не так швидко. Операції, які є O (1) у звичайних мовах (наприклад, індексація масиву), у BF значно довші. Більшість правил для ефективного сортування можуть бути викинуті у вікно, коли ви кодуєте в таріті Тюрінга.

Тож тут є завдання впровадити "Найшвидший BrainF *** сортування звичайних коли-небудь". Я проведу всі записи за допомогою інтерпретатора нижче. Інтепретер використовує 16K стрічку непідписаних символів. І стрічка, і комірки загортаються, коли просуваються / збільшуються за межі меж. Читання EOF ставить 0 у поточній комірці. Виміряний час включає як час для розбору вихідного файлу, так і час для обробки всіх вхідних файлів. Виграє найшвидший код.

Тестовим вектором буде набір файлів Ascii, призначених для тестування сортування крайових справ, у тому числі

  • Вже відсортований список: "замовлено"

    &#33;"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    
  • Зворотний відсортований список: "зворотний"

    ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!
    
  • Файл, що складається з безлічі копій кількох унікальних значень: "onlynine"

    ibbkninbkrauickabcufrfckbfikfbbakninfaafafbikuccbariauaibiraacbfkfnbbibknkbfankbbunfruarrnrrrbrniaanfbruiicbuiniakuuiubbknanncbuanbcbcfifuiffbcbckikkfcufkkbbakankffikkkbnfnbncbacbfnaauurfrncuckkrfnufkribnfbcfbkbcrkriukncfrcnuirccbbcuaaifiannarcrnfrbarbiuk
    
  • Повністю випадковий файл ascii: "random"

    'fQ`0R0gssT)70O>tP[2{9' 0.HMyTjW7-!SyJQ3]gsccR'UDrnOEK~ca 'KnqrgA3i4dRR8g.'JbjR;D67sVOPllHe,&VG"HDY_'Wi"ra?n.5nWrQ6Mac;&}~T_AepeUk{:Fwl%0`FI8#h]J/Cty-;qluRwk|S U$^|mI|D0\^- csLp~`VM;cPgIT\m\(jOdRQu#a,aGI?TeyY^*"][E-/S"KdWEQ,P<)$:e[_.`V0:fpI zL"GMhao$C4?*x
    
  • Випадковий файл у діапазоні 1..255: "весь діапазон"

    öè—@œ™S±ü¼ÓuǯŠf΀n‚ZÊ,ˆÖÄCítÚDý^öhfF†¬I÷xxÖ÷GààuÈ©ÈÑdàu.y×€ôã…ìcÑ–:*‰˜IP¥©9Ä¢¬]Š\3*\®ªZP!YFõ®ÊÖžáîÓ¹PŸ—wNì/S=Ìœ'g°Ì²¬½ÕQ¹ÀpbWÓ³
    »y  »ïløó„9k–ƒ~ÕfnšÂt|Srvì^%ÛÀâû¯WWDs‰sç2e£+PÆ@½ã”^$f˜¦Kí•òâ¨÷ žøÇÖ¼$NƒRMÉE‹G´QO¨©l¬k¦Ó 
    

Кожен вхідний файл має максимум 255 байт.

Ось перекладач. Він написаний для Windows в консольному режимі, але він повинен бути легким для порту: просто замініть його read_time()та sysTime_to_ms()використовуйте для нього певні еквіваленти.
Використання: bftime program.bf infile1 [infile2 ...]

#include <windows.h>
#include <stdio.h>

#define MS_PER_SEC  1000.0f
#define MAXSIZE  (0x4000)
#define MAXMASK  (MAXSIZE-1)

typedef  __int64 sysTime_t;
typedef unsigned char Uint8;
typedef unsigned short Uint16;

typedef struct instruction_t {
   Uint8 inst;
   Uint16 pair;
} Instruction;

Instruction prog[MAXSIZE] = {0};
Uint8 data[MAXSIZE] = {0};
const Uint8 FEND = EOF;

sysTime_t read_time() {
    __int64 counts;
    QueryPerformanceCounter((LARGE_INTEGER*)&counts);
    return counts;
}

float sysTime_to_ms(sysTime_t timeIn) {
    __int64 countsPerSec;
    QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
    return (float)timeIn * MS_PER_SEC / (float)countsPerSec;
}

int main(int argc, char* argv[])
{
   FILE* fp;
   Uint8 c;
   Uint16 i = 0;
   Uint16 stack = 0;
   sysTime_t start_time;
   sysTime_t elapsed=0,delta;

   if (argc<3) exit(printf("Error: Not Enough Arguments\n"));
   fp = fopen(argv[1],"r");
   if (!fp) exit(printf("Error: Can't Open program File %s\n",argv[1]));

   start_time=read_time();
   while (FEND != (c = fgetc(fp)) && i <MAXSIZE) {
      switch (c)  {
      case '+': case '-': case ',': case '.': case '>': case '<':
         prog[++i].inst = c;
         break;
      case '[': 
         prog[++i].inst = c;
         prog[i].pair=stack;
         stack = i;
         break;
      case ']': 
         if (!stack) exit(printf("Unbalanced ']' at %d\n",i));
         prog[++i].inst = c;
         prog[i].pair=stack;
         stack = prog[stack].pair;
         prog[prog[i].pair].pair=i;
         break;
      }
   }
   if (stack) exit(printf("Unbalanced '[' at %d\n",stack));
   elapsed = delta = read_time()-start_time;
   printf("Parse Time: %f ms\n", sysTime_to_ms(delta));

   for (stack=2;stack<argc;stack++) {
      Instruction *ip = prog;
      fp = fopen(argv[stack],"r");
      if (!fp) exit(printf("Can't Open input File %s\n",argv[stack]));
      printf("Processing %s:\n", argv[stack]);
      memset(data,i=0,sizeof(data));

      start_time=read_time();
      //Run the program
      while (delta) {
         switch ((++ip)->inst) {
         case '+': data[i]++; break;
         case '-': data[i]--; break;
         case ',': c=getc(fp);data[i]=(FEND==c)?0:c; break;
         case '.': putchar(data[i]);  break;
         case '>': i=(i+1)&MAXMASK;   break;
         case '<': i=(i-1)&MAXMASK;   break;
         case '[': if (!data[i]) ip = prog+ip->pair; break;
         case ']': if (data[i])  ip = prog+ip->pair;  break;
         case 0: delta=0; break;
         }
      }
      delta = read_time()-start_time;
      elapsed+=delta;
      printf("\nProcessing Time: %f ms\n", sysTime_to_ms(delta));
   }
   printf("\nTotal Time for %d files: %f ms\n", argc-2, sysTime_to_ms(elapsed));
}

Результати поки що

Ось середній час 5 запусків повного набору векторів:

 Author    Program      Average Time    Best Set          Worst Set
 AShelly   Quicksort    3224.4 ms       reverse (158.6)   onlynine (1622.4) 
 K.Randall Counting     3162.9 ms       reverse (320.6)   onlynine  (920.1)
 AShelly   Coinsort      517.6 ms       reverse  (54.0)   onlynine  (178.5) 
 K.Randall CountingV2    267.8 ms       reverse  (41.6)   random     (70.5)
 AShelly   Strandsort    242.3 ms       reverse  (35.2)   random     (81.0)

Який діапазон вхідних елементів?
Кіт Рендалл

Це діапазон комірок, крім 0: 1-255.
AShelly

Ви повинні переробити моє, я зробив це трохи швидше.
Кіт Рендалл

Схоже, це в 2 рази швидше, ніж моє останнє - я зроблю офіційні терміни, коли повернусь до машини, яку я використовував для інших.
AShelly

Відповіді:


9

Ось такий сорт, який принаймні в 6 разів швидший, ніж мій квакісорт. Це алгоритм, який мало б мало сенсу в традиційній мові, оскільки це O (N * m), де m - максимальне вхідне значення. Після збору вхідного сигналу він проходить через масив, рахуючи комірки> 0, а потім декрементуючи кожну. Потім він додає 1 до перших countкомірок у векторному результаті. Він повторює проходи, поки кількість не дорівнює 0.
BF:

Get Input
>,[>>+>,]   
Count values GT 0 and decrement each
<[<[<<<+>>>-]<[-<<+>>>]>[<]<<]
While count: add 1 to results
<[[[<<+>>-]<+<-]
Seek back to end of input
>[>>]>>>[>>>]
Repeat counting step
<<<[<[<<<+>>>-]<[-<<+>>>]>[<]<<]<]
Seek to far end of results and print in reverse order 
<[<<]>>[.>>]

C еквівалентний алгоритм:

 uchar A[MAX]={0}; uchar R[MAX]={0}; int count,i,n=0;
 while (A[n++]=getchar()) ;
 do { 
   count = 0;
   for (i=0; i<n; i++) count += (A[i]) ? (A[i]-->0) : 0;
   for (i=0; i<count; i++) R[i]++; 
 } while (count>0);
 for (i=0; R[i]; i++) ;
 for (i--; i>=0; i--) putchar(R[i]);

Ось такий, що в 2 рази швидше. Він грунтується вільно на "сорті спагетті" : він відкладає рядок розміром 1s, скільки кожного введення. Значення в кожній комірці представляє кількість ниток, принаймні, що довге. (Так стає [3,2,1,2] |4|0|3|0|1|0|0|). Потім він починає «вимірювати» пасма і виводить довжину щоразу, коли знаходить кінець однієї.

>,[ [-[>>+<<-]>+>] <[<<]>,]   build strand of 1s for each input
+>[>+<-]>[                    while there are strands
  >[>+<<->-]                  do any strands end here?
  <[<<.>>-]                   print length of all that do  
  <<[>>+<<-]>>+>>]            shift right 1; inc length 

Сире:

>,[[-[>>+<<-]>+>]<[<<]>,]+>[>+<-]>[>[>+<<->-]<[<<.>>-]<<[>>+<<-]>>+>>]

Не стукай підрахунок сорту! Це мій улюблений сорт, завдяки величезній виграші, яку я отримав від цього одного разу: якщо m, як відомо, невеликий, ви можете отримати величезні прискорення в іншому випадку "швидких" алгоритмів. Аналогічно, сортування міхурів б'є швидкісні спори за здебільшого відсортованими даними. Жоден ___ алгоритм не найкращий для кожного контексту.
кабінка

Я не думаю, що це саме такий підрахунок. Ваш коментар змусив мене ще кілька досліджень. Я думаю, це більше схоже на сорт бісеру . Але я навіть не впевнений, що це правильно.
AShelly

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

4
Фізична аналогія полягає в тому, що у вас є N пачок монет різного розміру. Виділіть місце для ще N стеків. Ви знімаєте одну монету у верхній частині кожної стопки, де є монети, а потім додаєте по 1 до кожної стопки в новому наборі справа наліво, поки ваша рука не буде порожньою. Повторюйте, поки всі оригінальні стеки не будуть порожніми. Тепер новий набір відсортований за зростанням зліва направо.
AShelly

7
>>+>,[->+>,]<[<[<<]<[.<[<<]<]>>[+>->]<<]

Я не пам'ятаю, чиєю ідеєю був цей алгоритм. Може, Бертрама Фельгенгауера? Це відбулося з обговорень навколо конкурсу Brainfuck Golf №2 понад десятиліття тому.

Це найшвидший на вкладках зразків.

Він також не обмежується входами довжиною <256, але може обробляти довільно довгі входи.

Обидві ці речі також були правдою відповідей Альберта нижче. Приємним у цьому є те, що час роботи O (N) на вхідній довжині. Так, ця річ насправді працює в лінійний час. Він уже їв постійний коефіцієнт 255 як закуску.


3

Проста реалізація сортування підрахунку. Кожне відро завдовжки 3 комірки, що містить поточний вхід, маркер та кількість підрахунків, скільки разів лічильник з'являється на вході.

process input
,[

while input is not zero
[

decrement input
-

copy input over to next bucket
[->>>+<<<]

mark next bucket as not the first
>>>>+<

repeat until input is zero
]

increment count for this bucket
>>+

rewind using markers
<[-<<<]<

process next input
,]

generate output
>+[>[<-.+>-]<[->>>+<<<]>>>+]

без коментарів:

,[[-[->>>+<<<]>>>>+<]>>+<[-<<<]<,]>+[>[<-.+>-]<[->>>+<<<]>>>+]


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.