Implementa QuickSort in BrainF *** [chiuso]


32

Come discusso nella sala lounge su Stack Overflow:

se non riesci ad implementare l'algoritmo Quicksort fornito in en.wikipedia.org/wiki/Quicksort in qualsiasi lingua di cui tu abbia una conoscenza minima, potresti prendere in considerazione una professione diversa. @sbi

ma SBI ha anche notato che forse BrainF *** era un'eccezione.

Quindi, ecco il puzzle / sfida: implementa QuickSort in BrainF *** . L'implementazione deve

  • essere interpretato da questo e / o dall'interprete qui (per script di grandi dimensioni)
  • implementare l'algoritmo come descritto su Wikipedia - se possibile come ordinamento sul posto
  • ordina il seguente elenco di numeri interi: [0,4,6,4,2,3,9,2,3,6,5,3] e stampa il risultato

Cercando un po 'sono in grado di trovare un'implementazione , ma è 6kB (e compilato da Haskell).
Peter Taylor,

@Peter in realtà l'implementazione di brainfuck è 474.2 K all'interno dell'archivio - che è un po 'più grande di quanto mi aspettassi (e troppo grande per l'interprete online). Forse dovrei cambiare l'interprete di destinazione .. (ma mi piacerebbe vedere qualcosa di scritto a mano)
Ronald

22
Scommetto che potrei fare invece il bubble sort e nessuno guardando il codice saprebbe la differenza ...
Peter Olson,

1
@Keith l'idea è quella di implementare davvero QuickSort, non qualsiasi tipo che funzioni ... :-)
Ronald

1
@Peter Of The Corn: scopriremmo una sorta di bolla per le cattive prestazioni.
utente sconosciuto

Risposte:


55

BrainF * (697 byte)

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

Di seguito è una versione annotata. Per tenere traccia di ciò che doveva accadere mentre lo sviluppavo, ho usato una notazione di commento simile a questa:|a|b=0|c=A0|@d|A0|A1|```|

|a| represents a named cell
|b=X| means we know the cell has value X, where X can be a constant or a variable name
|@d|  means the data pointer is in this cell
|A0|A1|```| is variable length array. (using ``` for ... because . is a command)

La memoria è strutturata con una pila di partizioni in crescita a sinistra da elaborare a sinistra, uno spazio di graffiatura al centro e l'array che viene ordinato a destra. L'indicizzazione dell'array viene gestita spostando un "bus dati" contenente l'indice e lo spazio di lavoro attraverso l'array. Quindi, per esempio, un bus di 3 larghezza |i|data|0|A0|A1|A2diventerà |A0|i-1|data|0|A1|A2dopo aver spostato di uno. Il partizionamento viene eseguito mantenendo il bus tra gli elementi high e low.
Ecco la versione completa:

Get input
>>>>>>>> ,[>,]                      |A0|A1|```|An|@0|
Count items
<[ [>>>+<<<-]>[<+>-]<+ <]  |@0|n|0|0|A0|A1|```
Make 8wide data bus w/ stack on left
>[<<<<<<<<+>>>>>>>>-]  ```|K1=n|K0=0|Z=0|a|b|c|d|e|@f|g|X=0|A0|A1|```
K1 and K0 represent the first index to process (I) and one past the last (J)
Check if still partitions to process
<<<<<<<<[
  Copy K1 to a&c via Z
  [>>+>+>>+<<<<<-]>>[<<+>>-] ```|K1=J|K0=I|@Z=0|a=J|b|c=J|d|e|f|g|X=0|A0|A1|```
  Copy K0 to b&d via Z
  <[>+>>+>>+<<<<<-]>[<+>-] ```|K1|K0|@Z=0|a=J|b=I|c=J|d=I|e|f|g|X=0|A0|A1|```
  Check if J minus I LE 1 : Subtract d from c
  >>>>[-<->]                    |a=J|b=I|c=JminusI|@d=0|e|f|g|
  d= c==0; e = c==1
  +<[>- >+<<-[>>-<<[-]]]        |a=J|b=I|@c=0|d=c==0|e=c==1|f|g|
  if d or e is 1 then J minus I LE 1: partition empty
  >[<+>-]>[<<+>>-]<+<      |a=J|b=I|@c=isEmpty|d=1|e=0|f|g|
  If Partition Empty;
  [->-                      |a=J|b=I|@c=0|d=0|c=0|f|g|
    pop K0: Zero it and copy the remaining stack right one; inc new K0
    <<[-]<[-]<<[-]<[[>+<-]<]>>[>]<+    ``|K1|@Z=0|a=J|b=I|c=0|d=0|e|f|g|
  Else:
  >>>>]>[-                   Z|a=J|b=I|c=isEmpty=0|@d=0|e|f|g|X|A0|A1
    Move Bus right I plus 1 frames; leaving first element to left
    <<+[ -[>+<-]<-[>+<-]>>>>>>>>      (dec J as we move)
      [<<<<<<<<+>>>>>>>>-]<<<<<< ]      Z|Ai|a=J|@b=0|c=0|d|e|f|g|X|Aq
    first element becomes pivot Ap; store in b
    <<[>>+<<-]            Z|@0|a=J|b=Ap|c=0|d|e|f|g|X|Aq
    While there are more elements (J GT 0);
    >[                    Z|0|@a=J|b=Ap|c=0|d|e|f|g|X|Aq
      copy Ap to e via c
      >[>+>>+<<<-]>[<+>-]  Z|0|a=J|b=Ap|@c=0|d=0|e=Ap|f|g|X=0|Aq
       copy Aq to g via X
      >>>>>>[<+<+>>-]<[>+<-] |c|d=0|e=Ap|f|g=Aq|@X=0|Aq
      Test Aq LT Ap:  while e; mark f; clear it if g 
      <<<[ >+>[<-]<[<]           |@d=0|e|f=gLTe|g|
        if f: set d and e to 1; dec e and g 
        >>[<<+>[-]+>-]>-<<-]
      set g to 1; if d: set f 
      >>[-]+<<< [->>+<<]
      If Aq LT Ap move Aq across Bus
      >>[->- <<<<<[>+<-] <[>+<-] >>>>>>>>
        [<<<<<<<<+>>>>>>>>-] <<]  Z|0|Aq|a=J|b=Ap|c|d|e|@f=0|g=0|X=0|Ar
      Else Swap AQ w/ Aj: Build a 3wide shuttle holding J and Aq                
      >[[-] <<<<<<[>>+>>>>>+<<<<<<<-]>>[<<+>>-] |@c=0|d|e|f=0|g=0|X=J|Aq|Ar|```
      If J then dec J
      >>>>>[-
        & While J shuttle right
        [>>[<<<+>>>-]<[>+<-]<-[>+<-]>] |a=J|b=Ap|c|d|e|f|Ar|```|Aj|g=0|@X=0|Aq|
        Leave Aq out there and bring Aj back
        <<[ [>>+<<-] < ]              |a=J|b=Ap|c|d|e|@f=0|g|X=0|Ar|```|Aj|Aq|
      ]>]
    Either bus moved or last element swapped; reduce J in either case
    <<<<<<-]                 |Aq|@a=0|b=Ap|c|d|e|f|g|X|Ar|```|
    Insert Ap To right of bus
    >[>>>>>>+<<<<<<-]        |Aq|a=0|@b=0|c|d|e|f|g|Ap|Ar|```|
    Move the bus back to original location tracking pivot location
    <<[ [>>>>>>>+<<<<<<<-]>[<+>-]<+ <]     
    <[ [>>>>>>>>+<<<<<<<<-]>>[<+>-]<+ <<] |K1|K0|@Z=0|a=0|b=p|c|d|e|f|g|X|Ar|```
    if p is not 0:  put new partition on stack between K0 and K1:
    >+>[<-                                 |K1|K0|Z=0|@a=pEQ0|b=p|
      move K0 to Z; search for last K
      <<[>+<-] <[<]                           |@0|Kn|```|K1|0|Z=K0|a=0|b=p| 
      shift left until return to 0 at K0;
      >[ [<+>-] >]                            |Kn|```|K1|0|@0|Z=K0|a=0|b=p|
      put p one left of there making it K1; restore K0 from Z;
      >>>[<<<<+>>>>-]<<[<+>-]                 |Kn|```|K2|K1=p|K0|@Z=0|a=0|b=0|
    else increment K0 (special case when first partition empty) 
    >>]<[- <<+>>]              
  >>>]  End if !empty
<<<<<<] End If Partitions remaining   @K1=0|K0=0|Z=0|a|b|c|d|e|f|g|X=0|A0|A1|```
Print the Results
>>>>>>>>>>>[.>]

Stavo lavorando su una soluzione simile ma non riuscivo a farlo funzionare. Fantastica idea di fare il partizionamento in quel modo. Stavo estraendo un elemento alla volta e lo sostituivo, e divenne abbastanza ingombrante abbastanza rapidamente. Ci ho messo anche 1.5k, quindi mi hai distrutto anche per efficienza.
captncraig,

1
Tutto in BF diventa ingombrante abbastanza rapidamente :) Anche cose apparentemente semplici come fare un efficiente if (i<j) {} else {}hanno richiesto diversi tentativi per ottenere il giusto. E i casi limite sono assassini. Non so quante volte ho pensato "solo questa piccola cosa è rimasta ..." e poi ho scoperto un caso di test che ha causato un altro lavoro di diverse ore da gestire. Penso di poterlo ridurre di alcune decine di personaggi, ma non sono sicuro di voler impegnarmi.
AShelly,

Una sola parola: wow! Onestamente non pensavo fosse umanamente possibile. Eseguirò alcuni input solo per vedere come funziona :-)
Ronald

Epico! Semplicemente epico!
vsz

l'unica cosa da dire è "santo f * ck!"
Chiller matematico

11

brainfuck (178 byte)

Anche se brainfuck è ingombrante, aiuta a lavorare con il grano della lingua. Chiediti "Devo memorizzare questo valore esplicitamente in una cella?" Spesso puoi ottenere velocità e concisione facendo qualcosa di più sottile. E quando il valore è un indice di array (o un numero naturale arbitrario), potrebbe non rientrare in una cella. Naturalmente, potresti semplicemente accettarlo come limite del tuo programma. Ma progettare il tuo programma per gestire valori elevati spesso lo renderà migliore in altri modi.

Come al solito, la mia prima versione funzionante era il doppio del tempo necessario: 392 byte. Numerose modifiche e due o tre importanti riscritture hanno prodotto questa versione relativamente graziosa da 178 byte. (Anche se in modo divertente un ordinamento a tempo lineare è di soli 40 byte.)

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

I valori di input sono distanziati ogni tre celle: per ogni cella (V) alue, c'è una cella (L) abel (utilizzata per la navigazione) e un'altra cella per (S) spazio di compressione. Il layout generale dell'array è

0 1 0 0 0 SVLSVL ... SVL 0 0 0 0 0 0 ...

Inizialmente tutte le celle L sono impostate su 1, per contrassegnare le parti dell'array che devono ancora essere ordinate. Quando abbiamo finito di partizionare un subarray, lo dividiamo in subarray più piccoli impostando la cella L del suo perno su 0, quindi individuiamo la cella L più a destra che è ancora 1 e partizioniamo quel subarray successivo. Stranamente, questa è tutta la contabilità di cui abbiamo bisogno per gestire correttamente l'elaborazione ricorsiva dei subarrays. Quando tutte le celle L sono state azzerate, l'intero array viene ordinato.

Per partizionare un subarray, estraiamo il suo valore più a destra in una cella S per agire come perno e portiamo a sinistra (e la corrispondente cella V vuota), confrontandolo con l'altro valore nel subarray e scambiandolo secondo necessità. Alla fine il pivot viene ricambiato, usando lo stesso codice di swap (che consente di risparmiare circa 50 byte). Durante il partizionamento, due celle L aggiuntive vengono mantenute impostate su 0, per contrassegnare le due celle che potrebbero dover essere scambiate tra loro; alla fine del partizionamento, lo 0 sinistro si fonderà con lo 0 a sinistra del subarray e lo 0 destro finirà per contrassegnare il suo perno. Questo processo lascia anche un ulteriore 1 nella cella L a destra del subarray; il ciclo principale inizia e termina in questa cella.

>+>>>>>,[>+>>,]>+[                      set up; for each subarray:
    --[+<<<-]<[                         find the subarray; if it exists:
        [<+>-]<[                        S=pivot; while pivot is in S:
            <[                          if not at end of subarray
                ->[<<<+>>>>+<-]         move pivot left (and copy it) 
                <<[>>+>[->]<<[<]<-]>    move value to S and compare with pivot
            ]>>>+<[[-]<[>+<-]<]>[       if pivot greater then set V=S; else:
                [>>>]+<<<-<[<<[<<<]>>+>[>>>]<-]     swap smaller value into V
                <<[<<<]>[>>[>>>]<+<<[<<<]>-]        swap S into its place
            ]+<<<                       end else and set S=1 for return path
        ]                               subarray done (pivot was swapped in)
    ]+[->>>]>>                          end "if subarray exists"; go to right
]>[brainfuck.org>>>]                    done sorting whole array; output it

1
Eccezionale. È molto più pulito quando lavori con gli idiomi di BF, invece di cercare di forzarlo ad agire come un linguaggio procedurale, come ho fatto io.
AShelly,

È; ma anche la versione 4 a 392 byte era idiomatica. Questa è la versione 39 o giù di lì. :)
Daniel Cristofani,
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.