From 7b6319986030fc8a2fb5f851a431b5113e774f60 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 15 Mar 2023 02:20:00 +0900 Subject: fix(content): fix XML notations --- .../write-fizzbuzz-in-php-2-letters-per-line.xml | 774 +++++++++++---------- 1 file changed, 421 insertions(+), 353 deletions(-) (limited to 'content/posts/2022-09-29') diff --git a/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml b/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml index 9ef6654..5cb5d93 100644 --- a/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml +++ b/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml @@ -71,80 +71,84 @@ 主な障害 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか? 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。 - #\ - i\ - n\ - c\ - l\ - u\ - d\ - e\ - <\ - s\ - t\ - d\ - i\ - o\ - .\ - h\ - >\ - /* - */ - i\ - n\ - t\ - /* - */ - m\ - a\ - i\ - n( - ){ - f\ - o\ - r( - i\ - n\ - t\ - /* - */ - i= - 1; - i< - 1\ - 0\ - 0; - i\ - +\ - +) - if - (i - %\ - 15 - == - 0) - p\ - r\ - i\ - n\ - t\ - f( - "\ - F\ - i\ - z\ - z\ - B\ - u\ - z\ - z\ - %\ - c\ - ", - 10 - ); + + + /* あとは同じように普通のプログラムを変形するだけなので省略 */ + ]]> + バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。 さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、echo @@ -180,10 +184,14 @@ また、2文字だと文字列がまともに書けないのも辛い。'' だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので -$a -=' -a' -;; + + + とすると $a"\na" になるのだが、余計な改行が入ってしまう。 これらの障害をどのように乗り越えるのか、次節から見ていく。 @@ -192,11 +200,15 @@ a'
普通の (?) fizzbuzz まずは普通に書くとしよう。 - <?php + + + for ($i = 1; $i < 100; $i++) { + echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; + } + ]]> + 素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。
@@ -205,14 +217,18 @@ a' for は、3文字もある長いキーワードである。こんなものは使えない。array_ 系の関数を使って、適当に置き換えるとしよう。 - <?php + + + $s = range(1, 100); + array_walk( + $s, + fn($i) => + printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + ); + ]]> + array_walkrangeprintf といった for よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echo は文 (statement) であり式 (expression) ではないので、式である printf @@ -223,18 +239,22 @@ a' rangearray_walkprintf は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。 - <?php + + + $s = $r(1, 100); + $w( + $s, + fn($i) => + $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + ); + ]]> + これで関数を呼び出している所は短くなった。では、$r$w$p、また 'Fizz''Buzz' はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。 @@ -252,23 +272,31 @@ a' というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、 Fizz という文字列が欲しければ、次のようにする。 -$f -=F -.i -.z -.z -;; + + + こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、@ 演算子を使って抑制してやるとよい。 -$f -=@ -F. -@i -.# -@z -.# -@z -;; + + + むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。 @@ -278,56 +306,72 @@ F. ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (&|^) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。 -$a = "12345"; -$b = "world"; + + + echo $result; + // => F]AXQ + ]]> + これを踏まえ、次のコードを見てみよう。 -$x = "x\nOm\n"; -$y = "\nk!\no"; -$r = $x ^ $y; -echo "$r\n"; + + + 実行すると、range が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。 -$x -='x -Om -'; -$y -=' -k! -o' -; + + + $r = $x ^ $y; + echo "$r\n"; + ]]> + さらに # を使って適当に調整すると、次のようになる。 -$x -=# -'x -Om -'; -$y -=' -k! -o' -;# -$r -=# -$x -^# -$y -;# + + + echo "$r\n"; + ]]> + 1行あたり2文字で、range という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 備考: Buzz 中にある小文字の u は、このロジックだと non-printable @@ -337,155 +381,159 @@ echo "$r\n";
完成系 完成したものがこちら。 - <?php + + + $x + =# + 'i + S' + ;; + $y + =' + b! + '; + $c + =# + $x + ^# + $y + ;# + $x + =# + 'x + Om + '; + $y + =' + k! + o' + ;# + $r + =# + $x + ^# + $y + ;# + $x + =# + 'k + Sk + ~} + Ma + '; + $y + =' + x! + s! + k! + '; + $w + =# + $x + ^# + $y + ;# + $x + =# + 'z + Hd + G' + ;# + $y + =' + x! + ~! + '; + $p + =# + $x + ^# + $y + ;# + $x + =# + 'L + [p + '; + $y + =' + c! + '; + $f + =# + $x + ^# + $y + ;# + $x + =# + 'H + [p + '; + $y + =' + _! + '; + $b + =# + $x + ^# + $y + ;# + $b + [1 + ]= + $c + (# + 13 + *9 + ); + $s + =# + $r + (1 + ,( + 10 + ** + 2) + ); + $w + (# + $s + ,# + fn + (# + $i + )# + => + $p + (( + (# + $i + %3 + ?# + '' + :# + $f + ). + (# + $i + %5 + ?# + '' + :# + $b + )? + :# + $i + )# + .' + ') + ); + ]]> +
感想など @@ -501,18 +549,22 @@ echo "$r\n"; ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。 - <?php + + + printf(` + e\ + c\ + h\ + o\ + \ + 1\ + 2\ + 3\ + `); + ]]> + なお、ここでは簡単のため出力に printf をそのまま使っているが、実際には printf という文字列を合成して可変関数で呼び出す。 ただし、これでは @@ -525,57 +577,73 @@ echo "$r\n"; に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。 もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。 -<?php + + + printf(` + e\ + c\ + h\ + o\ + ${ + '_ + '} + 1\ + 2\ + 3\ + `); + ]]> + 先程と同じく、chrprintf を生成する部分は長くなるので省いた。 -${ -'_ -'} + + + は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。シェルに渡されている文字列は次のようになる。 -e\ -c\ -h\ -o\ -\ -1\ -2\ -3\ + + + これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 ということでこれは別解ということにしておく。 ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。 -${ -'_ -'} + + + 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。
-- cgit v1.2.3-70-g09d2