Tri le plus rapide dans BrainF ***

15

Après avoir implémenté QuickSort dans BrainF *** , j'ai réalisé que ce n'était probablement pas si rapide. Les opérations qui sont O (1) dans les langages normaux (comme l'indexation de tableaux) sont considérablement plus longues dans BF. La plupart des règles pour ce qui fait un tri efficace peuvent être jetées par la fenêtre lorsque vous codez dans un tarpit Turing.

Voici donc un défi pour implémenter le "Fastest BrainF *** Sort Routine Ever". Je chronométrerai toutes les entrées en utilisant l'interpréteur ci-dessous. L'interprète utilise une bande de 16 Ko de caractères non signés. La bande et les cellules s'enroulent lorsqu'elles sont avancées / incrémentées au-delà des limites. La lecture de l'EOF met un 0 dans la cellule actuelle. Le temps mesuré comprend à la fois le temps d'analyse du fichier source et le temps de traitement de tous les fichiers d'entrée. Le code le plus rapide gagne.

Le vecteur de test sera un ensemble de fichiers Ascii conçus pour tester les cas de bord de tri, y compris

  • Une liste déjà triée: "commandée"

    &#33;"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    
  • Une liste triée inversée: "inversée"

    ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!
    
  • Un fichier composé de nombreuses copies de quelques valeurs uniques: "onlynine"

    ibbkninbkrauickabcufrfckbfikfbbakninfaafafbikuccbariauaibiraacbfkfnbbibknkbfankbbunfruarrnrrrbrniaanfbruiicbuiniakuuiubbknanncbuanbcbcfifuiffbcbckikkfcufkkbbakankffikkkbnfnbncbacbfnaauurfrncuckkrfnufkribnfbcfbkbcrkriukncfrcnuirccbbcuaaifiannarcrnfrbarbiuk
    
  • Un fichier ascii complètement aléatoire: "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
    
  • Un fichier aléatoire sur la plage 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¦Ó 
    

Chaque fichier d'entrée a au plus 255 octets.

Voici l'interprète. Il est écrit pour Windows en mode console, mais devrait être facile à porter: il suffit de le remplacer read_time()et sysTime_to_ms()avec des équivalents spécifiques à la plate-forme.
Usage: 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));
}

Résultats à ce jour

Voici le temps moyen de 5 exécutions de l'ensemble complet de vecteurs:

 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
la source
Quelle est la plage des éléments d'entrée?
Keith Randall
C'est la plage des cellules, sauf 0: 1-255.
AShelly
vous devriez retimer le mien, je l'ai fait un peu plus vite.
Keith Randall
Il semble être plus de 2 fois plus rapide que mon plus récent - je ferai le chronométrage officiel lorsque je serai de retour sur la machine que j'ai utilisée pour les autres.
AShelly

Réponses:

9

Voici un tri qui est au moins 6 fois plus rapide que mon tri rapide. C'est un algorithme qui aurait peu de sens dans un langage traditionnel, car c'est O (N * m) où m est la valeur d'entrée maximale. Après avoir collecté l'entrée, elle passe à travers le tableau, en comptant les cellules> 0 puis en décrémentant chacune. Il ajoute ensuite 1 aux premières countcellules du vecteur de résultat. Il répète les passes jusqu'à ce que le compte soit 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 
<[<<]>>[.>>]

Algorithme équivalent 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]);

En voici un qui est 2x plus rapide. Il est vaguement basé sur le "tri spaghetti" : il établit une chaîne de 1 aussi longtemps que chaque entrée. La valeur dans chaque cellule représente le nombre de brins au moins aussi longs. (Donc [3,2,1,2] devient |4|0|3|0|1|0|0|). Ensuite, il commence à «mesurer» les brins et imprime la longueur chaque fois qu'il trouve la fin d'un.

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

Brut:

>,[[-[>>+<<-]>+>]<[<<]>,]+>[>+<-]>[>[>+<<->-]<[<<.>>-]<<[>>+<<-]>>+>>]
AShelly
la source
Ne frappez pas en comptant le tri! C'est mon genre préféré, en raison d'une victoire massive que j'ai obtenue une fois: si m est connu pour être petit, vous pouvez obtenir d'énormes accélérations sur des algorithmes autrement "rapides". De même, le tri à bulles bat le tri rapide sur les données principalement triées. Aucun algorithme ___ n'est le meilleur pour chaque contexte.
boothby
Je ne pense pas que ce soit exactement une sorte de comptage. Votre commentaire m'a obligé à faire d'autres recherches. Je pense que cela ressemble plus à une sorte de perle . Mais je ne suis même pas sûr que ce soit vrai.
AShelly
Non, tu as raison. C'est une sorte étrange. Pourrait être utile pour certaines applications impliquant des listes de listes liées ... mais je suis même douteux de cela.
stand
4
L'analogie physique est que vous avez N piles de pièces de différentes tailles. Réservez de l'espace pour un autre N piles. Vous enlevez une pièce du haut de chaque pile contenant des pièces, puis ajoutez 1 à chaque pile du nouvel ensemble de droite à gauche jusqu'à ce que votre main soit vide. Répétez jusqu'à ce que toutes les piles d'origine soient vides. Le nouvel ensemble est maintenant trié de gauche à droite.
AShelly
7
>>+>,[->+>,]<[<[<<]<[.<[<<]<]>>[+>->]<<]

Je ne me souviens pas de l'idée de cet algorithme. Peut-être celle de Bertram Felgenhauer? Il est venu de discussions autour du concours de golf Brainfuck # 2, il y a plus d'une décennie.

C'est le plus rapide à ce jour sur les entrées d'échantillon.

Il n'est pas non plus limité aux entrées de longueur <256, mais peut gérer des entrées arbitrairement longues.

Ces deux choses étaient également vraies pour les réponses d'Albert, ci-dessous. La bonne chose à propos de celui-ci est que le temps de fonctionnement est O (N) dans la longueur d'entrée. Oui, cette chose fonctionne réellement en temps linéaire. Il a déjà mangé un facteur constant de 255 comme collation.

Daniel Cristofani
la source
3

Une implémentation simple de tri de comptage. Chaque compartiment a une largeur de 3 cellules, contenant l'entrée actuelle, un marqueur et le nombre de fois où le compteur apparaît dans l'entrée.

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

sans commentaire:

,[[-[->>>+<<<]>>>>+<]>>+<[-<<<]<,]>+[>[<-.+>-]<[->>>+<<<]>>>+]
Keith Randall
la source
2
>,[>+>,]+[<[<-<]>>[<[>>]>[<->[>>]<.<[<<]]>>]<<<<<+]
Albert
la source
2
>>+>,[>+>,]<[[<-<]>+>+[>]>[[-<<[[>>+<<-]<]>>]>-.+[>]>]<<]
Albert
la source