Implementar QuickSort en BrainF *** [cerrado]

32

Como se discutió en la sala Lounge en Stack Overflow:

Si no puede implementar el algoritmo Quicksort dado en en.wikipedia.org/wiki/Quicksort en cualquier idioma que tenga un conocimiento mínimo, es posible que desee considerar una profesión diferente. @sbi

pero SBI también señaló que tal vez BrainF *** fue una excepción.

Entonces, aquí está el rompecabezas / desafío: implementar QuickSort en BrainF *** . La implementación debe

  • ser interpretado por esto y / o por los intérpretes aquí (para guiones grandes)
  • implementar el algoritmo como se describe en Wikipedia, si es posible como una ordenación in situ
  • ordenar la siguiente lista de enteros: [0,4,6,4,2,3,9,2,3,6,5,3] e imprimir el resultado
Ronald
fuente
Buscando un poco, puedo encontrar una implementación , pero es de 6kB (y compilada de Haskell).
Peter Taylor
@Peter en realidad la implementación de brainfuck es 474.2 K dentro del archivo, que es un poco más grande de lo que esperaba (y demasiado grande para el intérprete en línea). Tal vez debería cambiar el intérprete de destino ... (pero me encantaría ver algo escrito a mano)
Ronald
22
Apuesto a que podría hacer una clasificación de burbujas y nadie que vea el código sabría la diferencia ...
Peter Olson
1
@Keith la idea es implementar realmente QuickSort, no cualquier tipo que funcione ... :-)
Ronald
1
@Peter Of The Corn: Descubriremos un tipo de burbuja por el mal desempeño.
usuario desconocido

Respuestas:

55

BrainF * (697 bytes)

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

A continuación se muestra una versión anotada. Para realizar un seguimiento de lo que se suponía que estaba sucediendo mientras lo desarrollaba, utilicé una notación de comentario que se ve así:|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 se presenta con una pila de particiones que crece a la izquierda para procesar a la izquierda, un espacio para raspar en el centro y la matriz que se ordena a la derecha. La indexación de la matriz se maneja moviendo un "bus de datos" que contiene el índice y el espacio de trabajo a través de la matriz. Entonces, por ejemplo, un autobús de 3 anchos |i|data|0|A0|A1|A2se convertirá |A0|i-1|data|0|A1|A2después de cambiar por uno. La partición se realiza manteniendo el bus entre los elementos alto y bajo.
Aquí está la versión 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
>>>>>>>>>>>[.>]
AShelly
fuente
Estaba trabajando en una solución similar pero no pude lograr que funcionara. Impresionante idea de hacer la partición de esa manera. Estaba sacando un elemento a la vez y reemplazándolo, y se volvió bastante engorroso con bastante rapidez. También estaba 1.5k en él, así que también me destruiste en eficiencia.
captncraig
1
Todo en BF se vuelve engorroso bastante rápido :) Incluso cosas aparentemente simples, como cómo hacer un eficiente if (i<j) {} else {} tomaron varios intentos para hacerlo bien. Y los casos extremos son asesinos. No sé cuántas veces pensé "solo quedaba esta cosita ..." y luego descubrí un caso de prueba que causó otras horas de trabajo para manejar. Creo que puedo reducirlo en unas pocas docenas de caracteres, pero no estoy seguro de querer esforzarme.
AShelly
Una palabra: ¡guau! Sinceramente, no pensé que fuera humanamente posible. Voy a ejecutar algunas entradas a través de él solo para ver cómo funciona :-)
Ronald
¡Épico! ¡Solo epico!
vsz
lo único que se puede decir es "santo f * ck!"
Enfriador de matemáticas el
11

brainfuck (178 bytes)

Incluso si el brainfuck es engorroso, ayuda a trabajar con el grano del lenguaje. Pregúntese "¿Tengo que almacenar este valor explícitamente en una celda?" A menudo puedes ganar velocidad y concisión haciendo algo más sutil. Y cuando el valor es un índice de matriz (o un número natural arbitrario), es posible que no quepa en una celda. Por supuesto, podría aceptar eso como un límite de su programa. Pero diseñar su programa para manejar valores grandes a menudo lo mejorará de otras maneras.

Como de costumbre, mi primera versión de trabajo fue el doble de lo necesario: 392 bytes. Numerosas modificaciones y dos o tres reescrituras importantes produjeron esta versión comparativamente elegante de 178 bytes. (Aunque de forma divertida, una ordenación de tiempo lineal es de solo 40 bytes).

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

Los valores de entrada están espaciados cada tres celdas: por cada celda de alor (V), hay una celda (L) abel (utilizada para la navegación) y una celda más para el espacio de bloqueo (S). El diseño general de la matriz es

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

Inicialmente, todas las celdas L se configuran en 1, para marcar partes de la matriz que aún necesitan ser ordenadas. Cuando hayamos terminado de particionar un subconjunto, lo dividimos en subconjuntos más pequeños al establecer la celda L de su pivote en 0, luego ubicamos la celda L más a la derecha que todavía es 1 y luego dividimos esa subarregla. Curiosamente, esta es toda la contabilidad que necesitamos para manejar adecuadamente el procesamiento recursivo de las submatrices. Cuando todas las celdas L se han puesto a cero, se ordena toda la matriz.

Para dividir un subconjunto, extraemos su valor más a la derecha en una celda S para que actúe como pivote, y lo llevamos (y la correspondiente celda V vacía) a la izquierda, comparándolo con el valor del otro en el subconjunto e intercambiando según sea necesario. Al final, el pivote se intercambia nuevamente, utilizando el mismo código de intercambio (que ahorra 50 bytes más o menos). Durante la partición, dos celdas L adicionales se mantienen establecidas en 0, para marcar las dos celdas que pueden necesitar intercambiarse entre sí; al final de la partición, el 0 izquierdo se fusionará con el 0 a la izquierda del subconjunto, y el 0 derecho terminará marcando su pivote. Este proceso también deja un 1 extra en la celda L a la derecha de la submatriz; El bucle principal comienza y termina en esta celda.

>+>>>>>,[>+>>,]>+[                      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
Daniel Cristofani
fuente
1
Increíble. Es mucho más limpio cuando trabajas con los modismos de BF, en lugar de tratar de forzarlo a actuar como un lenguaje de procedimiento, como lo hice yo.
AShelly
Es; pero la versión 4 en 392 bytes también fue una idiotez idiomática. Esta es la versión 39 más o menos. :)
Daniel Cristofani