Реализация быстрой сортировки в BrainF *** [закрыто]

32

Как обсуждалось в комнате отдыха на переполнении стека:

Если вы не можете реализовать алгоритм Quicksort, указанный на en.wikipedia.org/wiki/Quicksort, на любом языке, на котором у вас минимальные знания, вы можете выбрать другую профессию. @sbi

но ВОО также отметил, что, возможно, BrainF *** был исключением.

Итак, вот загадка / задача: внедрить QuickSort в BrainF *** . Реализация должна

  • быть интерпретированным этим и / или интерпретатором (ами) здесь (для больших сценариев)
  • реализовать алгоритм, как описано в Википедии - если это возможно, как сортировка на месте
  • отсортировать следующий список целых чисел: [0,4,6,4,2,3,9,2,3,6,5,3] и вывести результат
Рональд
источник
Немного обыскав, я смог найти одну реализацию , но это 6 КБ (и скомпилировано из Haskell).
Питер Тейлор
@Peter на самом деле реализация внутри мозга - 474,2 КБ, что немного больше, чем я ожидал (и слишком велико для онлайнового интерпретатора). Может быть, мне следует сменить целевого переводчика ... (но я бы хотел увидеть что-то написанное от руки)
Рональд
22
Могу поспорить, что вместо этого я мог бы делать пузырьковую сортировку, и никто, взглянув на код, не узнает разницу ...
Питер Олсон
1
@ Идея заключается в том, чтобы по-настоящему реализовать QuickSort, а не какой-либо вид, который будет работать ... :-)
Рональд
1
@Peter Of The Corn: Мы обнаружили бы пузырьковую сортировку по плохой производительности.
пользователь неизвестен

Ответы:

55

BrainF * (697 байт)

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

Ниже приведена аннотированная версия. Чтобы отслеживать, что должно было происходить при разработке, я использовал примечание комментария, которое выглядит так:|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)

Память размещена с растущим слева стеком разделов для обработки слева, пустым пространством в центре и массивом, отсортированным справа. Индексирование массива осуществляется путем перемещения «шины данных», содержащей индекс и рабочее пространство, через массив. Так, например, шина шириной 3 |i|data|0|A0|A1|A2, станет |A0|i-1|data|0|A1|A2после сдвига на единицу. Разбиение осуществляется путем удержания шины между верхним и нижним элементами.
Вот полная версия:

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
источник
Я работал над похожим решением, но не смог заставить его работать. Отличная идея сделать разбиение таким образом. Я вытаскивал один элемент за раз и заменял его, и это стало довольно громоздким довольно быстро. Я тоже был в ней 1.5к, так что вы меня тоже убили по эффективности.
Captncraig
1
Все в BF становится довольно обременительным довольно быстро :) Даже на первый взгляд простые вещи, такие как, как сделать эффективное, if (i<j) {} else {}заняли несколько попыток, чтобы получить право. И крайние случаи - убийцы. Я не знаю, сколько раз я думал, что "только эта маленькая вещь осталась ...", а затем обнаружил контрольный пример, который заставил работать еще несколько часов. Я думаю, что могу уменьшить его на несколько десятков символов, но я не уверен, что хочу приложить усилия.
AShelly
Одним словом: вау! Честно говоря, я не думал, что это возможно по-человечески. Я собираюсь провести через него несколько входов, просто чтобы посмотреть, как это работает :-)
Рональд
Эпическая! Просто эпично!
вс
Единственное, что нужно сказать, это "святой блин!"
Математический чиллер
11

Brainfuck (178 байт)

Даже если мозговитость громоздка, это помогает работать с зерном языка. Спросите себя "Должен ли я хранить это значение явно в ячейке?" Вы можете часто набирать скорость и краткость, делая что-то более тонкое. И когда значение является индексом массива (или произвольным натуральным числом), оно может не помещаться в ячейку. Конечно, вы можете просто принять это как ограничение вашей программы. Но разработка вашей программы для работы с большими значениями часто делает ее лучше другими способами.

Как обычно, моя первая рабочая версия была вдвое длиннее необходимой - 392 байта. Многочисленные модификации и два или три основных переписывания произвели эту сравнительно изящную 178-байтовую версию. (Хотя забавно, что сортировка по линейному времени составляет всего 40 байтов.)

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

Входные значения располагаются через каждые три ячейки: для каждой ячейки (V) alue существует (L) ячейка abel (используется для навигации) и еще одна ячейка для (S) промежуточного пространства. Общая схема массива

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

Первоначально все ячейки L установлены в 1, чтобы пометить части массива, которые все еще нуждаются в сортировке. Когда мы закончим разделение подмассива, мы разделим его на более мелкие подмассивы, установив L-ячейку его сводной ячейки в 0, затем найдем крайнюю правую L-ячейку, которая по-прежнему равна 1, и разделим эту подмассив дальше. Как ни странно, это все, что нам нужно для правильной обработки рекурсивной обработки подмассивов. Когда все L ячеек были обнулены, весь массив сортируется.

Чтобы разделить подмассив, мы вытягиваем его самое правое значение в S-ячейку, чтобы он действовал как свод, и переносим его (и соответствующую пустую V-ячейку) влево, сравнивая его друг с другом в подмассиве и меняя местами при необходимости. В конце, пивот возвращается обратно, используя тот же код подкачки (который экономит 50 байтов или около того). Во время разделения две дополнительные ячейки L сохраняются равными 0, чтобы пометить две ячейки, которые, возможно, необходимо поменять местами друг с другом; в конце разбиения левый 0 слится с 0 слева от подмассива, а правый 0 в конечном итоге помечает свою точку поворота. Этот процесс также оставляет лишнюю 1 в ячейке L справа от подмассива; основной цикл начинается и заканчивается в этой ячейке.

>+>>>>>,[>+>>,]>+[                      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
Потрясающе. Это намного чище, когда вы работаете с идиомами Б.Ф., вместо того, чтобы пытаться заставить его действовать как процедурный язык, как я.
AShelly
Это; но версия 4 на 392 байта тоже была идиоматичной. Это версия 39 или около того. :)
Даниэль Кристофани