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で公開したものを加筆・修正したものです。