今天看到TW发了道FizzBuzzWhizz的面试题。这道题其实就是逢3过
的高级版,手痒痒来解一下。。
- 你首先说出三个不同的特殊数,要求必须是个位数,比如3、5、7。
- 让所有学生拍成一队,然后按顺序报数。
- 学生报数时,如果所报数字是第一个特殊数(3)的倍数,那么不能说该数字,而要说Fizz;如果所报数字是第二个特殊数(5)的倍数,那么要说Buzz;如果所报数字是第三个特殊数(7)的倍数,那么要说Whizz。
- 学生报数时,如果所报数字同时是两个特殊数的倍数情况下,也要特殊处理,比如第一个特殊数和第二个特殊数的倍数,那么不能说该数字,而是要说FizzBuzz, 以此类推。如果同时是三个特殊数的倍数,那么要说FizzBuzzWhizz。
- 学生报数时,如果所报数字包含了第一个特殊数,那么也不能说该数字,而是要说相应的单词,比如本例中第一个特殊数是3,那么要报13的同学应该说Fizz。如果数字中包含了第一个特殊数,那么忽略规则3和规则4,比如要报35的同学只报Fizz,不报BuzzWhizz。
说实话,作为程序员,如果解不出这道题,真的该考虑改行了。号称最难面试的TW考察的肯定不是仅仅能正确跑出结果。我觉得这道题的得分项在这里:
强烈建议写单元测试
,这个没什么说的;请展示出你超赞的面向对象/函数式编程功底
,coding是一种武功,不同的领悟打出不同的招式和威力;建议尽量减少圈复杂度
,我想最让人崩溃的就是看一坨充满了for/if/switch的代码;
好了,来看题。。 这道题其实就是把输入(一个数字)经过一系列的规则转换成Fizz/Buzz/Whizz,来看看这些rule:
- 如果这个数包含了第一个特殊数,输出Fizz;
- 如果这个数是某个特殊数的倍数,输出相应单词;
- 直接输出原数;
估计很多人到这就直接开始写分支判断了。其实这三个规则存在这样的优先级:rule1 > rule2 > rule3,而在javascript的||
操作符正好适用,这样也就避免了代码里充斥分支语句。基于这样一个规则表,我们就可以写出代码了。高亮版
var _ = require('underscore');
var special_words = ["Fizz", "Buzz", "Whizz"];
var candidates = seq(9); // 1..9
// select 3 random numbers from candidates.
var special = _(3).times(function(i) {
var selected = candidates[_.random(candidates.length-1)];
candidates = _.without(candidates, selected);
return selected;
});
console.log(special);
var input = seq(100); // 1..100
// main
var output = _.map(input, function yamete(n) {
return rule1(n) || rule2(n) || rule3(n);
});
console.log(_.zip(input, output));
function rule1(n) {
return new RegExp(special[0] + '').test(n + '') ? special_words[0]:false;
}
function rule2(n) {
return _.reduce(special, function(memo, s){
this.i++;
return memo + ((n%s) ? '' : special_words[this.i]);
}, '', {i:-1});
}
function rule3(n) { return n; }
function seq(n) { return _(n).times(function(n){return n+1}); }
好吧,如果TW招人是为了帮他们写更短的代码的话,我就无话可说了。其实这个题要是放在某某hack大赛上,更短的代码肯定是更有意思。任何实现代码都必须完成这道题里的点(循环,字符串查找,倍数判断等),如果某种语言有库或是语法能更简短的实现这里的点,那就可以写出你所谓的精。甚至我还可以依此问题域设计一门DSL,只用一行代码就可以解题了。。^^
@coordcn , @lujb說的不錯,代碼一定是要maintainable的. 你的解題思路很好, 我認為是包含的關係
1. 你首先说出三个不同的特殊数,要求必须是个位数,比如3、5、7。
2. 让所有学生拍成一队,然后按顺序报数。
3. 学生报数时,如果所报数字是第一个特殊数(3)的倍数,那么不能说该数字,而要说Fizz;如果所报数字是第二个特殊数(5)的倍数,那么要说Buzz;如果所报数字是第三个特殊数(7)的倍数,那么要说Whizz。
4. 学生报数时,如果所报数字同时是两个特殊数的倍数情况下,也要特殊处理,比如第一个特殊数和第二个特殊数的倍数,那么不能说该数字,而是要说FizzBuzz, 以此类推。如果同时是三个特殊数的倍数,那么要说FizzBuzzWhizz。
5. 学生报数时,如果所报数字包含了第一个特殊数,那么也不能说该数字,而是要说相应的单词,比如本例中第一个特殊数是3,那么要报13的同学应该说Fizz。如果数字中包含了第一个特殊数,那么忽略规则3和规则4,比如要报35的同学只报Fizz,不报BuzzWhizz。
以下用python寫的(點這裡)
#!/usr/bin/env python
import sys
CASE1 = 1
CASE2 = 5
CASE3 = 7
WORD1 = 'Fizz'
WORD2 = 'Buzz'
WORD3 = 'Whizz'
def filter(num):
if str(CASE1) in str(num):
print(WORD1)
return
if num % CASE1 == 0:
sys.stdout.write(WORD1)
if num % CASE2 == 0:
sys.stdout.write(WORD2)
if num % CASE3 == 0:
sys.stdout.write(WORD3)
def main():
filter(int(raw_input('Please input a number: ')))
if __name__ == '__main__':
main()
@lujb 我想TW出这题目也不是要找代码写得更短的人,而是要找对语言特性了解很深的人。这题目还有一个难点就是写测试代码,这个如何测?用自己的算法测试自己的结果,显然不行。自己苦哈哈的从1到100报一次?这是程序员做的事情么?希望活动结束后TW能公布出来,让大家见识见识,大牛的解题思路。 我弄不明白解这么个小问题,你要引入一个库?难道这就是传说中的可维护?你跟for/if/switch有仇么?必要的时候,我还很喜欢goto呢。。。任何美丽的东西都是由代价的,女人如此,代码也是如此,你引入的库也没少用for/if/switch,人家用得,我们自然也用得。 讽刺人什么的最没意思了,弄得我去搜了下DSL是什么意思,今天又学了个新知识,叫领域专用语言,谢谢了。可惜我实在没那个本事啊,实现不了一行代码,一统江湖的夙愿。
@coordcn 我的意思是没有必要过分沉迷于更短的代码(任何代码最终都是翻译成机器码在CPU上跑),特别是在招聘的场合上。至于你说的引入第三库,如果这个库是这门语言的标准库呢。反驳你不是说你错,只是我们考虑问题的出发点不一样,根本没有讽刺人啥的,哥们别多心哈。。
@lujb 你误解了我好几个意思,其一,我并没有说代码越短越好,短得无法交流那是卖弄,代码首先是要给人读的,能用朴实精炼的代码正确的实现题意恐怕是出题人强调10行的本意。其二,我并没有说不能引入第三方库,而是觉得这个情况下,没必要引入,很简单一道题目,一个循环,几个判断就完事的,却要引入一个第三方库,你的目的是什么?难道就是为了避免使用for/if/switch?在某种情况下库即语言,你引入一些库其实就是在创造语言,你引用的库差不多就是这个感觉了,到底谁在设计一门语言呢?我按照你这个思路实现一行代码,一统江湖的梦想说不定真可以,例如:require(‘xx’)(3, 5, 7);这有意思么?你的话还不够讽刺?
我回复你的最主要原因就是看到了你对for/if/switch的不屑,就这个几个词大多数程序员都是靠它们吃饭的,我看了你的github才发现你是搞erlang的。但是我想说的是,语言跟语言真的不一样,我们不能用一个标准去要求所有语言。我也学过一段时间erlang,很喜欢,但是自觉智商不够,就没有再深入。
erlang里面一些特性nodejs现在慢慢也能模拟了,不过大体上还是没有erlang做得好,尤其在可靠性上,两个不是一个数量级的,在对多核的支持上两者相差也很大。nodejs的优势在于简单易用,前后端通吃,在创业公司里,能少用一个人那都是极好的。
再回过头来说这个第三方库,如果仅仅是为了避免几个关键词的使用,引入了一个不大不小的第三方库,这跟软件的可维护性有半毛钱关系么?当然有半毛钱关系,那就是引入的第三方库增加了软件的维护成本,如果没选好,发了bug自己有没办法修复,项目夭折的可能性都有。
// 获得用户输入
var nums = process.argv[2];
if (!nums) die('please enter three numbers. example: 3,5,7');
nums = nums.split(',');
if (nums.length !== 3) die('please enter three numbers. example: 3,5,7');
nums.forEach(function (n) {
if (!(n > 0 && n < 10)) die(n + ' is not an valid number. must be greater than 0 and less than 10');
});
function die (msg) {
console.log(msg);
process.exit(-1);
}
function print (msg) {
console.log(msg);
}
/******************************************************************************/
var a = nums[0], b = nums[1], c = nums[2];
var A = 'Fizz', B = 'Buzz', C = 'Whizz';
function isIn (i, n) {
return (i.toString().indexOf(n) !== -1);
}
function isTimes (i, n, s) {
return (i % n === 0) ? s : '';
}
// 计算
for (var i = 1; i <= 100; i++) {
if (isIn(i, a)) {
print(A);
} else {
var s = isTimes(i, a, A) + isTimes(i, b, B) + isTimes(i, c, C);
print(s || i);
}
}
@coordcn 我是看到你说的10行代码的精,才认为·你觉得精的代码都是短·,如果是这样,那我确实误解你了,罪过罪过。窃以为,如果具备解决问题的能力,对于问题,首先分析出问题的关键点,然后给出策略予以解决,仅此而已,只是不同的策略有不同的效果(可维护性,可拓展性等),这也是我发此帖子的出发点(给出问题的关键点,然后用什么策略去解决)。
另外,对于消除for/if/switch,是我在看到TW的要求:建议尽量减少圈复杂度
的前提下来做的,我这里写的可能意义不大,但是对于大的项目而言,减少圈复杂度
确实是软件工程中所极力推崇的。Erlang中也会用if/case什么的,当代码中开始充斥大量的分支语句时,也是时候考虑重构了。消除分支语句,不是说你的程序最终编译链接出的目标码连跳转的指令都没有(这显然也是不可能的),而只是说保证你写的代码中没有大量杂乱的分支语句,把这些繁琐难维护的地方交给可靠的库去处理,这样我们的代码就会干净很多。此外,你说·引入一些库其实就是在创造语言·,这点我不是很同意。在这里我用到的underscore,主要是用一些基本的函数式功能,要知道,在Erlang这样的语言中,underscore的大部分函数都是标准库。(话说我刚开始确实准备用pegjs写一个简单的parser,但后来觉得实在是太小题大做了,会贻笑大方的^^)
最后,对于这个题,如果只是说要解的话,那10行代码确实够精了,我是故意往我觉得这道题的得分项
上靠的。。
@lujb 我想最让人崩溃的就是看一坨充满了for/if/switch的代码; 这是你的原话,实在抱歉,这样的表述,我看不出跟软件工程,重构有什么联系。现在可算明白了,你这么写的初衷。那你软件工程的目的达到了么?需求变动了,你确信你改动的代码一定就比ifelse少?请不要忘记软件工程的目标,为了软件工程而软件工程就没劲了。
underscore === pegjs ?
我觉得你用Erlang再解一下这个题倒是不错的。
@coordcn 我严重怀疑你的理解能力了。请注意·我想最让人崩溃的就是看一坨充满了for/if/switch的代码·是我用来表述建议尽量减少圈复杂度
这点的,这是TW的要求,你能不能关注下上下文信息?圈复杂度就是软件工程中的,不知道的话能不能先搜索一下再来讨论?underscore===pegjs ? 我去,你这神理解啊!你说·引入库其实就是创造语言·,所以我就提了下我开始是打算用pegjs写个DSL的解析器!就此打住吧,在这里继续讨论下去没任何意义了。
@lujb 在某种情况下库即语言,你引入一些库其实就是在创造语言,你引用的库差不多就是这个感觉了 看清楚我的原话。
概念这个东西最能忽悠人了,我们能不能不摆弄概念,看看实际效果?我不是计算机专业的,今天可真学了不少东西,又是软件工程,又是重构,又是圈复杂度,当然还有那个印象最深的DSL,我是弄明白了,原来是你自己要DSL一把。你好人做到底,这题目的圈复杂度是多少?按照你的方法降低了多少?
underscore===pegjs ? 你真没明白我的意思?我也能讽刺人的。。。
@coordcn 我是觉得,楼主的代码,我没有看下去的欲望。首先是自己没用过underscore,到处是 _
,跟自己的代码习惯差距太大。其次是不觉得这么简单的问题,需要额外引入一个库(不是能看懂JS代码就行的么?!)
@leizongmin 交流绝对是最主要的。我这个人有个坏毛病,喜欢跟人叫板。但是我是真心喜欢朴实无华的代码,反对过度编程,这个心情估计就跟楼主看到到处ifelse一样,管不住嘴,乱喷一气,还望楼主海涵。跟楼主叫板这一天真学到了不少东西。
我倒是绝对楼主用Erlang来写可能更合适。语言模拟语言毕竟是隔靴搔痒,很是不爽啊。所以我对coffe什么的也不感冒,看到这样的源码直接翻译过来读起来爽啊。
###C#解决方案,全文20行,实现方法不超过10行,无if,else,while条件语句。
using System;
using System.Linq;
namespace FizzBuzzWhizz
{
class Program
{
static void Main(string[] args)
{
Output(Console.ReadLine());
Console.ReadLine();
}
static void Output(string recive)
{
int[] Input = recive.Split(',').Select(i => Convert.ToInt32(i)).ToArray();
for (int i = 0; i <= 100; i++)
{
Console.WriteLine(i.ToString().Contains(Input[0].ToString()) ? "Fizz" : (0 != i % Input[0] && 0 != i % Input[1] && 0 != i % Input[2]) ? i.ToString() : (0 == i % Input[0] ? "Fizz" : "") + (0 == i % Input[1] ? "Buzz" : "") + (0 == i % Input[2] ? "Whizz" : ""));
}
}
}
}
我感觉减少循环次数是个坑,如果都按照100×3的循环太没有创意了。。 构造答案才是王道啊。还有你们都考虑了0和负数的情况吗 function zz(a, b, c) { var result = new Array(101); add(a, result, ‘Fizz’); add(b, result, ‘Buzz’); add(c, result, ‘Whizz’); if(a<0)return result; for(var i=0;i<=9;i++){ v1 = Number(i+’’+a); result[v1] = 'Fizz’; if(a == 0) continue; v2 = Number(a+’’+i); result[v2] = 'Fizz’; } if(a==0)result[100] = 'Fizz’; return result; }
function add(n, a, v) { n = Math.abs(n); if(n == 0) return; for(var i = n;i<=100;i++){ if(a[i]) a[i] += v; else a[i] = v; } }