Самая быстрая сортировка в BrainF ***

15

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

Итак, вот задача для реализации «Fastest BrainF *** Sort Routine Ever». Я буду время всех записей, используя переводчик ниже. Интерпретатор использует ленту без знака 16К. И лента, и ячейки переносятся, когда их значение увеличивается / увеличивается за пределы. Чтение 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)
AShelly
источник
Каков диапазон входных элементов?
Кит Рэндалл
Это диапазон ячеек, кроме 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 раза быстрее. Он основан на «сортировке спагетти» : он устанавливает строку из 1 с до тех пор, пока каждый ввод. Значение в каждой ячейке представляет количество нитей, по крайней мере, такой длины. (Так [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 

сырье:

>,[[-[>>+<<-]>+>]<[<<]>,]+>[>+<-]>[>[>+<<->-]<[<<.>>-]<<[>>+<<-]>>+>>]
AShelly
источник
Не стучите, считая сортировку! Это мой любимый вид, благодаря огромному выигрышу, который я однажды получил от него: если известно, что m маленький, вы можете получить огромное ускорение по сравнению с другими «быстрыми» алгоритмами. Аналогичным образом, сортировка пузырьков превосходит быструю сортировку по главным образом отсортированным данным. Ни один ___ алгоритм не является лучшим для любого контекста.
Boothby
Я не думаю, что это как раз подсчет. Ваш комментарий заставил меня сделать еще несколько исследований. Я думаю, что это больше похоже на бисер . Но я даже не уверен, что это правильно.
AShelly
Нет, ты прав. Это странный вид. Может быть полезно для некоторых приложений, включающих списки связанных списков ... но я сомневаюсь даже в этом.
Boothby
4
Физическая аналогия в том, что у вас есть N стопок монет разных размеров. Выделите место для еще N стеков. Вы берете одну монету с верха каждой стопки, в которой есть монеты, и затем добавляете 1 к каждой стопке в новом наборе справа налево, пока ваша рука не опустеет. Повторяйте до тех пор, пока все оригинальные стопки не станут пустыми. Теперь новый набор отсортирован по возрастанию слева направо.
AShelly
7
>>+>,[->+>,]<[<[<<]<[.<[<<]<]>>[+>->]<<]

Я не помню, чья идея был этот алгоритм. Может быть, Бертрам Фельгенгауэр? Это произошло из-за дискуссий вокруг соревнования по гольфу Brainfuck №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
>+[>[<-.+>-]<[->>>+<<<]>>>+]

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

,[[-[->>>+<<<]>>>>+<]>>+<[-<<<]<,]>+[>[<-.+>-]<[->>>+<<<]>>>+]
Кит Рэндалл
источник
2
>,[>+>,]+[<[<-<]>>[<[>>]>[<->[>>]<.<[<<]]>>]<<<<<+]
Альберт
источник
2
>>+>,[>+>,]<[[<-<]>+>+[>]>[[-<<[[>>+<<-]<]>>]>-.+[>]>]<<]
Альберт
источник