我是计算机科学专业的大一学生。这是我对Java中FizzBu​​zz面试问题的理解。我可以进行哪些改进?

//Prints numbers 1 - 100, 25 values per line.
//Numbers that are multiples of 3 are replaced with Fizz
//Numbers that are mulitples of 5 are replaced with Buzz
//Numbers that are multiples of both 3 and 5 are replaced with FizzBuzz

public class FizzBuzz {

    public static void main(String[] args) {

        String fizz = "Fizz";
        String buzz = "Buzz";
        String fizzBuzz = "FizzBuzz";

        //Loop from 1 through 100
        for (int i = 1; i <= 100; i++) {

            if (i % 3 == 0 && i % 5 == 0) {          //Checks for numbers that are both multiples of 5 and 3
                if (i % 25 != 0) {                   //Control flow allows 25 values printer per line
                    System.out.printf("%4.8s ", fizzBuzz);                  
                }
                else {
                    System.out.printf("%4.8s\n ", fizzBuzz); 
                    System.out.println();
                }

            }

            else if (i % 3 == 0) {                   //Checks for numbers that are multiples of 3
                if (i % 25 != 0) {                   //Control flow allows 25 values printer per line
                    System.out.printf("%4.8s ", fizz);
                }
                else {
                    System.out.printf("%4.8s\n ", fizz);
                    System.out.println();
                }

            }

            else if (i % 5 == 0) {                   //Checks for numbers that are multiples of 5
                if (i % 25 != 0) {                   //Control flow allows 25 values printer per line
                    System.out.printf("%4.8s ", buzz);
                }
                else {
                    System.out.printf("%4.8s\n ", buzz);
                    System.out.println();
                }

            }

            else {                                   //Prints numbers that are not multiples of 3 or 5
                if (i % 25 != 0) {                   //Control flow allows 25 values printer per line
                    System.out.print(i + " ");
                }
                else {
                    System.out.println(i + " ");
                    System.out.println();
                }
            }
        }
    }
}


评论

如果我除以25,而我们知道我未能除以5;这告诉我关于我的什么信息?

我们在面试过程中以“白板”测试的形式提出这个问题。这实际上只是一个酸性测试,以了解某人是否可以对某个编程人员与知道“正确话语”的人员进行编程。尽管真正可怕的解决方案会引起人们的注意,但无需提出最佳解决方案。但是,如果有人提出了“完善的解决方案”,我会非常怀疑他们是否知道这个问题,并要求再提供一个样本。令您惊讶的是,有太多人认为/变得过于紧张,而对实际上是一种非常简单的锻炼却一无所知。

看到我前一段时间在一次采访中对此提出的问题。有很多很棒的答案:codereview.stackexchange.com/questions/60145 / ...

这是开玩笑的意思,但演示了您可能会在大型公司中进行的软件开发类型:github.com/EnterpriseQualityCoding/FizzBu​​zzEnterpriseEdition

我认为这是对FizzBu​​zz的不错扩展,因为它可能会导致初学者陷入对8种情况进行编码的陷阱。现在添加更多规则,您将需要编写16、32、64 ...个案例。那把重点带回家。

#1 楼



System.out.printf("%4.8s ", ...);实际上在这里不是必需的:该代码意味着您将格式化String,使其至少具有4个字符,最多8个字符。由于"Fizz""Buzz""FizzBuzz"的长度介于4到8 ,这将导致仅输出String而不进行任何更改。因此,使用System.out.print(... + " ");更为简单。
您的代码的一部分重复了4次。您要不惜一切代价避免代码重复。最好将重复的逻辑重构为小的可重用方法,然后在其他地方调用。
以同样的方式,您可以将两个boolean的结果存储在3或5的整数中。 br />
在这种情况下,您要重复检查以每行仅打印25个值,而在所有if/else逻辑之后只能执行一次时。

您可以更简单地编写代码:

for (int i = 1; i <= 100; i++) {
    boolean shouldFizz = i % 3 == 0;
    boolean shouldBuzz = i % 5 == 0;
    if (shouldFizz && shouldBuzz) { // Checks for numbers that are both multiples of 5 and 3
        System.out.print(fizzBuzz);
    } else if (shouldFizz) { // Checks for numbers that are multiples of 3
        System.out.print(fizz);
    } else if (shouldBuzz) { // Checks for numbers that are multiples of 5
        System.out.print(buzz);
    } else { // Prints numbers that are not multiples of 3 or 5
        System.out.print(i);
    }
    System.out.print(" ");
    if (i % 25 == 0) { // Control flow allows 25 values printer per line
        System.out.println();
        System.out.println();
    }
}


评论


\ $ \ begingroup \ $
有趣的是,现在值3和5在变量名中,因此,如果它们更改了,您仍然可以在任何地方更改它们(我希望如此)。但是我们不应该在意,只是FizzBu​​zz。我们无需担心规格更改。为了使其更“通用”会导致疯狂。
\ $ \ endgroup \ $
– RemcoGerlich
16年2月29日在9:59

\ $ \ begingroup \ $
我认为最后使用那两个println调用不是很好。您不能只打印一次(“%n%n”);吗?
\ $ \ endgroup \ $
– SirPython
16年2月29日在22:49

\ $ \ begingroup \ $
boolean shouldFizz = i%3 == 0和boolean shouldBuzz = i%5 == 0使重构更加容易,并且变量名称好得多。
\ $ \ endgroup \ $
– nhgrif
16 Mar 1 '16 at 0:32

\ $ \ begingroup \ $
@nhgrif感谢您的宝贵意见!我没想到要像这样命名变量。
\ $ \ endgroup \ $
–Tunaki
16 Mar 1 '16 at 8:13

#2 楼

软件工程中的两个主要缺点是示波器蠕变和镀金。范围蔓延是客户和经理犯下的罪过。镀金是程序员犯下的罪过。这是后者的完美示例。嘶嘶声的要求非常简单。这些要求没有关于每行打印25个值的要求。只需每行显示一个值的fizz-buzz实现就可以了。

虽然Java是一种冗长的语言而闻名,即使在Java中,fizz-buzz的实现也只有63多行(计算空白行和注释)太长。作为一名面试官,我祝贺您正确回答了问题。但是,在面试后的会议上,我很可能建议您不要被雇用。违反了非常标准的编码惯例):

// Solve the fizz-buzz problem: See
// http://imranontech.com/2007/01/24/using-fizzbuzz-to-find-developers-who-grok-coding/
public class FizzBuzz {
    public static void main(String[] args)
    {
        for (int i = 1; i <= 100; i++)
            if (i % 15 == 0) System.out.println("FizzBuzz");
            else if (i % 3 == 0) System.out.println("Fizz");
            else if (i % 5 == 0) System.out.println("Buzz");
            else System.out.println(i);
    }
}


即使在合并了那些非常标准的编码惯例之后(forifelse等后面的语句也应该分开行,并用大括号括起来),即使在Java中,fizz-buzz也应较小。

评论


\ $ \ begingroup \ $
在寻求更简单解决方案的任务中,您没有注意到帖子第一行中的注释://打印数字1-100,每行25个值。即使不是正常情况,似乎这也是OP面试问题的一部分。
\ $ \ endgroup \ $
– Holroy
16-2-29在18:48

\ $ \ begingroup \ $
无论如何,可以通过在循环的末尾仅添加一条额外的行来解决此要求,而不是检查ifs的每个分支:if(0 == i%25)System.out.println ();
\ $ \ endgroup \ $
– Dewi Morgan
16-2-29在19:06



\ $ \ begingroup \ $
@holroy-那是镀金。每行打印25个值(每个字符8个字符加一个空格)是反人类的。每行80个字符后,我们发现人类非常糟糕。 (几年前一个没有远见的同事对我们这些视觉受限的人感到可惜。)请注意,python的BDFL认为80个字符的限制已过时。最多79个字符,或者至少如此说。
\ $ \ endgroup \ $
–David Hammen
16-2-29在19:53



\ $ \ begingroup \ $
只是为了鼓励OP,虽然这确实是一个高级职位的主要危险信号,但对于研究生级别的职位来说还不错,我的建议在很大程度上取决于其余的面试。对于初学者来说,我要指出这是很长的路要走,只是问问受访者是否可以减少这种情况。
\ $ \ endgroup \ $
–罗马·斯塔科夫
16-2-29在22:52

\ $ \ begingroup \ $
这个答案可能解决了镀金问题,但是使开发人员倾向于为客户和经理的范围攀升而付出代价。
\ $ \ endgroup \ $
– nhgrif
16 Mar 1 '16 at 0:30

#3 楼

免责声明:将FizzBuzz泛化可能不适合胆小的人,因此David Hammen具有与范围蠕变和镀金有关的有效观点。其他答案集中在可能的简化和其他方面,但是在此答案中,我想沿着泛化FizzBu​​zz算法的思路,考虑到其他数字和其他单词。


有两个我将对您的代码进行重大改进:


当认识到您的打印语句非常相等时,请在方法末尾将它们提取到单个打印中,以免重复您自己
类似地,当认识到15是3和5的倍数时,请考虑更改以仅计算模的最小次数。那就是更改您的代码,以便记住“ FizzBu​​zz”的情况。 FizzBu​​zz规则:

class FizzBuzzRule {
    public int value;
    public String string;

    public FizzBuzzRule(int value, String string) {
        this.value = value;
        this.string = string;
    }

    // Define default set
    static public final ArrayList<FizzBuzzRule> BASIC_RULES = new ArrayList<>();
    static {
        BASIC_RULES.add(new FizzBuzzRule(3, "Fizz"));
        BASIC_RULES.add(new FizzBuzzRule(5, "Buzz"));  
    }


FizzBuzzRule方法

此代码还将基本集定义为静态内容,以便我们可以访问使用fizzyfy的规则。

进一步简化列表的生成,我们将实现一种专用方法,使用给定规则集来模糊数字,如下所示:

public static String fizzyfy(int i, ArrayList<FizzBuzzRule> rules) {

    StringBuilder fizzy = new StringBuilder();

    for (FizzBuzzRule rule : rules) {
        if (i % rule.value == 0) {
            fizzy.append(rule.string);
        }
    }

    if (fizzy.length() > 0) {
        return fizzy.toString();
    } else {
        return Integer.toString(i);
    }
} 


请注意,我们现在如何使用FizzBuzzRule.basicRules连接冒充电话号码的不同部分。这使它更加通用,此外,对于给定的集合,它仅计算一次模运算。在基本版本中,只为3和5做,而不是针对3、5和15做。如果您添加更多规则,例如打印可除以7的“ Baz”和可除以11的“ Shoe”,那么这将更加有意义……这将在您的代码中产生大量StringBuilder语句。

在这个基本扩展中,我使用取模运算if来计算除法i % rule.value的余数。由于使用了类,因此允许扩展为其他数字和/或单词添加不同的规则。备用规则集(受Wikipedia启发,可以定义为:

ArrayList<FizzBuzzRule> extendedRuleset = new ArrayList<>();
extendedRuleset.append(new FizzBuzzRule(3, "Fizz");
extendedRuleset.append(new FizzBuzzRule(5, "Buzz");
extendedRuleset.append(new FizzBuzzRule(7, "Pop");
extendedRuleset.append(new FizzBuzzRule(11, "Whack");


另一种扩展,是受FizzBu​​zz的二十种方式启发(用Javascript编写)的,极端,并在通用规则类中引入谓词(如#12所示)。但是,我将在以后某个时候将其留给另一篇文章。

重建主循环br />我看到了使用i / rule.value的论点,因为您可以限制输出并获得漂亮的列,但是在这里您还应避免重复自己,而应使用一种与当前输出相关的格式。 br />
为避免重复自己,我将行尾作为单独的语句,当与其他元素结合使用时,我们可以以类似以下内容的结尾:

public static void main(String[] args) {

    for (int i = 1; i <= 21; i++) {

        System.out.printf("%-8.8s ", fizzyfy(i, FizzBuzzRule.BASIC_RULES));

        if (i % 4 == 0) {
            System.out.println();
        }
    }
}


请注意,这段代码是如何将字符串的长度限制为正好8个字符,并为列分隔符添加了额外的空间。我在每列中仅选择了4个数字,但是

这种方法的另一个优点是您知道已经将业务逻辑和表示逻辑分开了。通过System.out.printf()将业务逻辑分为FizzBuzzRule和“ fizzyfication”的规则定义,最后将表示逻辑巧妙地分组在fizzyfy()方法中。

如果您愿意,您当然可以选择在main()类中包含fizzyfy(),然后在此处和此处选择其他名称。但是,仍然保留了不重复自己,仍然允许修改和扩展规则集或表示形式的一般原则。

要使用备用规则集FizzBuzzRule,请将extendedRuleset的第二个参数从fizzyfy()替换为FizzBuzzRule.BASIC_RULES(并在extendedRuleset循环的前面添加规则集的创建)。

评论


\ $ \ begingroup \ $
这不是一个好的实现。规则引擎是一个独立的函数,要求规则是一个有序列表。规则处理的正确输出取决于添加规则的顺序和保持该顺序的顺序。规则的逻辑('%value == 0')也封装在规则本身之外,这限制了灵活性。如果您的目标只是向大一学生解释企业实施可能如何解决FizzBu​​zz,则最好对其进行清理并添加更多关于您为何提出企业结构及其运作方式的解释。
\ $ \ endgroup \ $
–埃里克
16-2-29在17:03

\ $ \ begingroup \ $
@Eric,我正在考虑使用Predicate 做一个版本,但是我现在的时间有点短。我使用有序列表进行的扩展是最基本的扩展,因为它扩展了模运算符的用法,而替换模规则则是更高等级的扩展。但是当我开始对其进行编辑时,我将尝试更具描述性。
\ $ \ endgroup \ $
– Holroy
16-2-29在17:09



#4 楼

public class FizzBuzz {
    public static void main(String[] args) {
        for (int i = 1; i <= 100; i++) {
            if (i % 15 == 0) {
                System.out.print("FizzBuzz");
            } else if (i % 3 == 0) {
                System.out.print("Fizz");
            } else if (i % 5 == 0) {
                System.out.print("Buzz");
            } else {
                System.out.print(i);
            }


            if ( i % 25 == 0 ) { 
                System.out.println(); 
            } else {
                // Separate our results.
                System.out.print(" ");
            }
        }
    }
}


这是一个非常简单的白板问题,检查者询问此问题很可能不是在寻找模块化,因此没有必要存储要初始化为预定值的局部变量,除非我们要修改它们,否则根据原始问题和帖子,我们实际上并没有对其进行修改。

所以我删除了存储的字符串变量。最终,大多数结果将对i%5 && I%3进行检查,以检查结果答案是否应为FizzBu​​zz,您可以通过获取它们都可以调制成的尽可能低的数字来简化此操作...简单地说就是3 * 5.

另一个可能的解决方案是执行3,如果检查没有其他检查

if(i%3 == 0)System.out.print(“ Fizz”) ;
if(i%5 == 0)System.out.print(“嗡嗡声”);
if(i%3!= 0 && i%5!= 0)System.out.print (i);

该解决方案的问题是,我们进行的每一次迭代都进行3次调制和3种可能的检查。那不是必需的。在这种情况下,使用if和else if序列可以得到最好的情况,即只进行1次调制来确定答案,最坏的情况是3次。这为我们节省了一些时间。

下面,我正在执行if i%25 == 0的检查,以确定是否需要换行,如果不添加,则我要在打印输出中添加一个空格,这样我们才能实际看到每个结果。

我会说原始帖子对于第一次尝试来说并不是一个不错的解决方案。只是要记住一些事情,如果您要重复执行的事情多于3或4行代码,那么应该有一种封装方法,我想说的是:

if (i % 25 != 0) {                   //Control flow allows 25 value printer per line
    System.out.printf("%4.8s ", buzz);
}
else {
    System.out.printf("%4.8s\n ", buzz);
    System.out.println();
}


评论


\ $ \ begingroup \ $
我已经使用了您过去至少提到过的“ no else blocks”方法,但是您编写它的方式不起作用。您的最终条件if(!(i%15 == 0))将发现被3和5整除的东西,只要它们不能被15整除即可。例如:Buzz10 11 Fizz12 13 14 FizzBu​​zz 16 17 Fizz18。您需要类似if(i%3!= 0 && i%5!= 0)这样的最终条件。
\ $ \ endgroup \ $
–马克·巴尔霍夫(Mark Ba​​lhoff)
16年2月29日在20:25

\ $ \ begingroup \ $
马克,没错,我现在将对其进行更新
\ $ \ endgroup \ $
– AresCaelum
16 Mar 1 '16 at 0:17



#5 楼

您基本上会重复相同的7行4次。而且,更糟糕的是,按照书面规定,不可能有一半的时间使用完整的块。不能在%25时执行。它也不必要地太长。在确定打印内容的条件之外,最后3行。无论您是否打印换行符,都与打印内容无关,没有理由将它们组合在一起。

解决了上述问题,将其移出了“可行但可怕的实现”类别。

但一般来说,我不喜欢混合输出和逻辑,而在较小程度上不喜欢循环和逻辑。

如果将循环中的逻辑提取到其自己的函数中,则会返回字符串,您可以更轻松地阅读,维护和测试。

#6 楼

这些答案中的大多数,以及问题中提出的原始答案,似乎都过于复杂。产生正确输出的程序应该大约有十行: 101”而不是“ <= 100”以简化循环比较。
五个IF语句仅检查当前数字,并打印适当的数字。其他解决方案可能使用更复杂的嵌套IF语句,但是我和其他程序员的经验是,嵌套循环通常更难以遵循,并导致更多的逻辑错误。如果可以避免,通常应该这样做。除此之外,该程序应具有相当的自我解释性。

评论


\ $ \ begingroup \ $
嗯,不。这不会打印1,2,4,7,8,11,13,14,16,17,19,22,23,26,...
\ $ \ endgroup \ $
–David Hammen
16-2-29在16:37

\ $ \ begingroup \ $
你是正确的。我看过其他的fizzbuzz,仅显示单词并跳过不匹配的数字。我加了那条线。
\ $ \ endgroup \ $
–拜伦·琼斯(Byron Jones)
16-2-29在16:40



\ $ \ begingroup \ $
我清除了反对票。但是,多次重复同一测试以避免嵌套if语句不一定是一件好事。
\ $ \ endgroup \ $
–David Hammen
16-2-29在17:06

\ $ \ begingroup \ $
我主要是在写每行25个值的需求。这是嘶嘶声问题说明中没有的内容。没有必要。您的程序的圈复杂度扩展为9。 (删除每行25个值的测试,复杂度降低一倍。)使用其他两个或两个可以进一步降低复杂度。嵌套的if-then-else语句不一定是邪恶的。人为的复杂性是,无论是由于镀金,还是因为害怕威胁那些本来就不应该进行编程的人。
\ $ \ endgroup \ $
–David Hammen
16-2-29在17:25

\ $ \ begingroup \ $
原始代码在第一行中声明以下内容://打印数字1-100,每行25个值。
\ $ \ endgroup \ $
–拜伦·琼斯(Byron Jones)
16-2-29在18:23

#7 楼

您可以进行两(2)个小循环来完成此操作;第一个将填充一个数组,第二个将每行打印25个数组单元格。直截了当的解决方案,但使用数组,数据将可重用。也许您想向后打印?一次又一次地进行相同的处理是很愚蠢的。

但是万一您绝对不知道会再次使用该数据,则每次迭代最多可以进行5次比较操作以将其打印出来:

public static void main(String args[]) {

    String[] storage = new String[101];

    // Store data
    for(int i = 1; i <= 100; i++) {

        if (i % 3 == 0 && i % 5 == 0) {
            storage[i] = "FizzBuzz";
        }
        else if (i % 3 == 0) {
            storage[i] = "Fizz";
        }
        else if (i % 5 == 0) {
            storage[i] = "Buzz"; 
        }
        else {
            storage[i] = Integer.toString(i);
        }

    }

    // Print data
    for(int i = 1; i < storage.length; i++) {
        System.out.print(storage[i] + " ");
        if(i % 25 == 0) {
            System.out.print("\n");
        }
    }
}


评论


\ $ \ begingroup \ $
这浪费时间和内存。首先,当您只需要完全打印它们时,就不需要存储字符串;其次,您的数组项不只是为了省略一些添加项或额外的变量,最后,您将在第一个循环100中循环时间和第二次101次(一次循环只有100次)。如果建议将所有内容都生成为持久性列表,请使用生成器函数正确地进行生成,或者将逻辑封装到根据数字输出字符串的函数中。
\ $ \ endgroup \ $
–法老王
16 Mar 1 '16 at 5:54

#8 楼

public class FizzBuzz{ 
  public final int FIZZ = 3;
  public final int BUZZ = 5;

  private String getFizzyBuzzyString(int num) {
    String fizzybuzzyString = "";
    fizzyBuzzyString += (num % FIZZ == 0)? "Fizz" : "";
    fizzyBuzzyString += (num % BUZZ == 0)? "Buzz" : "";

    return StringUtils.hasLength(fizzyBuzzyString)? fizzyBuzzyString : String.valueOf(num);
  }

   public static void main(String [] args){
     for (int i = 1; i < 101; i++){
       System.print(getFizzyBuzzyString(i) + " ");
       if (i % 25 == 0) {
         System.print("\n"); 
       }
    }
 }


改进

这将使fizzbuzz逻辑脱离main方法;对代码面试而言,这是一件好事,因为它表明您了解问题的分离现在,您的fizzbuzz方法仅告诉应打印的整数,并且打印本身由main方法处理。

Sidenote(与大多数代码采访无关)

这种方法有一个缺点。这比仅具有四个if子句要慢得多。前者先检查3的可除性,然后再检查5,而后一种方法仅检查15的可除性(请参见if语句中的快捷方式)。但是,由于大多数数字不能被15整除,因此速度差异在复杂性方面微不足道。