FizzBuzz 問題をご存じでしょうか?
一時期、ネット上で話題となった問題です。
恐らくこの記事がきっかけだったかと思います。
問題は次のような内容です。
1 から 100 までを順番に出力しなさい。
ただし、3 の倍数の時は数字の代わりに Fizz を、
5 の倍数の時は数字の代わりに Buzz を、
3 の倍数且つ 5 の倍数の時は数字の代わりに FizzBuzz を出力しなさい。
また、一部の人達の間では、できるだけコードの文字数を短縮することが競われました。
というわけで、この問題を PowerShell でできるだけ少ない文字数で解いてみましょう。
と、その前に。
本記事では、次のコードが理解できることを前提条件とさせて頂きます。
このコードは、FizzBuzz 問題を文字数にこだわらずに PowerShell で解いた一例です。
1..100 |
% {
$s = “”;
if (($_ % 3) -eq 0)
{
$s += “Fizz”;
}
if (($_ % 5) -eq 0)
{
$s += “Buzz”;
}
if ($s -eq “”)
{
Write-Host $_;
}
else
{
Write-Host $s;
}
};
よろしいでしょうか。
では、いきなり僕の中での最短コードをお見せします。
1..100|%{$s=@(“Fizz”)[$_%3]+@(“Buzz”)[$_%5];($s,$_)[!$s]}
57 文字です。
このコードを PowerShell で実行してみれば、ちゃんと FizzBuzz になっていることがおわかり頂けると思います。
このコードはどういう理屈で FizzBuzz してるのでしょうか?
このままじゃ見にくいので、このコードにスペースと改行を混ぜ、更にコードスタイルを統一することで、もう少し見やすいコードにしてみましょう。
@(1..100) |
% {
$s = @(“Fizz”)[$_ % 3] + @(“Buzz”)[$_ % 5];
@($s, $_)[!$s];
};
これで見た目は大分わかりやすくなりましたが、3 行目と 4 行目が何をしているのかわかりますか?
3 行目は、3 の倍数なら Fizz、5 の倍数なら Buzz、3 の倍数且つ 5 の倍数なら FizzBuzz を、変数 s に代入する、というコードです。
4 行目は、変数 s が null でなければ変数 s を出力し、変数 s が null ならパイプラインに渡された数値を出力する、というコードです。
どちらもポイントは「配列の添え字」です。
まず、3 行目の @(“Fizz”)[$_ % 3] を見てみます。
“Fizz” という一つの要素を持つ配列を作成し、パイプラインに渡された数値を 3 で割った時の剰余を、配列の添え字に指定しています。
すると、パイプラインに渡された数値が 3 の倍数の場合、添え字は 0 になります。つまり、配列の 0 番目の要素である “Fizz” が取得されます。
そして、それ以外の場合、添え字は 1 か 2 になります。この配列には 1 つしか要素がないので、配列の要素数を超えてしまうことになるのですが、これは PowerShell ではエラーにならず、null が取得されます。
つまり @(“Fizz”)[$_ % 3] は、パイプラインに渡された数値が 3 の倍数なら “Fizz” を返し、それ以外は null を返すのです。
同じく、@(“Buzz”)[$_ % 5] は、パイプラインに渡された数値が 5 の倍数なら “Buzz” を返し、それ以外は null を返します。
更に、この二つは + で繋がれているので、3 の倍数且つ 5 の倍数の時は “FizzBuzz” を返すというわけです。
ちなみに + 演算子の左右が null の場合は null になりますので、3 の倍数でも 5 の倍数でもない場合は null を返します。
次に、4 行目の @($s, $_)[!$s] を見てみます。
変数 s と、パイプラインに渡された数値の、二つの要素を持つ配列を作成し、変数 s を論理否定した値を、配列の添え字に指定しています。
「変数 s を論理否定した値を、配列の添え字に指定」というのが意味不明ですね。まず「変数 s を論理否定した値」 (!$s の部分) だけを見てみましょう。
実は、PowerShell では Boolean 値に限らずどんなオブジェクトにでも、論理否定演算子を使用することができます。その際、オブジェクトは一度暗黙的に Boolean 値に変換されてから論理否定が行われます。そして、文字列を Boolean 値に変換する場合、文字列の長さが 1 以上で “false” という文字列でない場合は true、それ以外の場合 (文字列の長さが 0 である、または “false” という文字列である、または null である場合) は false を返します。
ここでは、変数 s には null か “Fizz” か “Buzz” か “FizzBuzz” しか入りません。つまり、!$s は変数 s が null なら true、それ以外は false を返します。
「変数 s を論理否定した値」の意味がわかりましたので、「変数 s を論理否定した値を、配列の添え字に指定」について見てみましょう。
といっても、後は Boolean 値を添え字にした場合の挙動がわかればいいだけの話です。
Boolean 値を添え字に指定した場合、true なら 1 に、 false なら 0 になります。
つまり、変数 s が null なら 1、それ以外は 0 を返すというわけです。
配列の 0 番目は変数 s、1 番目はパイプラインに渡された数値ですので、先ほど書いたように、変数 s が null でなければ変数 s を出力し、変数 s が null ならパイプラインに渡された数値を出力する、ということになるわけです。
以上で説明終了です。
ちなみに、この最短コードはあくまで僕の中での最短コードです。もしかしたら、これよりもっと短くできるかもしれませんし、できないかもしれません。
それでは。
コメント
わかりやすい解説ですね。
FizzBuzz問題を57文字に短縮できた理由、よくわかりました。
「剰余を、配列の添え字にする」はなるほどw と思いました。
あとは @($s, $_)[!$s] すばらしいですね。
TrueかFalseを利用して配列の要素のどちらを取るかを決めているんですね。
よこけんさんの記事はいつも読みやすいですね。解説がうまいので勉強になります。
次も楽しみにしています。
わかりやすく書けたか心配だったのですが、問題なさそうで安心しました~
> よこけんさんの記事はいつも読みやすいですね。解説がうまいので勉強になります。
いやいや、HIRO さんの記事の方が読みやすいですよ^^;
文章もさることながら、コード掲載する際は PowerShell のコンソールを再現させたりと、凝ってますし。
僕はそこまで気を遣うと、最初の内はいいのですが、段々面倒に感じてしまうので・・・w