【问题标题】:Code Golf: Collatz Conjecture代码高尔夫:科拉茨猜想
【发布时间】:2011-01-24 04:48:23
【问题描述】:

http://xkcd.com/710/ 启发,这里有一个代码高尔夫。

挑战

给定一个大于 0 的正整数,打印出该数字的冰雹序列。

冰雹序列

详情请见Wikipedia..

  • 如果是偶数,则除以二。
  • 如果数字是奇数,则将其加三并加一。

用产生的数字重复这个直到它达到1。(如果它在1之后继续,它将进入1 -> 4 -> 2 -> 1...的无限循环)

有时代码是最好的解释方式,所以这里有一些来自维基百科

function collatz(n)
  show n
  if n > 1
    if n is odd
      call collatz(3n + 1)
    else
      call collatz(n / 2)

此代码有效,但我增加了一个额外的挑战。 程序不得容易受到堆栈溢出的影响。所以它必须要么使用迭代,要么使用尾递归。

此外,如果它可以计算大数字并且该语言尚未实现它,则可以加分。 (或者如果您使用固定长度整数重新实现大数字支持)

测试用例

Number: 21
Results: 21 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1

Number: 3
Results: 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1

此外,代码 Golf 必须包含完整的用户输入和输出。

【问题讨论】:

  • 不能容易受到堆栈溢出的影响:你不应该把它贴在这里! ;)
  • 我的朋友不再给我打电话了,这是否意味着我解决了问题?
  • 你在 SO,但曾经有过朋友? ...那是什么样的?
  • 汇编答案很酷,但是选择最长答案有点anti-code-golf

标签: language-agnostic code-golf rosetta-stone collatz


【解决方案1】:

Perl,59 个字符

$n=shift;for($i=1;$n>1;$i++){$n=$n%2==0?$n/2:$n*3+1;printf"%002s: %s\n",$i,$n;}

不过,我更喜欢这个 79 个字符(不包括空格)的版本,因为它会打印行号和迭代值:

$n = shift; for($i = 1; $n > 1; $i++){ $n = $n % 2 == 0 ? $n / 2 : $n*3 + 1; printf "%002s: %s\n", $i, $n;}

$n = shift; 

for($i = 1; $n > 1; $i++){ 
    $n = $n % 2 == 0 ? $n / 2 : $n*3 + 1; 
    printf "%002s: %s\n", $i, $n;
}

【讨论】:

    【解决方案2】:

    PHP

    function Collatz($n)
    {
            $i = 0;
        while($n>1)
        {
            if($n % 2)
            {
                $n = (3*$n) + 1;
                $i++;
                echo "step $i:  $n <br/>";
            }
    
            else 
            {
                $n = $n/2;
                $i++;
                echo "step $i:  $n <br/>";
            }
        }
    
    }
    

    【讨论】:

    • 您将$i++;echo "step ..."; 复制了两次。将它们移到 else 块之后以保存一些字符。
    【解决方案3】:

    PHP, 78 72 67 个字符

    我实际上是在大约两年前编写了这个程序,当时我在一本 Pickover 书中读到了这个序列。我把它清理了一下,这是我能做的最小的,并且仍然有用户输入和一个漂亮、可读的输出:

    <?$n=fgets(STDIN);while($n!=1){$n=(($n&1)==0)?($n/2):(($n*3)+1);echo"$n\n";}?>
    

    必须假设启用了短标签,我不太确定输入是否适用于所有控制台。但它在我的 Windows 机器上完美运行。


    更新:通过在数学上稍微作弊,我们可以刮掉一些字符:

    <?$n=fgets(STDIN);while($n!=1){$n=(($n&1)==0)?$n/2:$n*3+1;echo"$n\n";}?>
    

    更新:

    • 鉴于$n&amp;1 返回10,我们可以利用PHP 的松散类型并删除更多字符。
    • 此外,结合下面 Christian 的评论(稍作改动以防止无限循环),我们可以再删除一个。
    • 最后,由于 PHP 脚本不需要终止 ?&gt;,我们可以再去掉两个字符:

    最终结果:

    <?$n=fgets(STDIN);while($n>1){$n=(!($n&1))?$n/2:$n*3+1;echo"$n\n";}
    

    【讨论】:

    • 建议更改为“while($n>0)”以遵守“给定大于 0 的正整数”要求。
    • @Christian Close;必须做while($n&gt;1) 以防止无限循环,但很好的建议。
    【解决方案4】:

    Ruby 55 个字符

    n=gets.to_i
    while(n>1) do n=((n%2)==1)?3*n+1:n/2;p n end
    

    【讨论】:

      【解决方案5】:

      由于似乎对 LOLCODE 解决方案有点兴趣,我想我会比较两种解决方案方法(迭代和尾递归)在这种语言中的实现。

      首先,有203个字符的迭代解:

      HAI 1.2
          I HAS A n
          GIMMEH n
          IM IN YR l
              VISIBLE n
              BOTH SAEM 1 BIGGR OF 1 n
              O RLY?
                  YA RLY
                      GTFO
              OIC
              MOD OF n 2
              WTF?
                  OMG 0
                      n R QUOSHUNT OF n 2
                      GTFO
                  OMG 1
                      n R SUM OF 1 PRODUKT OF 3 n
              OIC
          IM OUTTA YR l
      KTHXBYE
      

      为您提供正在发生的事情的要点:

      • 使用 GIMMEH 关键字从 STDIN 读取输入
      • 循环在IM IN YR &lt;loopname&gt;IM OUTTA YR &lt;loopname&gt; 语句之间完成
      • VISIBLE 用于打印到 STDOUT
      • O RLY?YA RLYOIC 语句处理条件 If/Then/Else 逻辑
      • WTF?OMG &lt;expression&gt;OIC 语句处理条件 Switch/Case 逻辑
      • 使用&lt;variable&gt; R &lt;value&gt; 执行分配
      • GTFO 从循环或条件 Switch/Case 中断

      然后是尾递归解决方案,它设法减少了 2 个额外的字符,最终计数为 201:

      HAI 1.2
          HOW DUZ I c YR n
              VISIBLE n
              DIFFRINT 1 BIGGR OF 1 n
              O RLY?
                  YA RLY
                      MOD OF n 2
                      WTF?
                          OMG 0
                              c QUOSHUNT OF n 2
                              GTFO
                          OMG 1
                              c SUM OF 1 PRODUKT OF 3 n
                      OIC
              OIC
          IF U SAY SO
          I HAS A i
          GIMMEH i
          c i
      KTHXBYE
      

      这里的区别在于HOW DUZ I &lt;funcname&gt; YR &lt;args&gt;IF U SAY SO 语句之间的函数定义。使用&lt;funcname&gt; &lt;args&gt; 调用函数。

      【讨论】:

      • @Earlz 我想以最小字符形式(没有缩进)显示代码,但到底是什么,我会把它添加进去。:)
      【解决方案6】:

      Smalltalk,103 个字符

      [:n||l|l:=OrderedCollection with:1.[n>1]whileTrue:[l addLast:n.n:=n odd ifTrue:[3*n+1]ifFalse:[n/2]].l]
      

      通过向它发送消息#value: 来调用它,并带有所需的参数:

      [:n||l|l:=OrderedCollection with:1.[n>1]whileTrue:[l addLast:n.n:=n odd ifTrue:[3*n+1]ifFalse:[n/2]].l] value: 123
      

      或者,更理智地说:

      [:n | | result |
              result := OrderedCollection with: 1.
              [n > 1] whileTrue: [
                      result addLast: n.
                      n := n odd ifTrue: [3*n + 1] ifFalse: [n / 2]].
              result] value: 123
      

      (正确的方法是将上述定义为 Integer 上的方法,因此您会说“123 collat​​z”,而不是匿名闭包。)

      【讨论】:

        【解决方案7】:

        普通 Lisp,76 74 个字符

        (defun c(n)(if (eql n 1)'(1)(cons n(if(oddp n)(c(1+(* 3 n)))(c(/ n 2))))))
        

        或者,写得好,

        (defun collatz (n)
          (if (eql n 1)
              '(1)
            (cons n (if (oddp n)
                        (collatz (1+ (* 3 n)))
                        (collatz (/ n 2))))))
        

        【讨论】:

          【解决方案8】:

          J,31 个字符

          -:`(>:@(3&*))`1:@.(1&=+2&|)^:a:
          

          用法:

          -:`(>:@(3&*))`1:@.(1&=+2&|)^:a: 9
          9 28 14 7 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
          

          【讨论】:

            【解决方案9】:

            Grooovy - 59 个字符

            int n=args[0] as int
            while(n>1){println n=n%2==0?n/2:n*3+1}
            

            例子

            $ ./collatz.groovy 5
            16
            8
            4
            2
            1
            

            输出更漂亮(66 个字符

            int n=args[0] as int
            while(n>1){print " -> ${n=n%2==0?n/2:n*3+1}"}
            

            例子

            $ ./collatz.groovy 5
            -> 16 -> 8 -> 4 -> 2 -> 1
            

            【讨论】:

              【解决方案10】:

              Clojure - 70 个字符

              ((fn[n](prn n)(if(> n 1)(recur(if(odd? n)(+(* 3 n)1)(/ n 2)))))(read))
              

              或者,使用适当的空格和缩进:

              ((fn [n]
                (prn n)
                (if (> n 1)
                  (recur
                    (if (odd? n)
                      (+ (* 3 n) 1)
                      (/ n 2)))))
                (read))
              

              recur 强制 Clojure 使用尾调用递归,因此没有堆栈溢出。适用于任意大的数字。包括输入和输出,但如果输入非数字会崩溃:)。


              注意:发布我的答案后不久,我注意到另一个具有几乎相同算法的 Clojure 实现。但是,由于那个人不会试图简短,所以我会在这里留下我的答案,因为它的价值。

              【讨论】:

                【解决方案11】:

                Josl - 58 个字符

                这个版本不会因为尾递归而堆栈溢出。

                c dup println dup 1 > if dup odd? if 3 * 1+ else 2 / end c
                

                用途:

                main 21 c
                

                或者,其他例子:

                main 
                  21 c
                  63,728,127 c
                

                【讨论】:

                  【解决方案12】:

                  Erlang,120 个字符

                  -module (f).
                  -export ([f/1]).
                  f(1)->1;
                  f(N)->
                      io:format("~p ",[N]),
                      if N rem 2 =:= 0
                          ->f(trunc(N/2));
                          true->f(3*N+1)
                  end.
                  

                  测试:

                  f:f(171).
                  
                  171 514 257 772 386 193 580 290 145 436 218 109 328 164 82 41 124 62 31 94 47 
                  142 71 214 107 322 161 484 242 121 364 182 91 274 137 412 206 103 310 155 466 
                  233 700 350 175 526 263 790 395 1186 593 1780 890 445 1336 668 334 167 502 251 
                  754 377 1132 566 283 850 425 1276 638 319 958 479 1438 719 2158 1079 3238 1619 
                  4858 2429 7288 3644 1822 911 2734 1367 4102 2051 6154 3077 9232 4616 2308 1154 
                  577 1732 866 433 1300 650 325 976 488 244 122 61 184 92 46 23 70 35 106 53 160 
                  80 40 20 10 5 16 8 4 2 1
                  

                  【讨论】:

                    【解决方案13】:

                    LOLCODE:406 CHARAKTERZ

                    HAI
                    BTW COLLATZ SOUNDZ JUS LULZ
                    
                    CAN HAS STDIO?
                    
                    I HAS A NUMBAR
                    BTW, I WANTS UR NUMBAR
                    GIMMEH NUMBAR
                    
                    VISIBLE NUMBAR
                    
                    IM IN YR SEQUENZ
                      MOD OF NUMBAR AN 2
                      BOTH SAEM IT AN 0, O RLY?
                        YA RLY, NUMBAR R QUOSHUNT OF NUMBAR AN 2
                        NO WAI, NUMBAR R SUM OF PRODUKT OF NUMBAR AN 3 AN 1
                      OIC
                      VISIBLE NUMBAR
                      DIFFRINT 2 AN SMALLR OF 2 AN NUMBAR, O RLY?
                        YA RLY, GTFO
                      OIC
                    IM OUTTA YR SEQUENZ
                    
                    KTHXBYE
                    

                    已通过 JUSTIN J. MEZA'S INTERPRETR 测试。再见!

                    【讨论】:

                      【解决方案14】:

                      Javascript,67 56 个字符

                      for(a=[i=prompt()];i-1;a.push(i=i%2?i*3+1:i/2));alert(a)
                      

                      【讨论】:

                      • 为什么要在var 上浪费字符?也不包括输出中的 ` -> `。使用三元而不是数组索引也将节省 3 个字符。
                      • 好的。我挤得更紧了。 ' -> ' 并不是真正有趣的部分,大多数解决方案都忽略了它。只需将alert(a) 替换为:alert(a.join(' -&gt; '))
                      【解决方案15】:

                      JavaScript - 68 个字符

                      与其他 JS(和大多数其他语言)不同,这个实际上遵循输出中的 -&gt;

                      for(s='',c=' -> ',i=readline();i>1;i=i%2?i*3+1:i/2)s+=i+c
                      print(s+1)
                      

                      如果我们避免这种情况,这是一个 53 char 替代方案,每行打印一个数字:

                      for(p=print,i=readline(),p(i);i>1;)p(i=i%2?i*3+1:i/2)
                      

                      打算与 SpiderMonkey 一起运行:

                      echo 21 | js thisfile.js
                      
                      21 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
                      

                      【讨论】:

                        【解决方案16】:

                        Windows cmd - 68 个字符

                        @set/pd=
                        :l 
                        @set/ad=(d+d%%2*(d*5+2))/2&echo %d%&if %d% NEQ 1 goto:l
                        

                        【讨论】:

                          【解决方案17】:

                          GW-BASIC - 54 个字符

                          1INPUT N
                          2N=(N+(N*5+2)*(N MOD 2))/2:?N:IF N>1GOTO 2
                          

                          【讨论】:

                            【解决方案18】:

                            Haskell, 62 个字符 63 76 83, 86, 97, 137

                            c 1=[1]
                            c n=n:c(div(n`mod`2*(5*n+2)+n)2)
                            main=readLn>>=print.c
                            

                            用户输入,打印输出,使用常量内存和堆栈,使用任意大的整数。

                            A sample run 这段代码,给定一个 80 位数字的所有 '1' (!) 作为输入,看起来很有趣。


                            原始的,仅功能版本:

                            Haskell 51 个字符

                            f n=n:[[],f([n`div`2,3*n+1]!!(n`mod`2))]!!(1`mod`n)
                            

                            @&^# 到底谁需要条件句?

                            (编辑:我很“聪明”并使用了修复。没有它,代码会减少到 54 个字符。 编辑2:通过分解f()下降到51)

                            【讨论】:

                            • 在完成我的 Miranda 帖子(基本上只是较旧的 Haskell)之后,至少在 Miranda 中,您可以通过每个仅使用一个感叹号来减少它 - fn=n:[[],[f( n div 2),f(3*n+1)]!(n mod 2)]!(1 mod n) - 有效:)
                            • 哦,是的,您缺少输入和输出。
                            • @Martinho:我也是,但是由于懒惰的评估,这些表格甚至比其他语言的表格酷得多
                            • 使用 jleedev 的想法:c 1=[1];c n=n:(c$div(nmod2*(5*n+2)+n)2) - 41 个字符,这使用了 k*(3n+1)+(1-k)*n/2 其中 k= n mod 2
                            • 我删除了我的其他条目,并将我的代码移到这里,并结合了这些 cmets 的更多想法。增加到 76 个字符,但可以输入和输出。
                            【解决方案19】:

                            Fortran - 60 个字符

                            read*,n
                            1 if(n/2*2<n)n=6*n+2
                            n=n/2
                            print*,n
                            if(n>1)goto1
                            end
                            

                            【讨论】:

                              【解决方案20】:

                              开始,130 个字符

                              package main
                              import(."os"
                              ."strconv")
                              func main(){n,_:=Atoi(Args[1])
                              println(n)
                              for n>1{if n%2!=0{n=n*3+1}else{n/=2}
                              println(n)}}
                              

                              示例

                              ./collatz 3
                              3
                              10
                              5
                              16
                              8
                              4
                              2
                              1
                              

                              【讨论】:

                                【解决方案21】:

                                C# : 659 个字符,支持 BigInteger

                                using System.Linq;using C=System.Console;class Program{static void Main(){var v=C.ReadLine();C.Write(v);while(v!="1"){C.Write("->");if(v[v.Length-1]%2==0){v=v.Aggregate(new{s="",o=0},(r,c)=>new{s=r.s+(char)((c-48)/2+r.o+48),o=(c%2)*5}).s.TrimStart('0');}else{var q=v.Reverse().Aggregate(new{s="",o=0},(r, c)=>new{s=(char)((c-48)*3+r.o+(c*3+r.o>153?c*3+r.o>163?28:38:48))+r.s,o=c*3+r.o>153?c*3+r.o>163?2:1:0});var t=(q.o+q.s).TrimStart('0').Reverse();var x=t.First();q=t.Skip(1).Aggregate(new{s=x>56?(x-57).ToString():(x-47).ToString(),o=x>56?1:0},(r,c)=>new{s=(char)(c-48+r.o+(c+r.o>57?38:48))+r.s,o=c+r.o>57?1:0});v=(q.o+q.s).TrimStart('0');}C.Write(v);}}}
                                

                                不打高尔夫球

                                using System.Linq;
                                using C = System.Console;
                                class Program
                                {
                                    static void Main()
                                    {
                                        var v = C.ReadLine();
                                        C.Write(v);
                                        while (v != "1")
                                        {
                                            C.Write("->");
                                            if (v[v.Length - 1] % 2 == 0)
                                            {
                                                v = v
                                                    .Aggregate(
                                                        new { s = "", o = 0 }, 
                                                        (r, c) => new { s = r.s + (char)((c - 48) / 2 + r.o + 48), o = (c % 2) * 5 })
                                                    .s.TrimStart('0');
                                            }
                                            else
                                            {
                                                var q = v
                                                    .Reverse()
                                                    .Aggregate(
                                                        new { s = "", o = 0 }, 
                                                        (r, c) => new { s = (char)((c - 48) * 3 + r.o + (c * 3 + r.o > 153 ? c * 3 + r.o > 163 ? 28 : 38 : 48)) + r.s, o = c * 3 + r.o > 153 ? c * 3 + r.o > 163 ? 2 : 1 : 0 });
                                                var t = (q.o + q.s)
                                                    .TrimStart('0')
                                                    .Reverse();
                                                var x = t.First();
                                                q = t
                                                    .Skip(1)
                                                    .Aggregate(
                                                        new { s = x > 56 ? (x - 57).ToString() : (x - 47).ToString(), o = x > 56 ? 1 : 0 }, 
                                                        (r, c) => new { s = (char)(c - 48 + r.o + (c + r.o > 57 ? 38 : 48)) + r.s, o = c + r.o > 57 ? 1 : 0 });
                                                v = (q.o + q.s)
                                                    .TrimStart('0');
                                            }
                                            C.Write(v);
                                        }
                                    }
                                }
                                

                                【讨论】:

                                  【解决方案22】:

                                  C : 64 个字符

                                  main(x){for(scanf("%d",&x);x>=printf("%d,",x);x=x&1?3*x+1:x/2);}
                                  

                                  支持大整数:431(必需)字符

                                  #include <stdlib.h>
                                  #define B (w>=m?d=realloc(d,m=m+m):0)
                                  #define S(a,b)t=a,a=b,b=t
                                  main(m,w,i,t){char*d=malloc(m=9);for(w=0;(i=getchar()+2)/10==5;)
                                  B,d[w++]=i%10;for(i=0;i<w/2;i++)S(d[i],d[w-i-1]);for(;;w++){
                                  while(w&&!d[w-1])w--;for(i=w+1;i--;)putchar(i?d[i-1]+48:10);if(
                                  w==1&&*d==1)break;if(*d&1){for(i=w;i--;)d[i]*=3;*d+=1;}else{
                                  for(i=w;i-->1;)d[i-1]+=d[i]%2*10,d[i]/=2;*d/=2;}B,d[w]=0;for(i=0
                                  ;i<w;i++)d[i+1]+=d[i]/10,d[i]%=10;}}
                                  

                                  注意:至少在原型 malloc/realloc 之前不要删除#include &lt;stdlib.h&gt;,因为这样做在 64 位平台上是不安全的(64 位 void* 将转换为 32-位整数)。

                                  这个还没有经过激烈的测试。它也可以使用一些缩短。


                                  以前的版本:

                                  main(x){for(scanf("%d",&x);printf("%d,",x),x-1;x=x&1?3*x+1:x/2);} // 66
                                  

                                  (删除了 12 个字符,因为没有人遵循输出格式...:|)

                                  【讨论】:

                                    【解决方案23】:

                                    C:63 个字符

                                    main(x){scanf("%d",&x);while(x>printf("%d ",x=x&1?3*x+1:x/2));}
                                    

                                    这是基于 KennyTM 的回答。将 for 循环更改为 while 循环,并将代码带入 while。

                                    【讨论】:

                                      【解决方案24】:

                                      JavaScript,61 个 70 字符,带输入

                                      迭代,精度取决于 JS 限制

                                      var i=prompt('');while(i>1){console.log(i);i=(i%2)?3*i+1:i/2}
                                      

                                      【讨论】:

                                      • 可以用alert而不是console.log得到55个符号,但可用性会受到影响。
                                      • 保存 4 以删除 var 并使用 for(i=prompt();i&gt;1;i=i%2?3*i+1:i/2) 删除大括号来保存更多字符...但是 - 您的代码不会打印最终的 1 或包含 -&gt;
                                      【解决方案25】:

                                      Ruby,41 个字符

                                      n=gets.to_i
                                      p n=[n/2,n*3+1][n%2]while n>1
                                      

                                      【讨论】:

                                        【解决方案26】:

                                        TI-BASIC

                                        不是最短的,而是一种新颖的方法。大序列肯定会显着减慢,但它不应该溢出。

                                        PROGRAM:COLLATZ
                                        :ClrHome
                                        :Input X
                                        :Lbl 1
                                        :While X≠1
                                        :If X/2=int(X/2)
                                        :Then
                                        :Disp X/2→X
                                        :Else
                                        :Disp X*3+1→X
                                        :End
                                        :Goto 1
                                        :End
                                        

                                        【讨论】:

                                          【解决方案27】:

                                          Golfscript : 20 个字符

                                            ~{(}{3*).1&5*)/}/1+`
                                          # 
                                          # Usage: echo 21 | ruby golfscript.rb collatz.gs
                                          

                                          这相当于

                                          stack<int> s;
                                          s.push(21);
                                          while (s.top() - 1) {
                                            int x = s.top();
                                            int numerator = x*3+1;
                                            int denominator = (numerator&1) * 5 + 1;
                                            s.push(numerator/denominator);
                                          }
                                          s.push(1);
                                          return s;
                                          

                                          【讨论】:

                                          • “必须包括完整的用户输入和输出”
                                          • @FX,将21 替换为~ 将导致程序使用标准输入中的数字
                                          • @gnibbler:Golfscript.rb 更新了吗?当用~ 替换21 时,我得到(eval):1:in initialize': undefined method leftparen' for nil:NilClass (NoMethodError)
                                          • @KennyTM,遗憾的是 GolfScript 无法交互式读取标准输入,您必须将某些内容输入标准输入,例如 echo 21 | ruby golfscript.rb collatz.gs
                                          【解决方案28】:

                                          bc 41 个字符

                                          我猜这种问题是 bc 被发明出来的:

                                          for(n=read();n>1;){if(n%2)n=n*6+2;n/=2;n}
                                          

                                          测试:

                                          bc1 -q collatz.bc
                                          21
                                          64
                                          32
                                          16
                                          8
                                          4
                                          2
                                          1
                                          

                                          正确的代码:

                                          for(n=read();n>1;){if(n%2)n=n*3+1else n/=2;print n,"\n"}
                                          

                                          bc 处理最多 INT_MAX 位数的数字

                                          编辑:Wikipedia article 提到这个猜想已经检查了直到 20x258 的所有值(大约 5.76 e18)。这个程序:

                                          c=0;for(n=2^20000+1;n>1;){if(n%2)n=n*6+2;n/=2;c+=1};n;c
                                          

                                          68 秒内测试 220,000+1(大约 3.98e6,020),144,404 个周期。

                                          【讨论】:

                                          • 将 'n!=1' 更改为 `n>1' 以获得 54 个字符。
                                          • 这里是一个用于为该条目生成随机任意长度数字(在本例中为 10000 位)的命令行:cat /dev/urandom | tr -dc '0-9' | head -c 10000 | bc collatz-conjecture.bc
                                          • @indiv - 我必须对其进行测试:),处理 10,000 位数字需要 3 分 12 秒。我将输出保存到一个文件中,它大约 1.2gb 长,但是它确实在 1 中正确完成。指向bc
                                          【解决方案29】:

                                          Perl

                                          我决定有点反竞争,并展示您通常如何在 Perl 中编写此类问题。
                                          最后还有一个 46(总)字符代码-golf 条目。

                                          前三个示例都以此标题开头。

                                          #! /usr/bin/env perl
                                          use Modern::Perl;
                                          # which is the same as these three lines:
                                          # use 5.10.0;
                                          # use strict;
                                          # use warnings;
                                          
                                          while( <> ){
                                            chomp;
                                            last unless $_;
                                            Collatz( $_ );
                                          }
                                          
                                          • 简单的递归版本

                                            use Sub::Call::Recur;
                                            sub Collatz{
                                              my( $n ) = @_;
                                              $n += 0; # ensure that it is numeric
                                              die 'invalid value' unless $n > 0;
                                              die 'Integer values only' unless $n == int $n;
                                              say $n;
                                              given( $n ){
                                                when( 1 ){}
                                                when( $_ % 2 != 0 ){ # odd
                                                  recur( 3 * $n + 1 );
                                                }
                                                default{ # even
                                                  recur( $n / 2 );
                                                }
                                              }
                                            }
                                            
                                          • 简单的迭代版本

                                            sub Collatz{
                                              my( $n ) = @_;
                                              $n += 0; # ensure that it is numeric
                                              die 'invalid value' unless $n > 0;
                                              die 'Integer values only' unless $n == int $n;
                                              say $n;
                                              while( $n > 1 ){
                                                if( $n % 2 ){ # odd
                                                  $n = 3 * $n + 1;
                                                } else { #even
                                                  $n = $n / 2;
                                                }
                                                say $n;
                                              }
                                            }
                                            
                                          • 优化迭代版本

                                            sub Collatz{
                                              my( $n ) = @_;
                                              $n += 0; # ensure that it is numeric
                                              die 'invalid value' unless $n > 0;
                                              die 'Integer values only' unless $n == int $n;
                                              #
                                              state @next;
                                              $next[1] //= 0; # sets $next[1] to 0 if it is undefined
                                              #
                                              # fill out @next until we get to a value we've already worked on
                                              until( defined $next[$n] ){
                                                say $n;
                                                #
                                                if( $n % 2 ){ # odd
                                                  $next[$n] = 3 * $n + 1;
                                                } else { # even
                                                  $next[$n] = $n / 2;
                                                }
                                                #
                                                $n = $next[$n];
                                              }
                                              say $n;
                                              # finish running until we get to 1
                                              say $n while $n = $next[$n];
                                            }
                                            

                                          现在我将展示如何使用 v5.10.0 之前的 Perl 版本来完成最后一个示例

                                          #! /usr/bin/env perl
                                          use strict;
                                          use warnings;
                                          
                                          while( <> ){
                                            chomp;
                                            last unless $_;
                                            Collatz( $_ );
                                          }
                                          {
                                            my @next = (0,0); # essentially the same as a state variable
                                            sub Collatz{
                                              my( $n ) = @_;
                                              $n += 0; # ensure that it is numeric
                                              die 'invalid value' unless $n > 0;
                                          
                                              # fill out @next until we get to a value we've already worked on
                                              until( $n == 1 or defined $next[$n] ){
                                                print $n, "\n";
                                          
                                                if( $n % 2 ){ # odd
                                                  $next[$n] = 3 * $n + 1;
                                                } else { # even
                                                  $next[$n] = $n / 2;
                                                }
                                                $n = $next[$n];
                                              }
                                              print $n, "\n";
                                          
                                              # finish running until we get to 1
                                              print $n, "\n" while $n = $next[$n];
                                            }
                                          }
                                          

                                          基准测试

                                          首先,IO 总是很慢的部分。因此,如果您真的按原样对它们进行基准测试,您应该从每个测试中获得大致相同的速度。

                                          为了测试这些,我打开了/dev/null ($null) 的文件句柄,并编辑了每个say $n 以改为读取say {$null} $n。这是为了减少对IO的依赖。

                                          #! /usr/bin/env perl
                                          use Modern::Perl;
                                          use autodie;
                                          
                                          open our $null, '>', '/dev/null';
                                          
                                          use Benchmark qw':all';
                                          
                                          cmpthese( -10,
                                          {
                                            Recursive => sub{ Collatz_r( 31 ) },
                                            Iterative => sub{ Collatz_i( 31 ) },
                                            Optimized => sub{ Collatz_o( 31 ) },
                                          });
                                          
                                          sub Collatz_r{
                                            ...
                                            say {$null} $n;
                                            ...
                                          }
                                          sub Collatz_i{
                                            ...
                                            say {$null} $n;
                                            ...
                                          }
                                          sub Collatz_o{
                                            ...
                                            say {$null} $n;
                                            ...
                                          }
                                          

                                          运行 10 次后,这是一个有代表性的示例输出:

                                          速率递归迭代优化 递归 1715/s -- -27% -46% 迭代 2336/s 36% -- -27% 优化 3187/s 86% 36% --

                                          最后,一个真正的代码高尔夫入口:

                                          perl -nlE'say;say$_=$_%2?3*$_+1:$_/2while$_>1'
                                          

                                          总共 46 个字符

                                          如果您不需要打印起始值,您可以再删除 5 个字符。

                                          perl -nE'say$_=$_%2?3*$_+1:$_/2while$_>1'
                                          

                                          总共 41 个字符
                                          实际代码部分有 31 个字符,但如果没有 -n 开关,代码将无法工作。所以我把整个例子都算进去了。

                                          【讨论】:

                                          • 你的优化版本不是。
                                          • @Motti 这些示例非常依赖于 IO。经过多次测试,优化后的版本始终保持显着领先。
                                          • @Brad,当您在一个数字上运行 Collat​​z 时,优化是一种悲观,因为没有数字应该出现多次(除非猜想是错误的)。你看到改进的原因是你运行了很多数字(如欧拉问题),事实上我最近写了一篇关于这个的博客文章lanzkron.wordpress.com/2010/01/18/…
                                          • @Motti 这就是我所说的优化。此外,在 Perl 中 $i + 1总是添加(响应博客条目)。同样使用Sub::Call::Recur 也是一种优化。否则我会使用@_=$n;goto &amp;Collatz。 (如果将state @next 更改为my @next,则会慢10-20%
                                          • 我相信 perl 高尔夫中风计数标准不计算调用解释器或引号的强制中风,但确实为 E 旁边的每个标志计算一个。使用这些规则,您的最后一个条目分别计算 37 个字符和 32 个字符。
                                          【解决方案30】:

                                          bash 57/61/60

                                          另一个 bash 条目。不执行无限精度数学,它可能会溢出。

                                          #!/bin/bash
                                          x=$1;echo $x;((x>1))&&$0 $((x%2?x*3+1:x/2))
                                          

                                          一个不应该溢出的版本可能是

                                          #!/bin/bash
                                          x=$1;echo $x;((x>1))&&exec $0 $((x%2?x*3+1:x/2))
                                          

                                          (编辑)还有一个迭代版本:

                                          #!/bin/bash
                                          for((x=$1;x>1;x=x%2?x*3+1:x/2));do echo $x;done
                                          

                                          【讨论】:

                                            猜你喜欢
                                            • 2017-06-28
                                            • 1970-01-01
                                            • 1970-01-01
                                            • 2019-01-03
                                            • 2010-12-17
                                            • 1970-01-01
                                            • 1970-01-01
                                            • 2010-12-01
                                            • 1970-01-01
                                            相关资源
                                            最近更新 更多