在BrainF ***中实现QuickSort [关闭]


32

正如在堆栈溢出休息室中所讨论的:

如果您无法以您最不了解的任何语言来实现en.wikipedia.org/wiki/Quicksort给出的Quicksort算法,则可能需要考虑其他职业。@sbi

但SBI还指出,也许BrainF ***是个例外。

因此,这是一个难题/挑战:在BrainF ***中实现QuickSort。实施必须

  • 和/或此处的解释器解释(对于大型脚本)
  • 实施Wikipedia上描述的算法-如果可能的话,就地进行排序
  • 排序以下整数列表:[0,4,6,4,2,3,9,2,3,6,5,3]并打印结果

经过一番搜索,我可以找到一个实现,但是它是6kB(由Haskell编译)。
彼得·泰勒

@Peter实际上在存档中的Brainfuck实现是474.2 K-比我预期的要大一点(对于在线解释器来说太大了)。也许我应该改变目标解释。(但我喜欢看到的东西手写)
罗纳德

22
我敢打赌我可以改成冒泡排序,没有人看代码会知道其中的区别……
彼得·奥尔森

1
@Keith的想法是真正实现QuickSort,而不仅仅是任何可以使用的方法……:-)
罗纳德

1
@Peter Of The Corn:我们会因为表现不佳而发现泡沫。
用户未知

Answers:


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

我当时正在研究类似的解决方案,但不能完全解决问题。那样做分区的好主意。我一次抽出一个元素并替换了它,很快就变得很麻烦。我当时也只有1.5k,所以您也因为效率而毁了我。
captncraig 2012年

1
BF中的所有内容都变得非常麻烦:)即使看似简单的事情(如如何执行高效操作)if (i<j) {} else {}也经过了几次尝试才能正确。边缘案例是杀手.。我不知道有多少次我想“仅剩一件小事……”,然后发现了一个测试用例,导致又要花几个小时才能处理。我认为我可以减少几十个字符,但是我不确定是否要付出努力。
AShelly 2012年

一句话:哇!老实说,我不认为这是人类可能的。我将通过它进行一些输入,只是为了查看它是如何工作的:-)
Ronald

史诗!只是史诗般的!
vsz 2012年

唯一要说的是“天哪!”
数学冷却器

11

脑干 (178字节)

即使Brainfuck很麻烦,也有助于使用该语言。问问自己“我是否必须在单元格中明确存储此值?” 您通常可以通过做一些更细微的事情来获得速度和简洁感。并且当值是数组索引(或任意自然数)时,它可能不适合单元格。当然,您可以接受它作为程序的限制。但是,设计程序以处理较大的值通常会使它在其他方面变得更好。

像往常一样,我的第一个工作版本是所需字节的两倍(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将结束标记其中心点。此过程还在子数组的右侧L单元中留下了额外的1;主循环在此单元格处开始和结束。

>+>>>>>,[>+>>,]>+[                      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
太棒了 当您使用BF的习惯用法时,它要干净得多,而不是像我一样强迫它像程序语言一样起作用。
AShelly

它是; 但是版本392字节的版本4也很惯用。这是版本39左右。:)
Daniel Cristofani
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.