BrainF中最快的排序***


15

在已经实施的BrainF ***快速排序,我意识到这可能不是那么快。在BF中,普通语言中的O(1)操作(如数组索引)要长得多。当您在图灵tarpit中进行编码时,构成有效排序的大多数规则都可以扔掉

因此,实现“有史以来最快的BrainF ***排序例程”是一项挑战。我将使用下面的解释器为所有条目计时。解释器使用带未签名字符的16K磁带。超出限制时,磁带和单元都将缠绕。读取EOF会将0放入当前单元格。测量的时间包括解析源文件的时间和处理所有输入文件的时间。最快的代码胜出。

测试向量将是一组用于测试排序边缘情况的Ascii文件,包括

  • 已经排序的列表:“已排序”

    &#33;"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    
  • 反向排序列表:“反向”

    ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!
    
  • 由多个唯一值的许多副本组成的文件:“ onlynine”

    ibbkninbkrauickabcufrfckbfikfbbakninfaafafbikuccbariauaibiraacbfkfnbbibknkbfankbbunfruarrnrrrbrniaanfbruiicbuiniakuuiubbknanncbuanbcbcfifuiffbcbckikkfcufkkbbakankffikkkbnfnbncbacbfnaauurfrncuckkrfnufkribnfbcfbkbcrkriukncfrcnuirccbbcuaaifiannarcrnfrbarbiuk
    
  • 完全随机的ascii文件:“随机”

    '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 2012年

您应该重新安排我的时间,我的速度更快。
基思·兰德尔

它的确比我最近的机器快了2倍-当我回到用于其他机器的机器上时,我将按照官方的时间进行计时。
AShelly 2012年

Answers:


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 

生的:

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

不要敲算排序!这是我最喜欢的一种,由于我有一次获得了巨大的成功:如果已知m小,则可以通过其他“快速”算法获得巨大的提速。同样,气泡排序在大多数排序的数据上胜过快速排序。没有一种____算法适合每种情况。
2010年

我不认为这是一种计数。您的评论迫使我进行了更多研究。我认为这更像是珠子排序。但是我什至不确定那是正确的。
AShelly 2012年

不,你是对的。这是一种奇怪的现象。对于某些涉及链表列表的应用程序可能很有用...但是我对此甚至表示怀疑。
12

4
物理类比是您有N堆不同大小的硬币。为另外N个堆栈留出空间。您从具有硬币的每个堆栈的顶部取下一个硬币,然后从右到左向新集中的每个堆栈加1,直到您的手为空。重复直到所有原始纸叠都为空。现在,新集合从左到右升序排列。
AShelly 2012年

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

我不记得这个算法是谁的主意。也许是Bertram Felgenhauer的?它来自十年前有关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
>+[>[<-.+>-]<[->>>+<<<]>>>+]

没有评论:

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.