编程竞技:考拉兹猜想

86

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

挑战

给定一个大于0的正整数,输出它的Hailstone序列。

Hailstone序列

详见Wikipedia

  • 如果数是偶数,则将其除以2。
  • 如果数是奇数,则将其乘以3再加1。

重复以上步骤,直到产生的数等于1(如果继续进行下去,它将进入一个无限循环 1 -> 4 -> 2 -> 1...)。

有时候通过代码来解释可能是最好的方式,以下是来自Wikipedia的一些代码示例:

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

此外,代码高尔夫必须包括完整的用户输入和输出。


20
“must not be vulnerable to stack overflows”的意思是“不能容易受到堆栈溢出攻击”。而你说的“那你就不应该在这里发布了!”是一种玩笑话语,暗示讨论的内容可能存在安全漏洞。 - Felix Kling
51
我的朋友不再打电话给我了,这是否意味着我已经解决了问题? - Martin
18
你在SO上,但曾经有朋友吗?那是什么感觉? - Pops
5
汇编语言的回答很棒,但选择最长的答案有点反对代码高尔夫 - John La Rooy
显示剩余7条评论
70个回答

3

C#:支持BigInteger的659个字符

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);
        }
    }
}

3

JavaScript - 68个字符

与其他JS(以及大多数其他语言)不同,它实际上遵循输出中的->

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

如果我们避免这种情况,这是一个53字符的替代方案,每行打印一个数字:
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

3

Windows命令提示符 - 68个字符

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

2

MATLAB 7.8.0 (R2009a): 58 characters

n=input('');while n>1,n=n/2+rem(n,2)*(n*5+2)/2;disp(n);end

测试用例:

>> n=input('');while n>1,n=n/2+rem(n,2)*(n*5+2)/2;disp(n);end
21
    64
    32
    16
     8
     4
     2
     1

1
嘿,gnovice,我们可以用以下代码避免 disp(n)n=input('');while n>1,n=n/2+rem(n,2)*(n*5+2)/2,end --- *50 characters! - Jacob
@Jacob:是的,那样做可以。但我试图保持与问题中相当格式良好的输出合理接近。;) - gnovice

2

Fortran: 71 chars

n=1
1 if(n==1)read*,n
n=merge(n/2,3*n+1,mod(n,2)==0)
print*,n
goto1
end

因为有人必须要做这件事情 :)
计数包括必需的换行符。 完全符合Fortran 95(及更高版本)的代码。 包含完整的I/O,并且可以执行多次!
编辑:使用goto少了一个字符(赢得了一些风格!)

2

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&1返回的是10,我们可以利用PHP的松散类型,再省略几个字符。
  • 此外,结合下面克里斯蒂安的评论(稍作修改以防止无限循环),我们可以再去掉一个字符。
  • 最后,由于PHP脚本不需要终止符?>,我们可以再省略两个字符:

最终结果:

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

建议更改为“while($n>0)”以符合“给定一个大于0的正整数”的要求。 - Christian
@Christian Close; 为了防止无限循环,必须使用 while($n>1),但是建议很好。 - user212218

2

Javascript, 67 56 chars

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

为什么要浪费字符在 var 上?输出中也不需要包括 ->。使用三元运算符代替数组索引还可以再节省 3 个字符。 - gnarf
好的。我已经进一步压缩了它。'->'并不是非常有趣的部分,大多数解决方案都省略了它。只需用alert(a.join(' -> '))替换alert(a)即可。 - Marko Dumic

2

Ruby,41个字符

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

2

由于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 <loopname>IM OUTTA YR <loopname> 之间执行。
  • VISIBLE 用于向 STDOUT 输出打印信息。
  • O RLY?YA RLYOIC 语句处理条件 If/Then/Else 逻辑。
  • WTF?OMG <expression>OIC 语句处理条件 Switch/Case 逻辑。
  • 使用 <variable> R <value> 进行赋值操作。
  • 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 <funcname> YR <args>IF U SAY SO语句中函数的定义。函数使用<funcname> <args>进行调用。

@Earlz 我想展示代码的最小字符形式(不带缩进),但是算了,我会添加它的。 :) - user359512

1

Python:

def collatz(n):
    if (n%2) == 0:
        return n/2
    else:
        return 3*n+1
def do_collatz(n):
    while n > 1:
        print n
        n = collatz(n)
    print n
do_collatz(int(input("Start number: ")))

不会受到堆栈溢出的影响,但在不收敛于1的序列上不会终止。

14
做得好,Raceimaztion。现在作为一个额外挑战,只需证明您的程序始终会终止,或者找到一个它不会终止的例子。 - jsn
1
@jsn,也许我应该做一个代码高尔夫来找到不行的数字,并看着人们受苦 :) - Earlz
@Earlz 那更像是一种“证明高尔夫”。 - Josh Lee
3
我曾经读过一位教授,在高级课程的期末考试中会把那些未解决的问题作为问题出题,偶尔会有学生解决了其中一个…… - rmeador
没有这样的数字不会收敛到1。 - Kugel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接