我最近达到了这个要求:

编写一些代码,以打印出以下连续数字范围:

数字“ fizz”表示3的倍数
'buzz'表示5的倍数的数字
'fizzbuzz'表示15的倍数的数字

例如如果我在1到20的范围内运行程序,我应该得到以下输出
1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizzbuzz 16 17 fizz 19 buzz


这种尝试是通过这种方式完成的,因为我需要在控制台上打印序列,并且我需要以显示相应的测试,例如TDD。
public static class Evaluate
{
    public static string FizzBuzz(int start, int end)
    {
        return Enumerable.Range(start, (end - start) + 1)
            .Select(FizzOrBuzz)
            .Aggregate(String.Empty, (y, x) => String.Format("{0} {1}", y, x))
            .Trim();
    }

    public static string FizzOrBuzz(int n)
    {
        if (n % 15 == 0) return "fizzbuzz";
        if (n % 3 == 0) return "fizz";
        if (n % 5 == 0) return "buzz";
        return n.ToString();
    }
}


评论

您可以将FizzOrBuzz设为私有,除此之外,一切对我来说似乎都很好

@RubberDuck显然您还没有看到此代码:codereview.stackexchange.com/questions/49058/…

@SimonAndréForsberg好点。讨论促使我发布了问题。

奇怪地类似于这个答案stackoverflow.com/a/5661471/659190

我永远不会理解对模块运算符进行嗡嗡嗡嗡的不健康困扰。专业职业实践:从不。采访筛查的发生:始终。

#1 楼

我宁愿使用String.Join方法代替Aggregate

public static string FizzBuzz(int start, int end)
{
    return String.Join(" ", Enumerable.Range(start, end - start + 1).Select(FizzOrBuzz));
}


这应消除多个字符串连接。

编辑。删除了多余的泛型类型参数。

评论


\ $ \ begingroup \ $
请注意,泛型类型参数是多余的,可以从FizzOrBuzz的签名中推断出它们-因此.Select(FizzOrBuzz)同样适用。
\ $ \ endgroup \ $
– Mathieu Guindon♦
15年1月13日在17:31

\ $ \ begingroup \ $
@ Mat'sMug我已经删除了通用类型参数。谢谢。
\ $ \ endgroup \ $
–德米特里
15年1月13日在19:28

#2 楼

你的名字不是很好。例如,作为类名的Evaluate并不能告诉我任何信息。

从TDD的角度来看,最好使用生成的序列,而不仅仅是生成的字符串。这样就可以测试预期的元素数量之类的东西。

方法的实际逻辑是好的。

使用其他方法的重命名版本:

public static class FizzBuzz
{
    public static string GenerateDisplayString(int start, int end)
    {
        return GenerateSequence(start, end)
            .Aggregate(String.Empty, (y, x) => String.Format("{0} {1}", y, x))
            .Trim();
    }

    public static IEnumerable<string> GenerateSequence(int start, int end)
    {
        return Enumerable.Range(start, (end - start) + 1)
            .Select(GetForDisplay);
    }

    private static string GetForDisplay(int number)
    {
        if (number % 15 == 0) return "fizzbuzz";
        if (number % 3 == 0) return "fizz";
        if (number % 5 == 0) return "buzz";
        return number.ToString();
    }
}


然后我将测试GenerateSequence方法。

评论


\ $ \ begingroup \ $
命名困扰了我。这是一个定时锻炼,所以我没有过多地讲。我同意将显示关注点提取到单独的功能中。感谢您的时间。
\ $ \ endgroup \ $
–chosenbreed37
15年1月13日在13:12

#3 楼

您确定您的要求吗?特别是这样:


'fizzbuzz'表示数字为15的倍数


问题通常表示为


如果数字是3和5的倍数,请打印“ FizzBu​​zz”。


如果面试官给了您发布的要求,那么您就很好。但是,如果需求实际上是后者,那么您就错过了保持代码灵活和DRY的机会。

根据经典要求,假设数字已更改,您现在需要以5的倍数打印“ Fizz”,以7的倍数打印“ Buzz”。除了对代码进行两次更改外,您将必须制作三个。不仅如此,维护人员还需要了解15是3和5的最小公倍数,以便能够计算“ FizzBu​​zz”数字的新常数。您可能最好删除15个常数,然后将其设为真正的常数:其他两个常数的最小公倍数。

评论


\ $ \ begingroup \ $
我从字面上复制并粘贴了需求(脱机测试)。我同意你的观点。
\ $ \ endgroup \ $
–chosenbreed37
15年1月13日在13:01

\ $ \ begingroup \ $
如果FizzBu​​zz中有任何神圣的战争,就是这样。
\ $ \ endgroup \ $
–西蒙·福斯伯格
15年1月13日在13:03

\ $ \ begingroup \ $
“经典要求”是对受访者能否完全编程的考验。这对它的考虑太多了。
\ $ \ endgroup \ $
– RemcoGerlich
15年1月13日在14:55

\ $ \ begingroup \ $
@RemcoGerlich,您可能会觉得很幽默。
\ $ \ endgroup \ $
–RubberDuck
15年1月13日在14:57

\ $ \ begingroup \ $
这似乎是假设给您的要求以最能描述其基础结构的方式陈述了问题。在现实生活中通常并非如此,发现结构是开发人员工作的一部分。
\ $ \ endgroup \ $
–本·亚伦森
15年1月14日在16:45

#4 楼

我建议对序列中的每个项目使用模数或除法运算符不必要地昂贵,与简单的加法和比较相比,两者都占用大量处理器。避免重复使用%。我提供了一个可选的start参数,因此该系列可以在传统开始之后启动,而不会浪费Skip

public static IEnumerable<string> FizzBuzzSeries(long start = 1)
{
    const ulong FFizz = 3UL;
    const ulong FBuzz = 5UL;

    if (start < 1)
    {
        throw new ArgumentOutOfRangeException("start");
    }

    var i = (ulong)start;
    var fizz = FFizz;
    if (i > FFizz)
    {
        fizz = i + (FFizz - (i % FFizz));
    }

    var buzz = FBuzz;
    if (i > FBuzz)
    {
        buzz = i + (FBuzz - (i % FBuzz));
    }

    var s = new StringBuilder(8);
    for (; i <= ulong.MaxValue; i++)
    {
        if (i == fizz)
        {
            fizz += FFizz;
            s.Append("fizz");
        }

        if (i == buzz)
        {
            buzz += FBuzz;
            s.Append("buzz");
        }

        if (s.Length > 0)
        {
            yield return s.ToString();
            s.Clear();
        }
        else
        {
            yield return i.ToString(CultureInfo.InvariantCulture);
        }
    }
}


要获得所需的输出,

Console.WriteLine(string.Join(" ", FizzBuzzSeries().Take(20)));


评论


\ $ \ begingroup \ $
好的,我想这可行,但是我认为使用else比使用continue更好。功能继续非常接近goto声明,而IMO也有类似的问题。否则会很好。
\ $ \ endgroup \ $
– Hogan
15年1月14日在16:58

\ $ \ begingroup \ $
@Hogan,随您便。
\ $ \ endgroup \ $
–乔德雷尔
15年1月14日在16:59

\ $ \ begingroup \ $
@Jodrell一种新颖的(至少对我来说)实现。做得很好。我喜欢这个解决方案。也许下次我在面试时会用到它:-)
\ $ \ endgroup \ $
–chosenbreed37
15年1月15日在17:10

\ $ \ begingroup \ $
@Jodrell ...说过,如果执行此操作,所有效率似乎都将丢失:FizzBu​​zzSeries.Skip(1000000).Take(20)...
\ $ \ endgroup \ $
–chosenbreed37
15年1月15日在17:52



\ $ \ begingroup \ $
@ chosenbreed37我添加了一个可选的开始参数。
\ $ \ endgroup \ $
–乔德雷尔
15年1月16日在9:53

#5 楼

简单的事情要保持简单。我认为一个问题应该在一行中解决:

return String.Join(
        Environment.NewLine,
        Enumerable.Range(start, (end - start) + 1)
          .Select(n => n % 15 == 0 ? "fizzbuzz" 
                     : n % 3 == 0 ? "fizz" 
                     : n % 5 == 0 ? "buzz" 
                     : n.ToString())


评论


\ $ \ begingroup \ $
为什么您认为这比投票最多的答案更简单?我不认为这要简单得多。
\ $ \ endgroup \ $
–递归
15年1月13日在18:48

\ $ \ begingroup \ $
就编译成什么而言简单?可能不会。但是,它与顶部答案不同,因为该方法是内联的,因此您可以在那里直接阅读它,并且方法主体为一行而不是4行,因此您无需在lambda内使用方括号和分号。
\ $ \ endgroup \ $
–挡风板
2015年1月13日在23:47



\ $ \ begingroup \ $
我不是在谈论MSIL编译。我在说代码。肯定是更少的语句,但是我认为这并不容易阅读。陈述并不总是不好的。例如,它们确实使添加断点变得非常容易。
\ $ \ endgroup \ $
–递归
15年1月14日在1:29

\ $ \ begingroup \ $
@recursive-我认为代码本身就是正确的,试图解释为什么我认为它更简单是没有意义的。每个人都会发现它是否简单。我并不是说它更好,因为它更简单-这是另一个问题。但我确实认为,在这种情况下,行数减少一半,对象名称减少5个。在其他情况下,减少名称的行数和数量会使情况变得更复杂...但是我认为在这种情况下不是这样。
\ $ \ endgroup \ $
– Hogan
15年1月14日在16:50

\ $ \ begingroup \ $
很公平。
\ $ \ endgroup \ $
–递归
15年1月14日在23:45