FizzBuzz 問題

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 ならパイプラインに渡された数値を出力する、ということになるわけです。

以上で説明終了です。

ちなみに、この最短コードはあくまで僕の中での最短コードです。もしかしたら、これよりもっと短くできるかもしれませんし、できないかもしれません。

それでは。

コメント

  1. HIRO より:

    わかりやすい解説ですね。

    FizzBuzz問題を57文字に短縮できた理由、よくわかりました。

    「剰余を、配列の添え字にする」はなるほどw と思いました。

    あとは @($s, $_)[!$s]  すばらしいですね。
    TrueかFalseを利用して配列の要素のどちらを取るかを決めているんですね。

    よこけんさんの記事はいつも読みやすいですね。解説がうまいので勉強になります。
    次も楽しみにしています。

  2. よこけん より:

    わかりやすく書けたか心配だったのですが、問題なさそうで安心しました~

    > よこけんさんの記事はいつも読みやすいですね。解説がうまいので勉強になります。
    いやいや、HIRO さんの記事の方が読みやすいですよ^^;
    文章もさることながら、コード掲載する際は PowerShell のコンソールを再現させたりと、凝ってますし。
    僕はそこまで気を遣うと、最初の内はいいのですが、段々面倒に感じてしまうので・・・w

タイトルとURLをコピーしました