文档章节

ThoughtWorks面试题:FizzBuzz

翟志军
 翟志军
发布于 2014/05/04 13:48
字数 2223
阅读 5211
收藏 71

FizzBuzzWhizz

题记

ThoughtWorks这次招人似乎有些狠。除了在微博上下大功夫,还和拉勾网、OSC合作。招人的方式也有所调整,先交代码,才有机会接到面试电话。我想他们的嗅觉应该很灵敏。哈。代码臭味过不了他们的鼻子。

我比较喜欢参加比赛,所以,怎么会落下这次机会。以下博客是我尝试阐述我的分析与设计。

使用文字将分析设计的过程说明清楚是一件很困难的事情。因为大脑运转的速度实在太快,一边动脑,一边如实地将大脑所想记录下来,我觉得是非常非常困难的。所以,我不理解意识流的文学作品到底是怎么写出来的。

所以,我采用另一种方式来说明我的分析设计过程:对比法。就是拿一个较差的实现与我觉得还可以的实现进行对比。

我假设大家都已经看过题目,如果没有看到题目,请点这里

第一种实现

这是我自己特意写的比较差的实现,如有雷同属巧合:

        public class BadImplements {

            public static void main(String[] args) throws Exception {
                report(3, 5, 7);
            }

            public static void report(int s1, int s2, int s3) {
                for (int i = 1; i <= 100; i++) {

                    //如果包含了第一个特殊数,则报第一个单词
                    if (("" + i).contains(("" + s1))) {
                        System.out.println("Fizz");
                        continue;
                    }

                    StringBuilder resultBuilder = new StringBuilder();

                    if (i % s1 == 0) {
                        resultBuilder.append("Fizz");
                    }

                    if (i % s2 == 0) {
                        resultBuilder.append("Buzz");
                    }

                    if (i % s3 == 0) {
                        resultBuilder.append("Whizz");
                    }

                    //不是任何特殊的倍数
                    if ("".equals(resultBuilder.toString().trim())) {
                        System.out.println(i);
                        continue;
                    }

                    System.out.println(resultBuilder.toString());
                }
            }
        }

因为如果符合第五条规则,第三、四条规则都会失效,所以,符合第五条规则后,本次循环直接返回。第三、四条规则使用3个if也实现了。 咋一看,没问题,运行起来,也没问题,而且,也没有任何多余的代码,看似也有些小优雅。

唯一不足可能就是没有提供用户接口(命令行输入或界面等),实事上,我觉得这部分相对游戏的核心业务逻辑不重要。

但是,我觉得是有问题的,问题出现在哪呢?

当报数的学生从100变成500呢?

这个问题倒还容易解决:

      public static void report(int s1, int s2, int s3, int startNumber, int endNumber) {
                         for (int i = startNumber; i <= endNumber; i++) {

这样写,同样是可以运行的,但带来了新的问题。当然,你也可以认为没有问题。

我认为的新问题是:report参数过多。当你站在一个代码维护者的角度来看:report(2,6,9,1,700);,我相信你是这种表情:

猫玩魔方

这个问题,还算是小问题,当客户提出,他们想允许用户输入9个特殊数,特殊数对应的单词也都变了,你可能这种表情:

看别人的代码

你好不容易为report加上多出的特殊数参数,调用的时候就变成了:report(1,2,3,4,5,6,7,8,1,999);。这下,我很难想像代码维护者的表情了。

某天,客户又提出,希望报告结果能输出到数据库中时,你可能就要崩溃了。

以上是有夸张的虚构的因素在里面,毕竟这是一个小小的测试题,没那么严重。你可以把report设计成:report(int startNumber,int endNumber, int... specialNumber)。 这样就可以适应客户提出特殊数方面的所有需求了,如特殊数的个数变了等。

但是在大型的业务项目里,这一点不夸张。客户的需求总是不那么清晰,看看下面这图,你就明白了:

温伯格的《探索需求》的插图

如果你经历过大型项目,不用看图,你就理解我的意思了。

第二种设计

ThoughtWorks的打擂题,当然不只是想看到一个刚刚能运行的程序,我想他们希望能看到你在分析、设计、写代码上的功底。但是,猜我都猜到一定有 人会卖弄各种设计模式,我觉得这样的代码更恐怖!

解决一个问题的时候,我习惯性地会去分析它的本质,也就是核心问题在哪里?

经分析,它核心问题在于当你得到一个数字后,你需要根据一定的规则来决定,你是应该是报数字,还是单词。因为,报告的导出方式(是打印在命令行,还是持久化到数据库中)、 特殊数(除了3,5,7还可以是其它个位数,如2,4)、特殊数的个数(现在是3个,但客户可能要求是可变的)等这些是可变的。只有规则本身是不变的。如果连规则都变了,那就是另一个游戏了。

打个比方,麻将分为四川麻将和贵州麻将,它们主要规则是不变的,只是某些小规则不同,但是它们还都是麻将!如果主要规则都变了,那么,我们就可以确定地说,这不是麻将。

这个游戏的主要规则就是得到数字,根据3条规则来决定是报单词还是数字。

我把这个核心放在Reporter类:

    public class Reporter {

        // number为接收到的数字,在本题中则是1到100之间的整数
        public static ReportResult report(int number, NumberWordMap numberWordMap) {

            // number 包含第一位特殊数
            if (numberWordMap.isContainsFirstSpecialNumber(number)) {
                return new ReportResult(number, numberWordMap.getFirstWord());
            }

            // 用于为number的倍数的特殊数
            List<Integer> multiples = new ArrayList<Integer>();

            for (Integer eachSpecialNumber : numberWordMap.allNumber()) {
                if (number % eachSpecialNumber == 0) {
                    multiples.add(eachSpecialNumber);
                }
            }

            //不是任何特殊数的倍数
            if (multiples.isEmpty()) return ReportResult.create(number);

            return ReportResult.create(numberWordMap, multiples, number);
        }

    }

NumberWordMap是用来定义特殊数字与单词之间的映射,如题目中,“3”对应“Fizz”,“5”对应“Buzz”。为什么我不直接使用LinkedHashMap呢? 而是另外自己再建立一个抽象数据结构呢?很明显,Java的纯Map类在这个问题上表达力不够。

    public class NumberWordMap {

        public static int MAX_NUMBER = 9;

        public static int MIN_NUMBER = 1;

        private LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>();

        /**
         * @param specialNumber
         * @param word
         * @return
         * @throws SpecialNumberIllegalException
         */
        public NumberWordMap put(int specialNumber, String word) {
            if (specialNumber < MIN_NUMBER || specialNumber > MAX_NUMBER)
                throw new SpecialNumberIllegalException("number will be one of " + MIN_NUMBER + "..." + MAX_NUMBER + " integer");
            map.put(specialNumber, word);
            return this;
        }

        public boolean isContainsFirstSpecialNumber(int number) {
            return (number + "").contains(getFirstNumber() + "");
        }
        .
        .
        .
        .

    }

你会看到,我返回的是一个ReportResult类,而不是一个String,这是考虑到用户(扩展者)可能不只是用到"1","Fizz",“Buzz”,这样字符串。所以ReportResult保存的是 原始数字及相应的报告,如本题中ReportResult会同时保存21和FizzWhizz。

这样,用户通过实现导出接口Exporter的方法export(ReportResult result)来实现自定义导出逻辑,如我默认提供了命令行导出器:

    package com.thoughtworks.FizzBuzzWhizz.internal;

    public class ConsoleExporter implements Exporter {
        @Override
        public void export(ReportResult result) {
            System.out.println(result.getWord());
        }
    }

相信读者有些头晕了,现在我们来最终主程吧:

    NumberWordMap map = new NumberWordMap().put(3, "Fizz").put(5, "Buzz").put(7, "Whizz");

    Game game = new Game(map, 1, 100);

    game.exportTo(new ConsoleExporter()).start();

一看这代码,大概就明白我的意思了吧。我们再看看Game类的start方法:

    public void start() {
        for (int i = startNumber; i <= endNumber; i++) {
            exporter.export(Reporter.report(i, numberWordMap));
        }
    }

这下,大家应该明白我的设计了。这样的设计比起第一种实现,还有更大的优点:易于测试!

这就是我的最终设计。需要说明的是设计的时候,常常要权衡的是复杂性和灵活性。过大的追求灵活性,常常会带来更多的复杂性,所以,我们要在 保证不增加复杂性的同时增加灵活性。

对比第一种设计,第二种设计为了灵活性,增加了一些复杂性。事实上,如果只是这么一个简单的游戏,不是用于真正商业的,完全没有必要,甚至有些过度设计。 既然是打擂,当然要写得好一些。虽然这只是玩具程序。

你也注意到了,全文下来,我没有对我的代码提设计模式,因为,我不喜欢谈设计模式。设计模式是解决某类问题的套路。 当你把问题的本质看清楚了,设计模式就在那里了。

最后

今天已经是截止交作业的时间,你交了没有?我也希望看看大家的代码,共同学习!

项目地址: github : https://github.com/zacker330/FizzBuzz

osc : http://git.oschina.net/zacker330/FizzBuzz

以上的图片源自网络,如果有版权问题,请联系我。

© 著作权归作者所有

共有 人打赏支持
上一篇: 编程警句
翟志军

翟志军

粉丝 348
博文 76
码字总数 79851
作品 2
深圳
程序员
私信 提问
加载中

评论(31)

老萨
老萨
个人感觉楼主还是写的挺好的。
把各种可能性很大的需求都考虑在了里面。例如输出,持久化到数据库。
数字从100变到500。
我之前想到的就是第一种方法,但是没有去提交代码,因为我也感觉自己写的有很大问题。
看了你的,终于明白了第一种方法哪里不对了。

另外配图大赞!2
a
alastin
看下这位的:http://blog.chinaunix.net/uid-24456535-id-4239904.html
cgcgbcbc
cgcgbcbc
话说4月26日的tc上这道题貌似和这个很类似http://community.topcoder.com/stat?c=problem_statement&pm=13062&rd=15958
不过范围是10^18..
悠悠然然
悠悠然然

引用来自“翟志军”的评论

@悠悠然然 规则4,我理解的意思是说是哪个特殊的倍数就加入那个特殊数的单词,如35就是BuzzWhizz。而53不是3,5,7任何一个特殊数的倍数。 规则5是说如果包含第一个特殊数,就返回Fizz,3,4规则作废

难道我没理解题目?
Sorry,是我脑子短路了把余数想成加数了。
翟志军
翟志军
@红薯 直接点“回复此评论”,然后在回复框里输入内容时非常的麻烦:输入框小;不支持换行!
翟志军
翟志军
@悠悠然然 规则4,我理解的意思是说是哪个特殊的倍数就加入那个特殊数的单词,如35就是BuzzWhizz。而53不是3,5,7任何一个特殊数的倍数。 规则5是说如果包含第一个特殊数,就返回Fizz,3,4规则作废

难道我没理解题目?
悠悠然然
悠悠然然

引用来自“悠悠然然”的评论

亲,不是我黑你,你的第一种BadImplements应该改个名叫:ErrorImplements,因为逻辑有问题:
具体就是在3个连续的IF语句这里。

引用来自“翟志军”的评论

你是说当0作为特殊数的时候吗?
那个地方,要用循环的,否则结果有问题。 如果是35,你的结果是正确的,如果53,你看看结果。
黄亿华
黄亿华

引用来自“bindo_”的评论

以顺序看了几位仁兄的代码,一个比一个高明,一个比一个复杂,尤其是黄兄的代码,看到大于十个类时,我不淡定了,如果在实际情况下,有另外人员接手并看到代码时,是该高兴呢还是要哭呢?

引用来自“悠悠然然”的评论

@黄亿华 请回复
好玩而已,实际项目中在扩展性和复杂性中取舍是必须的。另外:我觉得这个问题lz的第一种解法其实是最好的。
翟志军
翟志军

引用来自“悠悠然然”的评论

亲,不是我黑你,你的第一种BadImplements应该改个名叫:ErrorImplements,因为逻辑有问题:
具体就是在3个连续的IF语句这里。
你是说当0作为特殊数的时候吗?
翟志军
翟志军

引用来自“悠悠然然”的评论

亲,不是我黑你,你的第一种BadImplements应该改个名叫:ErrorImplements,因为逻辑有问题:
具体就是在3个连续的IF语句这里。
请问,3个连接if有什么问题?我没看出。
翟志军/FizzBuzz

FizzBuzzWhizz 题记 ThoughtWorks这次招人似乎有些狠。除了在微博上下大功夫,还和拉勾网、OSC合作。招人的方式比较特别,先交代码,才有机会得到面试电话。我想他们的嗅觉应该很灵敏。哈。代...

翟志军
2016/10/23
0
0
ThoughtWorks的面试题FizzBuzzWhizz,C实现

看到这个题目,做一下吧! 你是一名体育老师,在某次课距离下课还有五分钟时,你决定搞一个游戏。此时有100名学生在上课。游戏的规则是: 1. 你首先说出三个不同的特殊数,要求必须是个位数,...

无若
2014/05/07
0
0
一个号称比 FizzBuzzWhizz 更有意思的题目

这是一个 SQL 语句的转换器,仅用于对 where 部分的条件表达式进行转换。 例如: 转换前:fizzBuzz = '1' and (buzzWhizz = '2' or fizzWhizz = '3') 转换后:fizzbuzz = '1' and (buzzwhizz...

黄勇
2014/05/16
305
1
ThoughtWorks 百年不变的面试题之 --- Merchant's Guide To The Galaxy

ThoughtWorks 百年不变的面试题之 --- Merchant's Guide To The Galaxy Code: https://github.com/zifeiniu/Merchants-Guide-To-The-Galaxy 我选的第三道,罗马数字转换。。 题是做完了,也算...

KurtNiu
08/10
0
0
聊聊面试时让候选人写代码

本文由伯乐在线 -清蒸竹马 翻译,艾凌风 校稿。未经许可,禁止转载! 英文出处:phil calcado。欢迎加入翻译组。 在 DigitalOcean,我们正在对后端开发人员招聘流程做出一些改变,简化了岗位...

伯乐在线
2017/01/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

sed, awk 练习

1. sed打印某行到某行之间的内容 2. sed 转换大小写 将单词首字母转化大写 将所有小写转化大写 3. sed 在某一行最后面添加一个数字 4. 删除某行到最后一行 解析: {:a;N;$!ba;d} :a : 是...

Fc丶
今天
2
0
babel6升级到7,jest-babel报错:Requires Babel "^7.0.0-0", but was loaded with "6.26.3".

自从将前端环境更新到babel7,jest-babel之前是基于babel6的,执行时候就会报:Requires Babel "^7.0.0-0", but was loaded with "6.26.3". 很烦,因为连续帮好几台电脑修复这个问题,所以记...

曾建凯
今天
1
0
探索802.11ax

802.11ax承诺在真实条件下改善峰值性能和最差情况。 如何改善今天的Wi-Fi? 在决定如何改进当前版本以外的Wi-Fi时,802.11ac,IEEE和Wi-Fi联盟调查了Wi-Fi部署和行为,以确定更广泛使用的障碍...

linuxprobe16
今天
2
0
使用linux将64G的SDCARD格式化为FAT32

一、命令如下: sudo fdisk -lsudo mkfs.vfat /dev/sda -Isudo fdisk /dev/sda Welcome to fdisk (util-linux 2.29.2). Changes will remain in memory only, until you decide to wri......

mbzhong
今天
4
0
深入理解Plasma(四):Plasma Cash

这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍在 Plasma 框架下的项目 Plasma Cash。 深入理解Plasma(1):...

HiBlock
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部