解解FizzBuzzWhizz这道题
发布于 10 年前 作者 lujb 6614 次浏览 最后一次编辑是 8 年前

今天看到TW发了道FizzBuzzWhizz的面试题。这道题其实就是逢3过的高级版,手痒痒来解一下。。

  1. 你首先说出三个不同的特殊数,要求必须是个位数,比如3、5、7。
  2. 让所有学生拍成一队,然后按顺序报数。
  3. 学生报数时,如果所报数字是第一个特殊数(3)的倍数,那么不能说该数字,而要说Fizz;如果所报数字是第二个特殊数(5)的倍数,那么要说Buzz;如果所报数字是第三个特殊数(7)的倍数,那么要说Whizz。
  4. 学生报数时,如果所报数字同时是两个特殊数的倍数情况下,也要特殊处理,比如第一个特殊数和第二个特殊数的倍数,那么不能说该数字,而是要说FizzBuzz, 以此类推。如果同时是三个特殊数的倍数,那么要说FizzBuzzWhizz。
  5. 学生报数时,如果所报数字包含了第一个特殊数,那么也不能说该数字,而是要说相应的单词,比如本例中第一个特殊数是3,那么要报13的同学应该说Fizz。如果数字中包含了第一个特殊数,那么忽略规则3和规则4,比如要报35的同学只报Fizz,不报BuzzWhizz。

说实话,作为程序员,如果解不出这道题,真的该考虑改行了。号称最难面试的TW考察的肯定不是仅仅能正确跑出结果。我觉得这道题的得分项在这里:

  • 强烈建议写单元测试,这个没什么说的;
  • 请展示出你超赞的面向对象/函数式编程功底,coding是一种武功,不同的领悟打出不同的招式和威力;
  • 建议尽量减少圈复杂度,我想最让人崩溃的就是看一坨充满了for/if/switch的代码;

好了,来看题。。 这道题其实就是把输入(一个数字)经过一系列的规则转换成Fizz/Buzz/Whizz,来看看这些rule:

  1. 如果这个数包含了第一个特殊数,输出Fizz;
  2. 如果这个数是某个特殊数的倍数,输出相应单词;
  3. 直接输出原数;

估计很多人到这就直接开始写分支判断了。其实这三个规则存在这样的优先级: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}); }
22 回复

这题的关键点不是在解,而是要解得精,那个10行代码是关键。

好吧,如果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);
  }
}

@leizongmin 简介明了,我的那个在TW的招聘贴里。比你的稍微差点,最后一句print用三元了,还是 || 运算符够简洁,赞一个。

@coordcn 我是看到你说的10行代码的精,才认为·你觉得精的代码都是短·,如果是这样,那我确实误解你了,罪过罪过。窃以为,如果具备解决问题的能力,对于问题,首先分析出问题的关键点,然后给出策略予以解决,仅此而已,只是不同的策略有不同的效果(可维护性,可拓展性等),这也是我发此帖子的出发点(给出问题的关键点,然后用什么策略去解决)。

另外,对于消除for/if/switch,是我在看到TW的要求:建议尽量减少圈复杂度的前提下来做的,我这里写的可能意义不大,但是对于大的项目而言,减少圈复杂度确实是软件工程中所极力推崇的。Erlang中也会用if/case什么的,当代码中开始充斥大量的分支语句时,也是时候考虑重构了。消除分支语句,不是说你的程序最终编译链接出的目标码连跳转的指令都没有(这显然也是不可能的),而只是说保证你写的代码中没有大量杂乱的分支语句,把这些繁琐难维护的地方交给可靠的库去处理,这样我们的代码就会干净很多。此外,你说·引入一些库其实就是在创造语言·,这点我不是很同意。在这里我用到的underscore,主要是用一些基本的函数式功能,要知道,在Erlang这样的语言中,underscore的大部分函数都是标准库。(话说我刚开始确实准备用pegjs写一个简单的parser,但后来觉得实在是太小题大做了,会贻笑大方的^^)

最后,对于这个题,如果只是说要解的话,那10行代码确实够精了,我是故意往我觉得这道题的得分项上靠的。。

想去TW试试的话,赶紧投了,^_^

@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代码就行的么?!)

@lujb 在这里贴代码的人跟你的underscore===pegjs心态其实是一样的,卖弄一下就算了,真要投了不都得偷偷摸摸的?

@coordcn 不算卖弄吧,就是好奇别人都是怎么写的……交流一下

@leizongmin 交流绝对是最主要的。我这个人有个坏毛病,喜欢跟人叫板。但是我是真心喜欢朴实无华的代码,反对过度编程,这个心情估计就跟楼主看到到处ifelse一样,管不住嘴,乱喷一气,还望楼主海涵。跟楼主叫板这一天真学到了不少东西。

我倒是绝对楼主用Erlang来写可能更合适。语言模拟语言毕竟是隔靴搔痒,很是不爽啊。所以我对coffe什么的也不感冒,看到这样的源码直接翻译过来读起来爽啊。

@leizongmin 謝邀,小弟才疏學淺就不亂說什麼了。

@coordcn 赞同朴实无华,我也不喜欢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; } }

回到顶部