「FizzBuzz」タグアーカイブ

PHPでInverse FizzBuzz(逆FizzBuzz)問題を問題を考えた

Fizz,Buzzとかのリストから最小で最短のFizzBuzzを出力できる連続数列を返すみたいなやつ。
強引に解いた感じでスマートじゃないけど関数型脳みそだから仕方ない。

中身

<?php
header('Content-Type: text/plain');
// Inverse FizzBuzz
inverseFizzBuzz(array('fizz')); // 3
inverseFizzBuzz(array('buzz')); // 5
inverseFizzBuzz(array('fizz', 'buzz')); // 9,10
inverseFizzBuzz(array('buzz', 'fizz')); // 5,6
inverseFizzBuzz(array('fizz', 'buzz', 'fizz')); // 3,4,5,6
inverseFizzBuzz(array('fizz', 'fizz')); // 6,7,8,9
inverseFizzBuzz(array('fizz', 'fizz', 'buzz')); // 6,7,8,9,10
inverseFizzBuzz(array('fizzbuzz', 'fizz')); // 15,16,17,18
// 解なし
inverseFizzBuzz(array('buzz', 'buzz'));
inverseFizzBuzz(array('buzz', 'fizz', 'buzz'));
inverseFizzBuzz(array('fizz', 'hoge'));
exit;
function inverseFizzBuzz($fb) {
    $values = array();
    for ($n = 1; $n < 101; $n++) {
        if ($fb[0] != n2fb($n)) continue;
        if ($arr = fb2array($fb, $n)) $values[] = $arr;
    }
    print implode(', ', $fb) . ' -> ' . implode(', ', fblen($values)) . "n";
}
function fblen($arr) {
    // 最短のを選ぶ
    if (!count($arr)) return $arr;
    $n = count($arr[0]);
    $ifb = $arr[0];
    foreach ($arr as $value) {
        if (count($value) < $n) {
            $ifb = $value;
            $n = count($value);
        }
    }
    return $ifb;
}
function fb2array($fb, $n) {
    // fizz,buzz,fizzbuzzから配列を得る
    $values = array($n);
    $length = count($fb);
    $fb = array_slice($fb, 1);
    for ($i = 1; $i < $length; $i++) {
        if (!count($fb)) break;
        // 15ぐらい先までで十分
        for ($j = $n + 1, $end = $n + 15; $j <= $end; $j++) {
            if ($j > 100) break;
            $values[] = $j;
            // fizz,buzzの順番が違ったらやめる
            if (n2fb($j) && $fb[0] != n2fb($j)) break;
            if ($fb[0] == n2fb($j)) {
                // fizz,buzz,fizzbuzzのどれかの場合は$fb[0]取り出してまた15先まで見る
                $fb = array_slice($fb, 1);
                $end += 15;
                if (!count($fb)) {
                    // 最小で終わらせる
                    break;
                }
            }
        }
    }
    if (count($fb) > 0) {
        return null;
    } else {
        return $values;
    }
}
function n2fb($n) {
    // 数値からfizzbuzzを得る
    return $n % 15 ? $n % 5 ? $n % 3 ? '' : 'fizz' : 'buzz' : 'fizzbuzz';
}
?>

実行結果

fizz -> 3
buzz -> 5
fizz, buzz -> 9, 10
buzz, fizz -> 5, 6
fizz, buzz, fizz -> 3, 4, 5, 6
fizz, fizz -> 6, 7, 8, 9
fizz, fizz, buzz -> 6, 7, 8, 9, 10
fizzbuzz, fizz -> 15, 16, 17, 18
buzz, buzz -> 
buzz, fizz, buzz -> 
fizz, hoge -> 

PHPでFizzBuzz問題を考えてみた

まず、FizzBuzz問題とは。

1~100までの数字で3で割り切れる時は「Fizz」、5で割り切れる時は「Buzz」、両者で割り切れる時は「FizzBuzz」と出力する。

たったこれだけです。
せんだ!みつお!なはなは!とか3の倍数と3が付くときのアレみたいな感じです。

「2分以内で書け」や「1行で書け」や「○○バイト以内で書け」や「%を使うな」などの条件を付ける場合もありますが、これをPHPで書いたらどうなるかと思いましてやってみました。

<?php
for ( $i = 1; $i <= 100; $i++ ) {
    if ( $i % 15 == 0 ) {
        print 'FizzBuzz';
    } else if ( $i % 3 == 0 ) {
        print 'Fizz';
    } else if ( $i % 5 == 0 ) {
        print 'Buzz';
    } else {
        print $i;
    }
    print "n";
}
// <プークスクス
?>

実行結果

ハッ 嘲笑なんか気にしなければこれで良いんですよ!!

では「1行で書く」ってのを考えると、これを改行入れなければいいじゃん?ってなる訳で「どれだけ短く書けるか」ってのに挑戦しました。

まず、 if { } else if { } … この辺が長いので三項演算子を使うことに。
あまりひねらない書き方ですが、それぞれを三項演算子に置き換え、1行で書いた方が短いので for文の {} や余計なスペース、最後のセミコロンも省略します。

<?php for($i=1;$i<=100;$i++)print($i%15?($i%3?($i%5?$i:'Buzz'):'Fizz'):'FizzBuzz')."n"?>

何かそれっぽくなってきた!!
この時点で89バイト。

更に短くするにはと思いついたのが
for ( $i = 1; $i <= 100; $i++ ) の部分です。ここを for ( $i = 1; $i < 101; $i++ ) にしたら1文字減りますね。 何かで見た事あった次の書き方でも動きます。 for ( $i = 0; ++$i < 101; ) これでいい気もしますが101ってのがどうも気に入らないので1減らして for ( $i = 0; $i++ < 100; ) この書き方の方が見やすいかもです。文字数変わりませんし。 [php title="1行でFizzBuzz (87バイト)"]<?php for($i=0;$i++<100;)print($i%15?($i%5?($i%3?$i:"Fizz"):"Buzz"):"FizzBuzz")."n";?>[/php] これをTwitterに書いた直後に print を echo に変えるって気付きました。 echo は print と違って ,(コンマ) で区切って出力するって方法があります。ついでに最初の <?php を <? でもいいっての思い出して [php title="79bytes"]<?for($i=0;$i++<100;)echo$i%15?($i%3?($i%5?$i:'Buzz'):'Fizz'):'FizzBuzz',"n"?>[/php] ギリギリ80バイトの壁を越えました。 もうこの辺からparse error覚悟で動けばいいや!ってスペースどこ削って大丈夫か体当たり的になってます。 echo$i なんか続けて書いていいのかよ!とツッコミ入れたい。 ただ、この書き方 15の時がどうにか出来そうな気がします。 「3で割り切れる」=「FizzもしくはFizzBuzzなのでFizzだけ出力」、「5で割り切れない」=「数字だけ」ということを頭に入れて15という数字を消すと [php gutter="0" classname="inline"]( $i % 3 ( $ % 5 ? $i : '' ) : 'Fizz' ), ( !$i % 5 ? 'Buzz' : '' )[/php] こう書くこともできます。 いや ( !$i%5 ? 'Buzz' : '' ) は逆に出来るじゃないか… ということで ( $i%5 ? '' : 'Buzz' ) それでPHPの定数で思い出した事が1つ。 定義してない定数(主にtypo)はそのまま定数名が出力されたことありませんか? このPHPのいい加減な何でも文字列を利用して 「Fizz, Buzzという定数のつもりだけど実は定義してなかったからそのまま出力してね!」作戦。 [php title="FizzBuzz 67bytes "]<?for($i=0;$i++<100;)echo$i%3?($i%5?$i:''):Fizz,$i%5?'':Buzz,"n"?>[/php] ここで67バイトまで縮めることが出来ました。 ギブアップ。 翌日、forじゃなくても良いんじゃね?て思いwhileに変えると結構短く出来ました。 [php title="FizzBuzz 63bytes"]<?while($i++<100)echo$i%3?($i%5?$i:''):Fizz,$i%5?'':Buzz,"n"?>[/php] ここで、Twitterにpostしたところ救世主 @daira4000 さんに「三項演算子の()を外して、nを直接LFにする」と教えて頂きましてさらに短くなり、最後の ?> は ; に変えても大丈夫ということでついに60バイトの壁突破。

<?while($i++<100)echo$i%3?$i%5?$i:'':Fizz,$i%5?'':Buzz,"
";

実行結果

ΩΩΩ< ()は消しても大丈夫なのか-!!!
さらに考えたけど順番が変わった程度でした。

<?while($i++<100)echo$i%3?'':Fizz,$i%5?$i%3?$i:'':Buzz,"
";

PHPでは最短56バイトで書けるらしいんですがそんな脳みそは持ち合わせておりません。おわり。