Ordinamento più veloce in BrainF ***


15

Dopo aver implementato QuickSort in BrainF *** , mi sono reso conto che probabilmente non era così veloce. Le operazioni che sono O (1) in linguaggi normali (come l'indicizzazione di array) sono significativamente più lunghe in BF. La maggior parte delle regole per ciò che rende un ordinamento efficiente possono essere gettate fuori dalla finestra quando si codifica in un tarpit di Turing.

Quindi ecco una sfida per implementare la "routine di ordinamento BrainF *** più veloce di sempre". Prenderò il tempo tutte le voci utilizzando l'interprete di seguito. L'intero utilizza un nastro 16K di caratteri non firmati. Sia il nastro che le celle si avvolgono quando sono avanzati / incrementati oltre i limiti. La lettura dell'EOF inserisce uno 0 nella cella corrente. Il tempo misurato include sia il tempo per analizzare il file sorgente sia il tempo per elaborare tutti i file di input. Il codice più veloce vince.

Il vettore di test sarà un set di file Ascii progettato per testare casi limite di ordinamento, incluso

  • Un elenco già ordinato: "ordinato"

    &#33;"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    
  • Un elenco ordinato inverso: "inverso"

    ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!
    
  • Un file costituito da molte copie di alcuni valori univoci: "onlynine"

    ibbkninbkrauickabcufrfckbfikfbbakninfaafafbikuccbariauaibiraacbfkfnbbibknkbfankbbunfruarrnrrrbrniaanfbruiicbuiniakuuiubbknanncbuanbcbcfifuiffbcbckikkfcufkkbbakankffikkkbnfnbncbacbfnaauurfrncuckkrfnufkribnfbcfbkbcrkriukncfrcnuirccbbcuaaifiannarcrnfrbarbiuk
    
  • Un file ASCII completamente casuale: "casuale"

    '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
    
  • Un file casuale nell'intervallo 1..255: "wholerange"

    öè—@œ™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¦Ó 
    

Ogni file di input ha al massimo 255 byte.

Ecco l'interprete. È scritto per Windows in modalità console, ma dovrebbe essere facile da portare: basta sostituire read_time()e sysTime_to_ms()con equivalenti specifici della piattaforma.
Uso: 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));
}

Risultati finora

Ecco il tempo medio di 5 esecuzioni del set completo di vettori:

 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)

Qual è la gamma degli elementi di input?
Keith Randall,

È l'intervallo delle celle, tranne 0: 1-255.
AShelly,

dovresti rinviare il mio, l'ho reso un po 'più veloce.
Keith Randall,

Sembra essere più del 2x più veloce del mio più recente: farò il cronometraggio ufficiale quando tornerò sulla macchina che ho usato per gli altri.
AShelly,

Risposte:


9

Ecco una specie che è almeno 6 volte più veloce del mio quicksort. È un algoritmo che avrebbe poco senso in un linguaggio tradizionale, poiché è O (N * m) dove m è il valore di input massimo. Dopo aver raccolto l'input, passa attraverso l'array, contando le celle> 0 e quindi diminuendo ciascuna. Quindi aggiunge 1 alle prime countcelle nel vettore risultato. Ripete i passaggi fino a quando il conteggio è 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 
<[<<]>>[.>>]

Algoritmo C equivalente:

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

Eccone uno che è il doppio più veloce. Si basa vagamente su "spaghetti sort" : stabilisce una stringa di 1 secondo quanto ogni input. Il valore in ogni cella rappresenta il numero di trefoli almeno così a lungo. (Quindi [3,2,1,2] diventa |4|0|3|0|1|0|0|). Quindi inizia a "misurare" i fili e stampa la lunghezza ogni volta che trova la fine di uno.

>,[ [-[>>+<<-]>+>] <[<<]>,]   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 

Crudo:

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

Non bussare al conteggio ordinamento! È il mio tipo preferito, a causa di una grande vittoria che ho ottenuto una volta: se m è noto per essere piccolo, puoi ottenere enormi accelerazioni su algoritmi altrimenti "veloci". Allo stesso modo, l'ordinamento a bolle batte quicksort su dati per lo più ordinati. Nessuno ___ algoritmo è il migliore per ogni contesto.
stand dal

Non penso che questo sia esattamente un tipo di conteggio. Il tuo commento mi ha costretto a fare qualche ricerca in più. Penso che sia più simile a una specie di perline . Ma non sono nemmeno sicuro che sia giusto.
AShelly,

No, hai ragione. Questo è un tipo strano. Potrebbe essere utile per alcune applicazioni che coinvolgono elenchi di elenchi collegati ... ma ne dubito anche.
stand dal

4
L'analogia fisica è che hai N pile di monete di dimensioni diverse. Metti da parte lo spazio per altre pile N. Prendi una moneta dalla cima di ogni pila che ha monete, quindi aggiungi 1 a ogni pila nel nuovo set da destra a sinistra fino a quando la tua mano è vuota. Ripetere fino a quando tutte le pile originali sono vuote. Ora il nuovo set è ordinato in ordine crescente da sinistra a destra.
AShelly,

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

Non ricordo di chi fosse l'idea di questo algoritmo. Forse Bertram Felgenhauer's? È venuto dalle discussioni sul concorso n. 2 del Brainfuck Golf, oltre un decennio fa.

Questo è il più veloce mai sugli input di esempio.

Inoltre, non è limitato agli input di lunghezza <256, ma può gestire input arbitrariamente lunghi.

Entrambe queste cose erano vere anche per le risposte di Albert, di seguito. La cosa bella di questo è che il tempo di esecuzione è O (N) nella lunghezza di input. Sì, questa cosa funziona effettivamente in tempo lineare. Ha già mangiato un fattore costante di 255 come spuntino.


3

Una semplice implementazione dell'ordinamento di conteggio. Ogni bucket ha una larghezza di 3 celle, contenente l'input corrente, un marker e il conteggio del numero di volte in cui il contatore appare nell'input.

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
>+[>[<-.+>-]<[->>>+<<<]>>>+]

senza commenti:

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

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

2
>>+>,[>+>,]<[[<-<]>+>+[>]>[[-<<[[>>+<<-]<]>>]>-.+[>]>]<<]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.