Posted at

C#:FizzBuzz問題を一般化して考えてみた

More than 1 year has passed since last update.

FizzBuzz問題が話題になってから、すでに10年以上経過するわけですが、 C#でFizzBuzz問題について考えてみたいと思います。


FizzBuzz問題とは

まずは、FizzBizz問題とは、以下のような問題です。


1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。


どうしてプログラマに・・・プログラムが書けないのか?から引用


普通に解く(1)

using System;

namespace FizzBuzzApp {
class Program {
static void Main(string[] args) {
for (int i = 1; i <= 100; i++) {
Console.WriteLine(FizzBuzz(i));
}
}

static string FizzBuzz(int n) {
if (n % 3 == 0 && n % 5 == 0)
return "FizzBuzz";
if (n % 3 == 0)
return "Fizz";
if (n % 5 == 0)
return "Buzz";
return n.ToString();
}
}
}

ロジックの中に、出力のコード(Console.WriteLine)が入るのは嫌なので、FizzBuzzというメソッドを定義しました。


普通に解く(2)

上のFizzBuzzメソッドは、次のようにも書けますね。

static string FizzBuzz(int n) {

string s = "";
if (n % 3 == 0)
s += "Fizz";
if (n % 5 == 0)
s += "Buzz";
return s == "" ? n.ToString() : s;
}

このほうが、比較する回数が少なくなります。

ここまでは、ありふれたFizzBuzz問題のコード。


一般化を試みる

さて、このFizzBuzz問題を一般化してみようと思います。やりたいことは、ルールをFizzBuzzメソッドの外側から与えられるようにしたいということ。 そうすれば、


  • 1からnまでの数をプリントするプログラムを書け。ただし

  • 3の倍数のときは数の代わりに「Jazz」とプリント

  • 5の倍数のときは「Buzz」とプリント

  • 7の倍数のときは「Pizz」とプリント


  • 3と5両方の倍数の場合には「JazzBuzz」とプリント


  • 3と7両方の倍数の場合には「JazzPizz」とプリント


  • 5と7両方の倍数の場合には「BuzzPizz」とプリント

  • 3と5と7の倍数の場合には「JazzBuzzPizz」とプリントすること。


のような問題にも答えることができるようになります。

C#のコードは以下の通り。いままではメソッドでしたが、FizzBuzzクラスとしました。

using System;

using System.Collections.Generic;
using System.Linq;

namespace Sample {
class Program {
static void Main(string[] args) {
var fb = new FizzBuzz();
fb.AddRule(n => n % 3 == 0, _ => "Fizz");
fb.AddRule(n => n % 5 == 0, _ => "Buzz");
fb.Execute(1, 100, Console.WriteLine);
}
}

public class FizzBuzz {
class Rule {
public Func<int, bool> Condition { get; set; }
public Func<int, string> Value { get; set; }
}

private List<Rule> rules = new List<Rule>();

public void Execute(int start, int stop, Action<string> action) {
string GetText(int n) {
var s = rules.Aggregate("", (a, rule) => rule.Condition(n) ? a + rule.Value(n) : a);
return (s == "") ? n.ToString() : s;
}

for (int i = start; i <= stop; i++) {
action(GetText(i));
}
}

public void AddRule(Func<int, bool> cond, Func<int, string> value) {
rules.Add(new Rule { Condition = cond, Value = value });
}
}
}

AddRuleメソッドで、ルールを追加しています。 第1引数が条件のラムダ式、第2引数が出力する文字列を組み立てるラムダ式です。 第2引数は、単純な文字列でも良かったのですが、柔軟性を持たせるためにラムダ式を受け取るようにしました。

Executeでは、ローカル関数を使ってみました。あまり意味ないけど...

単純なバージョンと比べてかなり複雑になってしまいましたが、このFizzBuzzクラスを利用すれば、以下のようにルールを変更することができます。

    static void Main(string[] args) {

var fb = new FizzBuzz();
fb.AddRule(n => n % 3 == 0, _ => "Jazz");
fb.AddRule(n => n % 5 == 0, _ => "Buzz");
fb.AddRule(n => n % 7 == 0, _ => "Pizz");
fb.Execute(1, 200, Console.WriteLine);
}


この記事は、Gushwell's C# Programming Pageで公開したものを加筆・修正したものです。