FizzBuzz是一个非常简单的问题,它的那些比较简短的解答往往不可扩展,抽象也用得少——这让不同语言的解答间的比较不那么有趣。
FizzBuzzBazz是FizzBuzz的一个变体,增加了一条规则——7的倍数输出Buzz
。例如,数字35将输出BuzzBazz
,因为它既是5的倍数,也是7的倍数。FizzBuzzBazz加入了足够的复杂性,使得不同语言的解答值得比较。
我向你发出挑战,你可以任选语言实现FizzBuzzBazz,然后找到最短的实现。你可以直接在下面评论,也可以发twitter(带上#fizzbuzzbazz
)——我会更新此页面,展示最短的程序。
当前赢家 Perl, 53字符。
FizzBuzzBazz规则如下
Fizz
Buzz
Bazz
FizzBazz
,因为它既是3的倍数,又是7的倍数。我的解答使用LiveScript编写,64个字符(你能用你选择的语言刷新记录么?):
[1 to 100]map ->[k+\\zz for k,v of{Fi:3,Bu:5,Ba:7}|it%v<1]*\'\'||it
这不是好的编码风格——为了求短。
LiveScript是一个编译为JavaScript的语言。它和JavaScript直接对应,允许你编写表达力强的代码,不用重复编写那些样版代码。虽然LiveScript添加了很多有助于函数式编程的特性,但是在面向对象编程和面向过程编程方面也有很多改进。
现在,我来解释一下上面的代码:
首先我们输出1到100的列表。
[1 to 100]
我们将对列表中的每个成员应用相同的规则,以便创建一个新列表,因此我们使用map
函数。
[1 to 100].map(...)
map
以函数为输入,这个函数会被应用到列表中的每个成员。传递给map
的函数必须是单一输入和单一输出的函数。在LiveScript中,函数通过箭头定义,这个箭头从参数指向函数体。
[1 to 100].map((x) -> ...)
我们使用对象表示我们的规则:
{Fizz: 3, Buzz: 5, Bazz: 7}
我们需要将这组规则转换成输出的结果,由于一个数字可能是多个数字的倍数,因此它可能符合多条规则。因此,我们必须过滤对象并生成结果列表,一个空列表表示没有一条规则适用。我们可以使用列表解析来完成,使用of
的话我们能得到键和值。我们打算通过将特定的规则组合应用到值(例如 3)上来输出键(例如 Fizz
)。格式为[输出 for 值 of 输入 when 条件]
。
[key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when ...]
条件很明显。如果相除余数为0,那么一个数字就是另一个数字的倍数。
[key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0]
这会生成一个列表。但是我们需要的是字符串,所以我们需要join
一下。
[key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join(\'\')
将这些组合起来——注意,LiveScript隐式返回函数体中的最后一个表达式(除非另有声明),所以不需要return
语句。
[1 to 100].map((x) ->
[key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join(\'\')
)
以上代码中,如果所有规则都不符合,会输出一个空字符串,但是我们希望输出数字。由于JavaScript中,空字符串是假的,所以我们可以使用or
操作符。
[1 to 100].map((x) ->
[key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join(\'\') or x
)
好了,我们已经完成了解答。但是它有点长——109个字符,我们来压缩一下。
首先,大多数情况下,调用函数都不需要括号。
[1 to 100].map (x) ->
[key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join(\'\') or x
用k
表示key
,v
表示value
,这样又可以省下几个字符。
[1 to 100].map (x) ->
[k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} when x % v == 0].join(\'\') or x
如果一个函数只有一个输入,我们可以使用it
来隐式地指代它,而不用显式地声明。所以我们用it
替换掉x
。
[1 to 100].map ->
[k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} when it % v == 0].join(\'\') or it
如果*
(乘法)操作符的右边是一个字符串的话,那么最终效果就是将左边的列表连接起来。我们用这个技巧替换掉.join(\'\')
。
[1 to 100].map ->
[k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} when it % v == 0] * \'\' or it
|
是when
的别称。
[1 to 100].map ->
[k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} | it % v == 0] * \'\' or it
我们处理的是整数,也不用担心负数,所以< 1
和== 0
等价。
[1 to 100].map ->
[k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} | it % v < 1] * \'\' or it
干得不错。但是还可以更进一步。在不会引起歧义的情况下,.
是可选的。
[1 to 100]map ->
[k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} | it % v < 1] * \'\' or it
zz
重复出现了,我们重构下,使用\\word
形式表达字符串,比\'word\'
节约一个字符:
[1 to 100]map ->
[k + \\zz for k, v of {Fi: 3, Bu: 5, Ba: 7} | it % v < 1] * \'\' or it
最后,我们将空格移除,由于or
需要前后空格,所以我们用||
替换。
[1 to 100]map ->[k+\\zz for k,v of{Fi:3,Bu:5,Ba:7}|it%v<1]*\'\'||it
这就是最后的解答:64个字符。
注意我们的方案扩展性相当强,因为我们使用数据表示规则,而不是使用过程式代码。
感谢Ian Barfield的解答,仍然是LiveScript,但是更短(62字符):
[++x>?[k+\\zz for k,v of{Fi:3,Bu:5,Ba:7}|x%v<1]*\'\' for x to 99]
谁能像Perl那样惜字如金?
map+(Fizz)[$_%3].(Buzz)[$_%5].(Bazz)[$_%7]||$_,1..100
来自Donnie Cameron
Davide Griffon提供了相同思路在Python中的实现:
[\'\'.join([w+\'zz\'for n,w in{3:\'Fi\',5:\'Bu\',7:\'Ba\'}.items()if x%n<1])or x for x in range(1,101)]
Pedro Rodrigues将LiveScript代码缩短到了60字符:
[\\Fizz *!(++x%3)+\\Buzz *!(x%5)+\\Bazz *!(x%7)||x for x to 99]
但是这个解答丧失了可扩展性,如果增加规则的话,代码长度将丧失优势。
原文 The shortest FizzBuzzBazz – can you do better?
编译 SegmentFault