blob: 53be0aed760355a1fc34e7223e25839bd0729f44 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
|
<!DOCTYPE html>
<html lang="ja-JP">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="nsfisis">
<meta name="copyright" content="© 2022 nsfisis">
<meta name="description" content="PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。">
<meta name="keywords" content="PHP">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<title>【PHP】 fizzbuzz を書く。1行あたり2文字で。 | REPL: Rest-Eat-Program Loop</title>
<link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f">
<link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525">
<link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144">
</head>
<body class="single">
<header class="header">
<nav class="nav">
<p class="logo">
<a href="/">REPL: Rest-Eat-Program Loop</a>
</p>
</nav>
</header>
<main class="main">
<article class="post-single">
<header class="post-header">
<h1 class="post-title">【PHP】 fizzbuzz を書く。1行あたり2文字で。</h1>
<ul class="post-tags">
<li class="tag">
<a href="/tags/php">PHP</a>
</li>
</ul>
</header>
<div class="post-content">
<section>
<h2 id="changelog">更新履歴</h2>
<ol>
<li class="revision">
<time datetime="2022-09-28">2022-09-28</time>: 公開
</li>
<li class="revision">
<time datetime="2022-09-29">2022-09-29</time>: 小さな文言の修正・変更
</li>
</ol>
</section>
<section id="section--_記事の構成について">
<h2><a href="#section--_記事の構成について">記事の構成について</a></h2>
<p>
この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、<a xl:href="https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings">このページ</a>にソースコードがあるので、そちらを先に見てほしい。
</p>
</section>
<section id="section--_レギュレーション">
<h2><a href="#section--_レギュレーション">レギュレーション</a></h2>
<p>
PHP で、次のような制約の下に fizzbuzz を書いた。
</p>
<ul>
<li>
<p>
1行あたりの文字数は2文字までに収めること (ただし<code><?php</code>タグは除く)
</p>
<ul>
<li>
<p>
厳密な定義:<code><?php</code>タグ以降のソースコードが、2 byte ごとに ラインフィード (LF) で区切られること
</p>
</li>
</ul>
</li>
<li>
<p>
スペースやタブを使用しないこと
</p>
</li>
<li>
<p>
ループのアンロールをしないこと
</p>
<ul>
<li>
<p>
100 回ループの代わりに 100 回コードをコピペ、というのは禁止
</p>
</li>
</ul>
</li>
<li>
<p>
PHP 7.4〜8.1 で動作すること
</p>
</li>
<li>
<p>
実行時に Notice や Warning が出ないこと
</p>
</li>
<li>
<p>
標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
</p>
</li>
</ul>
<p>
備考: PHP には<code>short_open_tag</code>というオプションがあり、これを有効にするとファイル冒頭の<code><?php</code>の代わりに<code><?</code>を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。
</p>
</section>
<section id="section--_主な障害">
<h2><a href="#section--_主な障害">主な障害</a></h2>
<p>
1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?
</p>
<p>
特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。
</p>
<pre language="c" linenumbering="unnumbered">
<code>#\
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
);
/* あとは同じように普通のプログラムを変形するだけなので省略 */</code>
</pre>
<p>
バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
</p>
<p>
さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、<code>echo</code>で出力することや、<code>for</code>でループすること、<code>new</code>でインスタンスを生成することができない。特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。
</p>
<p>
当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである:
</p>
<ul>
<li>
<p>
<code>_</code>:<code>gettext</code>のエイリアス
</p>
</li>
<li>
<p>
<code>dl</code>: 拡張モジュールをロードする
</p>
</li>
<li>
<p>
<code>pi</code>: 円周率を返す
</p>
</li>
</ul>
<p>
(環境によって多少は変わるかも)
</p>
<p>
2文字の関数を定義しまくった拡張モジュールを用意しておいて<code>dl()</code>で読み込む行為は、レギュレーションで定めた
</p>
<blockquote>
<ul>
<li>
<p>
標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
</p>
</li>
</ul>
</blockquote>
<p>
に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。
</p>
<p>
また、2文字だと文字列がまともに書けないのも辛い。<code>''</code>だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
</p>
<pre language="php" linenumbering="unnumbered">
<code>$a
='
a'
;;</code>
</pre>
<p>
とすると<code>$a</code>は<code>"\na"</code>になるのだが、余計な改行が入ってしまう。
</p>
<p>
これらの障害をどのように乗り越えるのか、次節から見ていく。
</p>
</section>
<section id="section--_解説">
<h2><a href="#section--_解説">解説</a></h2>
<section id="section--_普通の_fizzbuzz">
<h3><a href="#section--_普通の_fizzbuzz">普通の (?) fizzbuzz</a></h3>
<p>
まずは普通に書くとしよう。
</p>
<pre class="monospaced">
<code><?php
for ($i = 1; $i < 100; $i++) {
echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
}</code>
</pre>
<p>
素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。
</p>
</section>
<section id="section--_for_の排除">
<h3><a href="#section--_for_の排除"><code>for</code>の排除</a></h3>
<p>
<code>for</code>は、3文字もある長いキーワードである。こんなものは使えない。<code>array_</code>系の関数を使って、適当に置き換えるとしよう。
</p>
<pre language="php" linenumbering="unnumbered">
<code><?php
$s = range(1, 100);
array_walk(
$s,
fn($i) =>
printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
);</code>
</pre>
<p>
<code>array_walk</code>や<code>range</code>、<code>printf</code>といった<code>for</code>よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<code>echo</code>は文 (statement) であり式 (expression) ではないので、式である<code>printf</code>に置き換えた。
</p>
</section>
<section id="section--_関数呼び出しの短縮">
<h3><a href="#section--_関数呼び出しの短縮">関数呼び出しの短縮</a></h3>
<p>
<code>range</code>、<code>array_walk</code>、<code>printf</code>は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。
</p>
<pre language="php" linenumbering="unnumbered">
<code><?php
$r = 'range';
$w = 'array_walk';
$p = 'printf';
$s = $r(1, 100);
$w(
$s,
fn($i) =>
$p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
);</code>
</pre>
<p>
これで関数を呼び出している所は短くなった。では、<code>$r</code>や<code>$w</code>や<code>$p</code>、また<code>'Fizz'</code>や<code>'Buzz'</code>はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。
</p>
</section>
<section id="section--_余談_php_8_x_で動作しなくてもいいなら">
<h3><a href="#section--_余談_php_8_x_で動作しなくてもいいなら">余談: PHP 8.x で動作しなくてもいいなら</a></h3>
<p>
今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。
</p>
<blockquote>
<ul>
<li>
<p>
PHP 7.4〜8.1 で動作すること
</p>
</li>
</ul>
</blockquote>
<p>
というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、<code>Fizz</code>という文字列が欲しければ、次のようにする。
</p>
<pre language="php" linenumbering="unnumbered">
<code>$f
=F
.i
.z
.z
;;</code>
</pre>
<p>
こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、<code>@</code>演算子を使って抑制してやるとよい。
</p>
<pre language="php" linenumbering="unnumbered">
<code>$f
=@
F.
@i
.#
@z
.#
@z
;;</code>
</pre>
<p>
むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。
</p>
</section>
<section id="section--_文字列リテラルの短縮">
<h3><a href="#section--_文字列リテラルの短縮">文字列リテラルの短縮</a></h3>
<p>
実際に使った手法の説明に移る。
</p>
<p>
ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (<code>&</code>、<code>|</code>、<code>^</code>) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。
</p>
<pre language="php" linenumbering="unnumbered">
<code>$a = "12345";
$b = "world";
// $a ^ $b は次のコードと同じ
$result = '';
for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) {
$result .= $a[$i] ^ $b[$i];
}
echo $result;
// => F]AXQ</code>
</pre>
<p>
これを踏まえ、次のコードを見てみよう。
</p>
<pre language="php" linenumbering="unnumbered">
<code>$x = "x\nOm\n";
$y = "\nk!\no";
$r = $x ^ $y;
echo "$r\n";</code>
</pre>
<p>
実行すると、<code>range</code>が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。
</p>
<pre language="php" linenumbering="unnumbered">
<code>$x
='x
Om
';
$y
='
k!
o'
;
$r = $x ^ $y;
echo "$r\n";</code>
</pre>
<p>
さらに<code>#</code>を使って適当に調整すると、次のようになる。
</p>
<pre language="php" linenumbering="unnumbered">
<code>$x
=#
'x
Om
';
$y
='
k!
o'
;#
$r
=#
$x
^#
$y
;#
echo "$r\n";</code>
</pre>
<p>
1行あたり2文字で、<code>range</code>という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。
</p>
<p>
備考:<code>Buzz</code>中にある小文字の<code>u</code>は、このロジックだと non-printable な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。
</p>
</section>
</section>
<section id="section--_完成系">
<h2><a href="#section--_完成系">完成系</a></h2>
<p>
完成したものがこちら。
</p>
<pre language="php" linenumbering="unnumbered">
<code><?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
)#
.'
')
);</code>
</pre>
</section>
<section id="section--_感想など">
<h2><a href="#section--_感想など">感想など</a></h2>
<p>
PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。
</p>
<p>
みんなもプログラムを細長くしよう。
</p>
</section>
<section id="section--_余談2_別解">
<h2><a href="#section--_余談2_別解">余談2: 別解</a></h2>
<p>
PHP では、バッククォートを使ってシェルを呼び出せる。これは<code>shell_exec</code>関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
</p>
<pre language="php" linenumbering="unnumbered">
<code><?php
printf(`
e\
c\
h\
o\
\
1\
2\
3\
`);</code>
</pre>
<p>
なお、ここでは簡単のため出力に<code>printf</code>をそのまま使っているが、実際には<code>printf</code>という文字列を合成して可変関数で呼び出す。
</p>
<p>
ただし、これでは
</p>
<blockquote>
<ul>
<li>
<p>
スペースやタブを使用しないこと
</p>
</li>
</ul>
</blockquote>
<p>
に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。
</p>
<p>
もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
</p>
<pre language="php" linenumbering="unnumbered">
<code><?php
$c = 'chr';
${
'_
'}
=#
$c
(#
32
).
$c
(#
92
);
printf(`
e\
c\
h\
o\
${
'_
'}
1\
2\
3\
`);</code>
</pre>
<p>
先程と同じく、<code>chr</code>や<code>printf</code>を生成する部分は長くなるので省いた。
</p>
<pre class="monospaced">
<code>${
'_
'}</code>
</pre>
<p>
は変数で、中にはスペースとエスケープが入っている (<code>chr(32) . chr(92)</code>)。シェルに渡されている文字列は次のようになる。
</p>
<pre class="monospaced">
<code>e\
c\
h\
o\
\
1\
2\
3\</code>
</pre>
<p>
これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。
</p>
<p>
ということでこれは別解ということにしておく。
</p>
<p>
ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
</p>
<pre class="monospaced">
<code>${
'_
'}</code>
</pre>
<p>
最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。
</p>
</section>
</div>
</article>
</main>
<footer class="footer">
© 2021 nsfisis
</footer>
</body>
</html>
|