FizzBuzz是一个非常简单的问题,它的那些比较简短的解答往往不可扩展,抽象也用得少——这让不同语言的解答间的比较不那么有趣。
FizzBuzzBazz是FizzBuzz的一个变体,增加了一条规则——7的倍数输出Buzz
。例如,数字35将输出BuzzBazz
,因为它既是5的倍数,也是7的倍数。FizzBuzzBazz加入了足够的复杂性,使得不同语言的解答值得比较。
我向你发出挑战,你可以任选语言实现FizzBuzzBazz,然后找到最短的实现。你可以直接在下面评论,也可以发twitter(带上#fizzbuzzbazz
)——我会更新此页面,展示最短的程序。
当前赢家 Perl, 53字符。
FizzBuzzBazz规则如下
- 输出1到100的数字——下列情况除外:
- 3的倍数输出
Fizz
- 5的倍数输出
Buzz
- 7的倍数输出
Bazz
- 任何上述情况的组合,输出组合的字符串。例如,21输出
FizzBazz
,因为它既是3的倍数,又是7的倍数。 - 结果列表表达式的形式是可接受的(事实上也是推荐的形式)。
Solution in LiveScript
我的解答使用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个字符。
注意我们的方案扩展性相当强,因为我们使用数据表示规则,而不是使用过程式代码。
LiveScript改进 62字符
感谢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 53 字符
谁能像Perl那样惜字如金?
map+(Fizz)[$_%3].(Buzz)[$_%5].(Bazz)[$_%7]||$_,1..100
来自Donnie Cameron
其他解答
Python 93 字符
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)]
LiveScript 60字符
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