更新履歴

    - -
  1. - : 公開 -
  2. - -
  3. - : 2問目、3問目の解説を追加、1問目に加筆 -
  4. - +
  5. + : 公開 +
  6. +
  7. + : 2問目、3問目の解説を追加、1問目に加筆 +
-
-

- - はじめに - -

-
-
-

本日開始された PHPerKaigi 2022 の PHPer -チャレンジにおいて、弊社 -デジタルサーカス株式会社 の問題を -3問作成した。この記事では、これらの問題の解説をおこなう。

-
-
-

リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens

-
-
-
-
-

- - 第1問 brainf_ck.php - -

-
-
-

ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。

-
-
-
-
<?php
+          
+

はじめに

+

+ 本日開始されたPHPerKaigi 2022の PHPer チャレンジにおいて、弊社デジタルサーカス株式会社の問題を 3問作成した。この記事では、これらの問題の解説をおこなう。 +

+ +

+ リポジトリはこちら:https://github.com/nsfisis/PHPerKaigi2022-tokens +

+
+ +
+

第1問 brainf_ck.php

+

+ ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。 +

+ +
+              <?php
 
-declare(strict_types=0O1);
+  declare(strict_types=0O1);
 
-namespace Dgcircus\PHPerKaigi\Y2022;
+  namespace Dgcircus\PHPerKaigi\Y2022;
 
-/**
- * @todo
- * Run this program to acquire a PHPer token.
- */
+  /**
+  * @todo
+  * Run this program to acquire a PHPer token.
+  */
 
-https://creativecommons.org/publicdomain/zero/1.0/
+  https://creativecommons.org/publicdomain/zero/1.0/
 
-\error_reporting(~+!'We are hiring!');
+  \error_reporting(~+!'We are hiring!');
 
-$z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
-$id = \spl_object_id(...);
-$put = fn($c) => \printf('%c', $c);
-$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
+  $z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
+  $id = \spl_object_id(...);
+  $put = fn($c) => \printf('%c', $c);
+  $mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
 
-$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
-$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
-$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
-$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
-$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
-$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
-    +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
-      $b => $loop(++$pc, ++$n),
-      $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
-      default => $loop(++$pc, $n),
-    })($pc, -![])],
-    default => [$mp, ++$pc],
-};
-$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
-    +!![] => [$mp, ++$pc],
-    default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
-      $e => $loop(--$pc, ++$n),
-      $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
-      default => $loop(--$pc, $n),
-    })($pc, -![])],
-};
-$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
-  isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
-)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
+  $👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
+  $👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
+  $👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
+  $👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
+  $📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
+  $🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+  +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+  $b => $loop(++$pc, ++$n),
+  $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
+  default => $loop(++$pc, $n),
+  })($pc, -![])],
+  default => [$mp, ++$pc],
+  };
+  $🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+  +!![] => [$mp, ++$pc],
+  default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+  $e => $loop(--$pc, ++$n),
+  $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
+  default => $loop(--$pc, $n),
+  })($pc, -![])],
+  };
+  $🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
+  isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
+  )($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
 
-$🐘([
-  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-  $🤡,
-    $👉, $👍, $👍, $👍,
-    $👉, $👍, $👍, $👍, $👍, $👍,
-    $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-    $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-    $👈, $👈, $👈, $👈, $👎,
-  $🎪,
-  $👉, $👍, $👍, $👍, $👍, $👍, $📝,
-  $👎, $👎, $📝,
-  $👉, $👎, $👎, $👎, $📝,
-  $👉, $👎, $👎, $👎, $📝,
-  $👎, $👎, $📝,
-  $👎, $📝,
-  $👈, $📝,
-  $👉, $👉, $👎, $👎, $📝,
-  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
-  $👈, $👎, $👎, $👎, $👎, $📝,
-  $👈, $📝,
-  $👉, $👍, $👍, $📝,
-  $👉, $👎, $📝,
-  $👈, $📝,
-]);
-
-
-
-

この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。

-
-
-

- - 解説 - -

-
-
-

- - 絵文字 - -

-
-
-

まず目につくのは大量の絵文字だろう。 PHP -は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。

-
-
-
-
-

- - プログラム全体 - -

-
-
-

Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck -とは、難解プログラミング言語のひとつであり、ここで説明するよりも -Wikipedia の該当ページを読んだ方がよい。

-
- -
-

なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。

-
-
-
-
+ + + + + + + + + +
+  $🐘([
+  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+  $🤡,
+  $👉, $👍, $👍, $👍,
+  $👉, $👍, $👍, $👍, $👍, $👍,
+  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+  $👈, $👈, $👈, $👈, $👎,
+  $🎪,
+  $👉, $👍, $👍, $👍, $👍, $👍, $📝,
+  $👎, $👎, $📝,
+  $👉, $👎, $👎, $👎, $📝,
+  $👉, $👎, $👎, $👎, $📝,
+  $👎, $👎, $📝,
+  $👎, $📝,
+  $👈, $📝,
+  $👉, $👉, $👎, $👎, $📝,
+  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
+  $👈, $👎, $👎, $👎, $👎, $📝,
+  $👈, $📝,
+  $👉, $👍, $👍, $📝,
+  $👉, $👎, $📝,
+  $👈, $📝,
+  ]);
+            
+ +

+ この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。 +

+ +
+

解説

+
+

絵文字

+

+ まず目につくのは大量の絵文字だろう。 PHP は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。 +

+
+ +
+

プログラム全体

+

+ Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck とは、難解プログラミング言語のひとつであり、ここで説明するよりも Wikipedia の該当ページを読んだ方がよい。 +

+ +

+ https://ja.wikipedia.org/wiki/Brainfuck +

+ +

+ なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。 +

+ +
+                  + + + + + + + + + +
 [
-  > + + +
-  > + + + + +
-  > + + + + + + + + + + + +
-  > + + + + + + + + + +
-  < < < < -
+> + + +
+> + + + + +
+> + + + + + + + + + + + +
+> + + + + + + + + + +
+< < < < -
 ]
 > + + + + + .
 - - .
@@ -227,541 +187,454 @@ Wikipedia の該当ページを読んだ方がよい。

< . > + + . > - . -< .
-
-
-
-

実行結果はこちら: https://ideone.com/22VWmb

-
-
-

それぞれの絵文字で表された関数が、各命令に対応している。

-
-
-
    -
  • -

    $👉: >

    -
  • -
  • -

    $👈: <

    -
  • -
  • -

    $👍: +

    -
  • -
  • -

    $👎: -

    -
  • -
  • -

    $📝: .

    -
  • -
  • -

    $🤡: [

    -
  • -
  • -

    $🎪: ]

    -
  • -
-
-
-

, (入力) に対応する関数はない -(このプログラムでは使わないので用意していない)。

-
-
-

なお、$🐘 はいわゆる main 関数であり、プログラムの実行部分である。

-
-
-
-
-

- - 絵文字の選択 - -

-
-
-

おおよそ意味に合致するよう選んでいるが、$🤡$🎪 -は弊社デジタルサーカスにちなんでいる。 また、$🐘 は PHP -のマスコットの象に由来する。

-
-
-
-
-

- - strict_types - -

-
-
-

declare 文の strict_types に指定できるのは、01 -の数値リテラルだが、 0x00b1 のような値も受け付ける。 今回は、PHP -8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

-
-
-
-
-

- - URL - -

-
-
-

ソースコードのライセンスを示したこの部分だが、

-
-
-
-
https://creativecommons.org/publicdomain/zero/1.0/
-
-
-
-

完全に合法な PHP のコードである。 https: 部分はラベル、// -以降は行コメントになっている。

-
-
-
-
-

- - リテラルなしで数値を生成する - -

-
-
-

ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 -PHP では、型変換を利用することで任意の整数を作り出すことができる。

-
-
-
-
assert(0 === +!![]);
-assert(1 === +![]);
-assert(2 === ![]+![]);
-assert(3 === ![]+![]+![]);
-assert(10 === +(![].+!![]));
-
-
-
-

[]! を適用すると true が返ってくる。それに + -を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 -はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する -('10')。これに + を適用すると、string から int -への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10 -個足し合わせてももちろん 10 が作れる)。

-
-
-

また、error_reporting に指定しているのは -1 である。 これは、! -によって文字列を false にし、+ によって false0 -にし、さらにビット反転して -1 にしている。

-
-
-
-
-

- - if 文なしで条件分岐 - -

-
-
-

三項演算子ないし match 式を使うことで、if -を一切書かずに条件分岐ができる。 また、&& / || も使えることがある。 -遅延評価が不要なケースでは、[$t, $f][$cond] -のような形で分岐することもできる。

-
-
-
-
-

- - whilefor 文なしでループ - -

-
-
-

不動点コンビネータを使って無名再帰する -(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に -Z コンビネータとして知られるものを使った ($z)。

-
-
-

実際のところ、$🤡$🎪$🐘 は、一度 Scheme (Lisp の一種) -で書いてから PHP に翻訳する形で記述した。

-
-
-

なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) -ので、 あまりに長い brainf*ck -プログラムを書くとスタックオーバーフローする。

-
-
-
-
-
-
-
-
-

- - 第2問 riddle.php - -

-
-
-

ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。

-
-
-
-
<?php
+< .
+                
+ +

+ 実行結果はこちら:https://ideone.com/22VWmb +

+ +

+ それぞれの絵文字で表された関数が、各命令に対応している。 +

+ +
    +
  • +

    + $👉:> +

    +
  • + +
  • +

    + $👈:< +

    +
  • + +
  • +

    + $👍:+ +

    +
  • + +
  • +

    + $👎:- +

    +
  • + +
  • +

    + $📝:. +

    +
  • + +
  • +

    + $🤡:[ +

    +
  • + +
  • +

    + $🎪:] +

    +
  • +
+ +

+ ,(入力) に対応する関数はない (このプログラムでは使わないので用意していない)。 +

+ +

+ なお、$🐘はいわゆる main 関数であり、プログラムの実行部分である。 +

+
+ +
+

絵文字の選択

+

+ おおよそ意味に合致するよう選んでいるが、$🤡$🎪は弊社デジタルサーカスにちなんでいる。 また、$🐘は PHP のマスコットの象に由来する。 +

+
+ +
+

strict_types

+

+ declare文のstrict_typesに指定できるのは、01の数値リテラルだが、0x00b1のような値も受け付ける。 今回は、PHP 8.1 から追加された、0Oまたは0oから始まる八進数リテラルを使った。 +

+
+ +
+

URL

+

+ ソースコードのライセンスを示したこの部分だが、 +

+ +
+                  https://creativecommons.org/publicdomain/zero/1.0/
+                
+ +

+ 完全に合法な PHP のコードである。https:部分はラベル、//以降は行コメントになっている。 +

+
+ +
+

リテラルなしで数値を生成する

+

+ ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 PHP では、型変換を利用することで任意の整数を作り出すことができる。 +

+ +
+                  assert(0 === +!![]);
+assert(1 === +![]);
+assert(2 === ![]+![]);
+assert(3 === ![]+![]+![]);
+assert(10 === +(![].+!![]));
+                
+ +

+ []!を適用するとtrueが返ってくる。それに+を適用すると、boolからintヘの型変換が走り、1が生成される。10はさらにトリッキーだ。まず10を作り、.で文字列として結合する ('10')。これに+を適用すると、stringからintへの型変換が走り、10が生まれる (コード量に頓着しないなら、1を 10 個足し合わせてももちろん 10 が作れる)。 +

+ +

+ また、error_reportingに指定しているのは-1である。 これは、!によって文字列をfalseにし、+によってfalse0にし、さらにビット反転して-1にしている。 +

+
+ +
+

if文なしで条件分岐

+

+ 三項演算子ないしmatch式を使うことで、ifを一切書かずに条件分岐ができる。 また、&&/||も使えることがある。 遅延評価が不要なケースでは、[$t, $f][$cond]のような形で分岐することもできる。 +

+
+ +
+

whilefor文なしでループ

+

+ 不動点コンビネータを使って無名再帰する (詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に Z コンビネータとして知られるものを使った ($z)。 +

+ +

+ 実際のところ、$🤡$🎪$🐘は、一度 Scheme (Lisp の一種) で書いてから PHP に翻訳する形で記述した。 +

+ +

+ なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) ので、 あまりに長い brainf*ck プログラムを書くとスタックオーバーフローする。 +

+
+ + + +
+

第2問 riddle.php

+

+ ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。 +

+ +
+              <?php
 
-/*********************************************************
- * This program displays a PHPer token.                  *
- * Guess 'N'.                                            *
- *                                                       *
- * Hints:                                                *
- * - N itself has no special meaning, e.g., 42, 8128,    *
- *   it is selected at random.                           *
- * - Each element of $token represents a single letter.  *
- * - One letter consists of 5x5 cells.                   *
- * - Remember, the output is a complete PHPer token.     *
- *                                                       *
- * License:                                              *
- *   https://creativecommons.org/publicdomain/zero/1.0/  *
- *********************************************************/
-const N = 0 /* Change it to your answer. */;
-assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
+  /*********************************************************
+  * This program displays a PHPer token.                  *
+  * Guess 'N'.                                            *
+  *                                                       *
+  * Hints:                                                *
+  * - N itself has no special meaning, e.g., 42, 8128,    *
+  *   it is selected at random.                           *
+  * - Each element of $token represents a single letter.  *
+  * - One letter consists of 5x5 cells.                   *
+  * - Remember, the output is a complete PHPer token.     *
+  *                                                       *
+  * License:                                              *
+  *   https://creativecommons.org/publicdomain/zero/1.0/  *
+  *********************************************************/
+  const N = 0 /* Change it to your answer. */;
+  assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
 
-$token = [
-  0x14B499C,
-  0x0BE34CC, 0x01C9C69,
-  0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
-  0x01C1C66, 0x0FC1C47, 0x01C1C66,
-  0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
-];
-foreach ($token as $x) {
-  $x = $x ^ N;
+  $token = [
+  0x14B499C,
+  0x0BE34CC, 0x01C9C69,
+  0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
+  0x01C1C66, 0x0FC1C47, 0x01C1C66,
+  0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
+  ];
+  foreach ($token as $x) {
+  $x = $x ^ N;
 
-  $x = sprintf('%025b', $x);
-  $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-  $x = implode("\n", str_split($x, length: 5));
-  echo "{$x}\n\n";
-}
-