hogashi.*

日記から何から

 サティで服を見ながら高い安いと言っている親に長らく気になっていた高い安いについてついに問いただして「ミニカーが3万円は高い」「車が3万円は安い」などと答えるので物によってどこから高い安いなのかは違うのだなとなんとなく理解したのをふと思い出した。物によって違うことはわかるが実際それが高いのか安いのかは今もってよくわかっていない。

 

正規表現でmasawada

 masawada Advent Calendar 2021 - Adventar 10日目の記事です。昨日の id:Sixeight さんに見事に釘を刺されてしまったので、ありがたく受け取り、whywaita Advent Calendar 2021 - Adventarはこちらに書きました → 秘蔵UserCSS大放出祭 - hogashi.*。大目に見てください!


 id:masawada / id:whywaita Advent Calendar 七五三おめでとうございます。 7, 5, 3 と素数ですね*1ゴルゴ13が好きな id:hogashi です。
 七五三は 11月 15日にやるそうです。 11 は素数だし、 15 は 3+5+7 ですね。 3×5 でも 15 になります。
 能では、 7歳ごろから稽古を始めるので、このあたりを初心というそうで*2世阿弥曰く、このころに自然に出る風情を大事にするのが良いようです。一方僕は 24歳ですが、このあたりは青年期といい「初心を忘れず稽古すべし」な年頃ということでした。
 3, 5, 15, 初心を忘れず……というわけで FizzBuzz について書きます。正規表現が好きなので正規表現でやってみました*3。せっかくなので masa と wada でいきます。

正規表現FizzBuzz とは

 今回は、入力に対して正規表現でパターンマッチして置換して出力することで、 FizzBuzz もとい masawada を実現することにしました。数字が勝手に入ってくるので、順番にパターンマッチして、 3の倍数かつ 5の倍数なら masawada と置き換える、みたいな感じ。趣味なので速度とかは何も考えてません。パターンが書きたかった。

できあがったものがこちらです

 大きい正規表現を見やすく書きつつ入出力をシュッとやるのに Perl は便利ですね。 8桁くらいまでの数字で試してみたところ、多分あってそう。 GitHub にも公開してあります GitHub - hogashi/fizzbuzz-regexp: FizzBuzz in RegExp

ここを開くと正規表現(が書かれた Perl の長いコード)がバーンと出ます
my $from = 1;
my $to = 30;
for my $i ($from .. $to) {
    # どの順で適用しても大丈夫なようにそれぞれ独立に書いている

    # 3の倍数 かつ 5の倍数ではない
    # - 最後が0と5以外で, 合わせて3の倍数
    $i =~ s/^
        ( # 合わせて3の倍数となるセットのパターン
            # 3の倍数
            [0369]
            |
            # 1,4,7: 3で割った余りが1
            # 2,5,8: 3で割った余りが1
            # 1,4,7のどれかひとつと2,5,8のどれかひとつがあると, 合わせて3の倍数(間に3の倍数があってもよい)
            [147][0369]*[258]
            |
            # 同上, 並びが違うもの
            [258][0369]*[147]
            |
            # 1,4,7のどれかひとつずつが3つあると, 合わせて3の倍数(間に3の倍数があってもよい)
            ([147][0369]*){2}[147]
            |
            # 2,5,8のどれかひとつずつが3つあると, 合わせて3の倍数(間に3の倍数があってもよい)
            ([258][0369]*){2}[258]
            |
            # 22121... のように, 3で割った余りが 2→1→2→1→... と続くパターン
            # これは3で割った余りが2の数の次に, 3で割った余りが2の数→3で割った余りが1の数, というセットが繰り返されると起きる
            # 3の倍数となる場合は, 2→1→...→2→1→1 か 2→1→...→2→1→2→2 のどちらか
            ( # まず3で割った余りが2の数から始まる
                # 1,4,7のどれかひとつずつが2つあると, 合わせて3で割った余りが2(間に3の倍数があってもよい)
                [147][0369]*[147]
                |
                # 2,5,8は3で割った余りが2
                [258]
            )
            ( # 上で拾った数(3で割った余りが2の数)に続いて, 3で割った余りが →1→2→1→2→... と続くパターン (間に3の倍数があってもよい)
                # 2,5,8が続くと3で割った余りが1になり,
                # 1,4,7が続くと3で割った余りが2になる
                # この並び以外のときは, 合わせて3の倍数になるセットのほうでマッチできるはず
                [0369]*[258][0369]*[147]
            )*
            # この間に3の倍数があってもよい
            [0369]*
            ( # 最後に →1 か →2→2 のどちらかで終わると, 合わせて3の倍数となる
                # →1 のパターン
                [147]
                |
                # →2→2 のパターン(間に3の倍数があってもよい)
                [258][0369]*[258]
            )
        )* # 下で1桁はマッチするのでここは2桁目以降になるので, 0回以上の繰り返し
        ( # 合わせて3の倍数となるセットのパターンのうち, 最後の桁が0,5以外
            # 最後の桁で0が出る可能性があったのはここだったので0を削る
            [369]
            |
            [147][0369]*[28]
            |
            [258][0369]*[147]
            |
            ([147][0369]*){2}[147]
            |
            ([258][0369]*){2}[28]
            |
            (
                [147][0369]*[147]
                |
                [258]
            )
            (
                [0369]*[258][0369]*[147]
            )*
            [0369]*
            (
                [147]
                |
                # 最後の桁で5が出る可能性があったのはここ(右の[258])だったので5を削る
                [258][0369]*[28]
            )
        )
        $/masa/x;
    # 3の倍数ではない かつ 5の倍数
    # - 最後が5のとき, 最終的に3の倍数にならないためには, それより前の桁までが, 合わせて3で割った余りが1にならなければよい
    #   - まず「合わせて3の倍数」な桁セットがあるか, 何もない
    #   - その次に「合わせて3で割った余りが2」な桁セットがあるか, 何もない
    #     - つまり「合わせて3で割った余りが1」な桁セットがない
    #   - その次に「合わせて3の倍数」な桁セットがあるか, 何もない
    #   - 最後は5
    # - 最後が0のとき, 最終的に3の倍数にならないためには, それより前の桁までが, 合わせて3の倍数にならなければよい
    #   - まず「合わせて3の倍数」な桁セットがあるか, 何もない
    #   - その次に「合わせて3で割った余りが1」か「合わせて3で割った余りが2」な桁セットがある
    #   - その次に「合わせて3の倍数」な桁セットがあるか, 何もない
    #   - 最後は0
    $i =~ s/^
        (
            # 最後が5のパターン
            ( # 合わせて3の倍数となるセットのパターン
                [0369]
                |
                [147][0369]*[258]
                |
                [258][0369]*[147]
                |
                ([147][0369]*){2}[147]
                |
                ([258][0369]*){2}[258]
                |
                (
                    [147][0369]*[147]
                    |
                    [258]
                )
                (
                    [0369]*[258][0369]*[147]
                )*
                [0369]*
                (
                    [147]
                    |
                    [258][0369]*[258]
                )
            )* # 3の倍数はいくらでもあってよいし, なくてもよい
            ( # 3で割った余りが2なセットのパターン(これがあると最後の桁が5なので最終的に3で割った余りは1になる)
                [258]
                |
                [147][0369]*[147]
            )? # なくてもよい(ないと最後の桁が5なので最終的に3で割った余りは2になる)
            ( # 合わせて3の倍数となるセットのパターン
                [0369]
                |
                [147][0369]*[258]
                |
                [258][0369]*[147]
                |
                ([147][0369]*){2}[147]
                |
                ([258][0369]*){2}[258]
                |
                (
                    [147][0369]*[147]
                    |
                    [258]
                )
                (
                    [0369]*[258][0369]*[147]
                )*
                [0369]*
                (
                    [147]
                    |
                    [258][0369]*[258]
                )
            )* # 3の倍数はいくらでもあってよいし, なくてもよい
            # 最後は5
            5
            |
            # 最後が0のパターン
            ( # 合わせて3の倍数となるセットのパターン
                [0369]
                |
                [147][0369]*[258]
                |
                [258][0369]*[147]
                |
                ([147][0369]*){2}[147]
                |
                ([258][0369]*){2}[258]
                |
                (
                    [147][0369]*[147]
                    |
                    [258]
                )
                (
                    [0369]*[258][0369]*[147]
                )*
                [0369]*
                (
                    [147]
                    |
                    [258][0369]*[258]
                )
            )* # 3の倍数はいくらでもあってよいし, なくてもよい
            ( # 3で割った余りが1 or 2なセットのパターン(最後の桁が0なので最終的に3で割った余りは1 or 2になる)
                # 3で割った余りが1
                [147]
                |
                # 3で割った余りが2
                [258]
                |
                # 3で割った余りが2
                [147][0369]*[147]
                |
                # 3で割った余りが1
                [258][0369]*[258]
            )
            ( # 合わせて3の倍数となるセットのパターン
                [0369]
                |
                [147][0369]*[258]
                |
                [258][0369]*[147]
                |
                ([147][0369]*){2}[147]
                |
                ([258][0369]*){2}[258]
                |
                (
                    [147][0369]*[147]
                    |
                    [258]
                )
                (
                    [0369]*[258][0369]*[147]
                )*
                [0369]*
                (
                    [147]
                    |
                    [258][0369]*[258]
                )
            )* # 3の倍数はいくらでもあってよいし, なくてもよい
            # 最後は0
            0
        )
        $/wada/x;
    # 3の倍数 かつ 5の倍数
    # - 最後が0のとき
    #   - それまでの桁が合わせて3の倍数
    # - 最後が5のとき
    #   - まず「合わせて3の倍数」な桁セットがある
    #   - その次に「合わせて3で割った余りが1」な桁セットがある
    #   - その次に「合わせて3の倍数」な桁セットがある
    #   - 最後は5
    $i =~ s/^
        (
            # 最後が0のパターン
            ( # 合わせて3の倍数となるセットのパターン
                [0369]
                |
                [147][0369]*[258]
                |
                [258][0369]*[147]
                |
                ([147][0369]*){2}[147]
                |
                ([258][0369]*){2}[258]
                |
                (
                    [147][0369]*[147]
                    |
                    [258]
                )
                (
                    [0369]*[258][0369]*[147]
                )*
                [0369]*
                (
                    [147]
                    |
                    [258][0369]*[258]
                )
            )* # 3の倍数はいくらでもあってよいし, なくてもよい
            # 最後は0
            0
            |
            # 最後が5のパターン
            ( # 合わせて3の倍数となるセットのパターン
                [0369]
                |
                [147][0369]*[258]
                |
                [258][0369]*[147]
                |
                ([147][0369]*){2}[147]
                |
                ([258][0369]*){2}[258]
                |
                (
                    [147][0369]*[147]
                    |
                    [258]
                )
                (
                    [0369]*[258][0369]*[147]
                )*
                [0369]*
                (
                    [147]
                    |
                    [258][0369]*[258]
                )
            )* # 3の倍数はいくらでもあってよいし, なくてもよい
            ( # 合わせて3で割った余りが1なセットのパターン(最後の5と合わせて3の倍数となる)
                [147]
                |
                [258][0369]*[258]
            )
            ( # 合わせて3の倍数となるセットのパターン
                [0369]
                |
                [147][0369]*[258]
                |
                [258][0369]*[147]
                |
                ([147][0369]*){2}[147]
                |
                ([258][0369]*){2}[258]
                |
                (
                    [147][0369]*[147]
                    |
                    [258]
                )
                (
                    [0369]*[258][0369]*[147]
                )*
                [0369]*
                (
                    [147]
                    |
                    [258][0369]*[258]
                )
            )* # 3の倍数はいくらでもあってよいし, なくてもよい
            # 最後は5
            5
        )
        $/masawada/x;
    print $i . qq(\n);
}
実行するとこうなります
1
2
masa
4
wada
masa
7
8
masa
wada
11
masa
13
14
masawada
16
17
masa
19
wada
masa
22
23
masa
wada
26
masa
28
29
masawada

どう作ったのか

 ここからが長いので、適宜斜めに読んでください :pray:

最初思いついたやつ

 1 から並んでいると仮定できるなら、数字がなんであれ、 3個おきに masa 、 5個おきに wada 、 15個おきに masawada にしたらよいはずなので、 15個ずつ数字を置き換えまくっていくと完成するはず。

$ seq 1 30 | tr '\n' ',' | perl -pe 's|((?:\d+,){2})\d+,(\d+,)\d+,\d+,((?:\d+,){2})\d+,\d+,(\d+,)\d+,((?:\d+,){2})\d+,|${1}Fizz,${2}Buzz,Fizz,${3}Fizz,Buzz,${4}Fizz,${5}FizzBuzz,|g' | tr ',' '\n'

 ただこれだと、 15個まとめてマッチするので、 1 〜 40 とかで実行したとき 31 〜 40 は余ってしまってマッチせず、 33 とかがそのままになってしまうのでした。ちょっと無念。

Regexp::Assemble

 じゃあどうやるといいのかなと思って、とりあえず Regexp::Assemble *4 に入れてみたところ、いいヒントがもらえて、 3で割った余りが同じ数字の並びのパターンを頑張ってやっていくとできそうなことがわかりました。 111 とか 12 とかになってたら、ひとまとめにして 3の倍数とできるな、みたいな。

$ rassemble 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz
[147][134679]?|[28][23689]?|Fizz(?:Buzz)?|Buzz|[36][12478]|5[23689]
作戦

 ひとつの置換作業でいっぺんにまとめてやろうと思うと、元の数が 3の倍数 (でない) かつ 5の倍数 (でない) という情報を置換するときに伝える必要があるけど、それは無理そうかな〜*5と思ったので、 masa, wada, masawada のそれぞれで計 3回置換することにしました。その代わりの頑張りポイントとして、どの順番でやっても成功するように、それぞれに依存しない独立したパターンを書きました *6*7

masa

 3の倍数かつ 5の倍数でない、というのは「全部の桁を足して 3の倍数、かつ、最後の桁が 0 でも 5 でもない」ということです。頑張って全部の桁を足して 3の倍数であることを確認しつつ、最後の桁が 0, 5 以外、というパターンを書きます。
 まず、数の桁のうちのある箇所が 3の倍数である、というパターンで思いつくのは、

  • (A) 0, 3, 6, 9 が何個でも
    • ...3930...
  • (B) 1, 4, 7 (3で割った余りが 1) が 1つと 2, 5, 8 (3で割った余りが 2) が 1つ
    • ...18...
    • (C) 前後逆でもよい
  • (D) 1, 4, 7 (3で割った余りが 1) が 3つ
    • ...174...
  • (E) 2, 5, 8 (3で割った余りが 2) が 3つ
    • ...255...

という感じになります (あとそれぞれの桁の間に 0, 3, 6, 9 が何個挟まっていてもよい)。これを正規表現に起こすとこう。

qr/
[0369]                 # (A)
|
[147][0369]*[258]      # (B)
|
[258][0369]*[147]      # (C)
|
([147][0369]*){2}[147] # (D)
|
([258][0369]*){2}[258] # (E)
/x

 これに加えて、 ...22 で始まり、 ...22121 と続き、 ...22121211... のようになる場合があります(それぞれの桁で 3で割った余りが同じなら同じなので ...25184271... でも同じ)。 3で割った余りを順番に見ていくと 2→1→2→1→2→1→2→0 で*8、言うなれば「C のパターンが、先頭の 2 と末尾の 1 の間に(1個以上)挟まれている」ような形になっています。先頭の 2 を遠くの末尾の 1 と結びつけるようなパターンは、上で出した A〜E にはないので、これのために別途パターンを書く必要があります。

f:id:hogashi:20211206015926j:plain

 とはいえ、このパターンは割と単純で、 221 か 1121 で始まり、 21 を繰り返し、 1 または 22 で終わります (12, 21, 111, 222 なら B〜E でマッチできる)。 21 を繰り返すのが面白いんですが、これは 3で割った余りが 2 の状態から 0 にならないように 2 で飛び越える感じで、個人的にはラダートレーニングのようなイメージがあります(下の図の②みたいな感じで、またぐために左足を大きく前に出す必要がある)。

https://item-shopping.c.yimg.jp/i/l/ishi0424_training-ladder_6

https://store.shopping.yahoo.co.jp/ishi0424/k-training-ladder.html

 というわけで正規表現に起こすとこうなります。もちろんこの場合も、それぞれの桁の間に 0, 3, 6, 9 が何個挟まっていてもよいです。

qr/
( # 先頭は 11 か 2
    [147][0369]*[147]
    |
    [258]
)
( # 21 の繰り返し
    [0369]*[258][0369]*[147]
)*
[0369]*
( # 最後に →1 か →2→2 のどちらかで終わる
    [147]
    |
    [258][0369]*[258]
)
/x

 ということで、 A 〜 E のパターンと、このラダーのパターンを合わせることで、 3の倍数を表すパターンが完成します。ちなみにこれは今後 wada と masawada でも使います。

 さて、ここまでで 3の倍数を表すことはできました。ここに 5の倍数ではないという条件を追加でくっつけることで、 masa のパターンをつくることができます。
 0, 5 以外で終わる、ということなので、さっきの 3の倍数パターンから、最後の桁が 0, 5 になるものを削ったパターンが、(さっきのパターンの後で)最後に登場していればよいことになります。具体的には、 [0369][369] になったり、 [147][0369]*[258][147][0369]*[28] になったりするわけですね。

qr/
(
    [369]                  # A'
    |
    [147][0369]*[28]       # B'
    |
    [258][0369]*[147]      # C
    |
    ([147][0369]*){2}[147] # D
    |
    ([258][0369]*){2}[28]  # E'
    |
                           # ↓ラダーのパターン'
    (
        [147][0369]*[147]
        |
        [258]
    )
    (
        [0369]*[258][0369]*[147]
    )*
    [0369]*
    (
        [147]
        |
        # 最後の桁で5が出る可能性があったのはここ(右の[258])だったので5を削る
        [258][0369]*[28]
    )
)
$
/x

 組み合わせるとこうなって完成です(最初にまとめて details に入れていた 1つ目を再掲)。

masa(3の倍数かつ5の倍数でない)のパターン
s/^
( # 合わせて3の倍数となるセットのパターン
    # 3の倍数
    [0369]
    |
    # 1,4,7: 3で割った余りが1
    # 2,5,8: 3で割った余りが1
    # 1,4,7のどれかひとつと2,5,8のどれかひとつがあると, 合わせて3の倍数(間に3の倍数があってもよい)
    [147][0369]*[258]
    |
    # 同上, 並びが違うもの
    [258][0369]*[147]
    |
    # 1,4,7のどれかひとつずつが3つあると, 合わせて3の倍数(間に3の倍数があってもよい)
    ([147][0369]*){2}[147]
    |
    # 2,5,8のどれかひとつずつが3つあると, 合わせて3の倍数(間に3の倍数があってもよい)
    ([258][0369]*){2}[258]
    |
    # 22121... のように, 3で割った余りが 2→1→2→1→... と続くパターン
    # これは3で割った余りが2の数の次に, 3で割った余りが2の数→3で割った余りが1の数, というセットが繰り返されると起きる
    # 3の倍数となる場合は, 2→1→...→2→1→1 か 2→1→...→2→1→2→2 のどちらか
    ( # まず3で割った余りが2の数から始まる
        # 1,4,7のどれかひとつずつが2つあると, 合わせて3で割った余りが2(間に3の倍数があってもよい)
        [147][0369]*[147]
        |
        # 2,5,8は3で割った余りが2
        [258]
    )
    ( # 上で拾った数(3で割った余りが2の数)に続いて, 3で割った余りが →1→2→1→2→... と続くパターン (間に3の倍数があってもよい)
        # 2,5,8が続くと3で割った余りが1になり,
        # 1,4,7が続くと3で割った余りが2になる
        # この並び以外のときは, 合わせて3の倍数になるセットのほうでマッチできるはず
        [0369]*[258][0369]*[147]
    )*
    # この間に3の倍数があってもよい
    [0369]*
    ( # 最後に →1 か →2→2 のどちらかで終わると, 合わせて3の倍数となる
        # →1 のパターン
        [147]
        |
        # →2→2 のパターン(間に3の倍数があってもよい)
        [258][0369]*[258]
    )
)* # 下で1桁はマッチするのでここは2桁目以降になるので, 0回以上の繰り返し
( # 合わせて3の倍数となるセットのパターンのうち, 最後の桁が0,5以外
    # 最後の桁で0が出る可能性があったのはここだったので0を削る
    [369]
    |
    [147][0369]*[28]
    |
    [258][0369]*[147]
    |
    ([147][0369]*){2}[147]
    |
    ([258][0369]*){2}[28]
    |
    (
        [147][0369]*[147]
        |
        [258]
    )
    (
        [0369]*[258][0369]*[147]
    )*
    [0369]*
    (
        [147]
        |
        # 最後の桁で5が出る可能性があったのはここ(右の[258])だったので5を削る
        [258][0369]*[28]
    )
)
$/masa/x

 水曜どうでしょうの「これはね……初日がヤマなんでしょう」を思い出す大変さでしたが、この後は、水どうよりは楽になっていきます。

wada

 今度は、 3の倍数でないかつ 5の倍数です。 3の倍数でない、というのを確かめるのは 5の倍数でないことよりも分かりづらいんですが、「3の倍数な部分の桁を全部取り去ったら、残った桁が 3の倍数でない」という作戦でいきます。ということはやはり、さっき作った 3の倍数のパターンは使います。

最後が 0

 まず最後の桁が 0 の場合。全部の桁で見て、 3の倍数になっていないこと (3で割った余りが 1 か 2) が確認できたら ok です。

xxxxxxxxxx0
|--------|
 ここが 3で割った余りが 1 or 2

 3で割った余りが 1 か 2 になるパターンはこれだけ。

qr/
[147]             # 7 とか → 余りは 1
|
[258]             # 8 とか → 余りは 2
|
[147][0369]*[147] # 794 とか → 余りは 2
|
[258][0369]*[258] # 808 とか → 余りは 1
/x

 これの前後に 3の倍数な桁のまとまりがあってもよく、そして最後が 0 、というパターンになります。単純。

最後が 5

 最後の桁が 5 の場合も似たような感じです。が、全体で見たときに、 3で割った余りが(最後の 5 によって)ずれるので、そこに注意します。 5 は 3で割ると余りが 2 なので、最後の桁を除いた桁は、 3の倍数か、 3で割った余りが 2 であれば、 5 と合わせて 3の倍数にならずに済みます (3 で割った余りが 1 のときは 5 と合わせると 3の倍数になってしまう)。

xxxxxxxxxx5
|--------|
 ここが 3の倍数 or 3で割った余りが 2

 正規表現に起こすと、 3で割った余りが 2 なパターンがあるか、何もない、というおもしろパターンになります。これも前後に 3の倍数があってもよく、そして最後は 5。

qr/
(
    [258]             # 8 とか → 余りは 2
    |
    [147][0369]*[147] # 794 とか → 余りは 2
)?                    # これがあるか、何もない
/x

 てことで、最後が 5 のパターンと最後が 0 のパターンのどちらか、という感じでつなげれば、 wada のパターンも完成です。ここまでくれば慣れたものです。

wada (3の倍数でないかつ5の倍数) のパターン
s/^
(
    # 最後が5のパターン
    ( # 合わせて3の倍数となるセットのパターン
        [0369]
        |
        [147][0369]*[258]
        |
        [258][0369]*[147]
        |
        ([147][0369]*){2}[147]
        |
        ([258][0369]*){2}[258]
        |
        (
            [147][0369]*[147]
            |
            [258]
        )
        (
            [0369]*[258][0369]*[147]
        )*
        [0369]*
        (
            [147]
            |
            [258][0369]*[258]
        )
    )* # 3の倍数はいくらでもあってよいし, なくてもよい
    ( # 3で割った余りが2なセットのパターン(これがあると最後の桁が5なので最終的に3で割った余りは1になる)
        [258]
        |
        [147][0369]*[147]
    )? # なくてもよい(ないと最後の桁が5なので最終的に3で割った余りは2になる)
    ( # 合わせて3の倍数となるセットのパターン
        [0369]
        |
        [147][0369]*[258]
        |
        [258][0369]*[147]
        |
        ([147][0369]*){2}[147]
        |
        ([258][0369]*){2}[258]
        |
        (
            [147][0369]*[147]
            |
            [258]
        )
        (
            [0369]*[258][0369]*[147]
        )*
        [0369]*
        (
            [147]
            |
            [258][0369]*[258]
        )
    )* # 3の倍数はいくらでもあってよいし, なくてもよい
    # 最後は5
    5
    |
    # 最後が0のパターン
    ( # 合わせて3の倍数となるセットのパターン
        [0369]
        |
        [147][0369]*[258]
        |
        [258][0369]*[147]
        |
        ([147][0369]*){2}[147]
        |
        ([258][0369]*){2}[258]
        |
        (
            [147][0369]*[147]
            |
            [258]
        )
        (
            [0369]*[258][0369]*[147]
        )*
        [0369]*
        (
            [147]
            |
            [258][0369]*[258]
        )
    )* # 3の倍数はいくらでもあってよいし, なくてもよい
    ( # 3で割った余りが1 or 2なセットのパターン(最後の桁が0なので最終的に3で割った余りは1 or 2になる)
        # 3で割った余りが1
        [147]
        |
        # 3で割った余りが2
        [258]
        |
        # 3で割った余りが2
        [147][0369]*[147]
        |
        # 3で割った余りが1
        [258][0369]*[258]
    )
    ( # 合わせて3の倍数となるセットのパターン
        [0369]
        |
        [147][0369]*[258]
        |
        [258][0369]*[147]
        |
        ([147][0369]*){2}[147]
        |
        ([258][0369]*){2}[258]
        |
        (
            [147][0369]*[147]
            |
            [258]
        )
        (
            [0369]*[258][0369]*[147]
        )*
        [0369]*
        (
            [147]
            |
            [258][0369]*[258]
        )
    )* # 3の倍数はいくらでもあってよいし, なくてもよい
    # 最後は0
    0
)
$/wada/x;
masawada

 ついに masawada です。 3の倍数かつ 5の倍数ですが、 wada の考え方のまま、全体で 3の倍数になるように変えてあげると完成します。つまり、最後が 0 のときはそれ以前は 3の倍数、最後が 5 のときはそれ以前は 3で割った余りが 1 になっていればよいですね。

xxxxxxxxxx0
|--------|
 ここが 3の倍数

xxxxxxxxxx5
|--------|
 ここが 3で割った余りが 1

 最後が 0 の場合は、素朴に 3の倍数のパターン + 0。最後が 5 の場合は、↓に書くような 3で割った余りが 1 のパターンがある (前後に 3の倍数があってもよく、最後が 5) 、でよいです。もう見覚えしかありません。

qr/
[147]             # 7 とか → 余りは 1
|
[258][0369]*[258] # 808 とか → 余りは 1
/x

 完成。めでたく 15 が masawada になります。

masawada (3の倍数かつ 5の倍数) のパターン
s/^
(
    # 最後が0のパターン
    ( # 合わせて3の倍数となるセットのパターン
        [0369]
        |
        [147][0369]*[258]
        |
        [258][0369]*[147]
        |
        ([147][0369]*){2}[147]
        |
        ([258][0369]*){2}[258]
        |
        (
            [147][0369]*[147]
            |
            [258]
        )
        (
            [0369]*[258][0369]*[147]
        )*
        [0369]*
        (
            [147]
            |
            [258][0369]*[258]
        )
    )* # 3の倍数はいくらでもあってよいし, なくてもよい
    # 最後は0
    0
    |
    # 最後が5のパターン
    ( # 合わせて3の倍数となるセットのパターン
        [0369]
        |
        [147][0369]*[258]
        |
        [258][0369]*[147]
        |
        ([147][0369]*){2}[147]
        |
        ([258][0369]*){2}[258]
        |
        (
            [147][0369]*[147]
            |
            [258]
        )
        (
            [0369]*[258][0369]*[147]
        )*
        [0369]*
        (
            [147]
            |
            [258][0369]*[258]
        )
    )* # 3の倍数はいくらでもあってよいし, なくてもよい
    ( # 合わせて3で割った余りが1なセットのパターン(最後の5と合わせて3の倍数となる)
        [147]
        |
        [258][0369]*[258]
    )
    ( # 合わせて3の倍数となるセットのパターン
        [0369]
        |
        [147][0369]*[258]
        |
        [258][0369]*[147]
        |
        ([147][0369]*){2}[147]
        |
        ([258][0369]*){2}[258]
        |
        (
            [147][0369]*[147]
            |
            [258]
        )
        (
            [0369]*[258][0369]*[147]
        )*
        [0369]*
        (
            [147]
            |
            [258][0369]*[258]
        )
    )* # 3の倍数はいくらでもあってよいし, なくてもよい
    # 最後は5
    5
)
$/masawada/x;

 数に対してこれらをどの順番でも適用してやれば、 1, 2, masa, 4, wada, ... という具合に masawada ができるようになりました。作ったときもかなり達成感があったけど、ここまで書いた今も同じくらい達成感があります。読んだ皆さんにも感じてもらえていたら幸いです。

むすび

 正規表現FizzBuzz もとい masawada をしました。足し算とかはできないもんな〜と思っていたけど、意外となんとかなるものなんだな、という感慨があってめちゃくちゃ面白かったです。こういうひたすらこねてなんとかするような趣味にはぴったりなので、皆さんも正規表現と戯れてみてください。 String::Random - Perl module to generate random strings based on a pattern - metacpan.org はおすすめで、なんとブラウザでも遊べるサイトがあります Demo of String_random.js

 masawada Advent Calendar 2021 - Adventar、明日は id:mazco さんです。

追伸

 正規表現可視化サイト (https://regexper.com/) があったので突っ込んでみました。わかりやすい。

masa wada masawada
f:id:hogashi:20211222114042p:plain f:id:hogashi:20211222114034p:plain f:id:hogashi:20211222114047p:plain

*1:masawada さんの m はアルファベットで頭から 13番目、whywaita さんの w は 23番目なので、何かと素数に縁がありそうな雰囲気を感じますね

*2:世阿弥のことば:7段階の人生論

*3:実はちょっと前にやってて、文章を書いていたら年末になっていた

*4:使ったのは GitHub - itchyny/rassemble-go: Go implementation of Regexp::Assemble

*5:もしかしたら方法があるかもしれないけどわからなかった

*6:依存させて良い場合、先に 3の倍数かつ 5の倍数なものを masawada に置換してしまえば、あとは 3の倍数なら masa にしてしまっていい (5の倍数でないことを確認しなくても、 5の倍数でないものしか残っていない)

*7:多分あってると思うけど間違ってたら大目に見てください

*8:最初は 2 なので余りは 2 、次は 2 なので足して 4 なので余りは 1 、次は 1 なので足して 5 なので余りは 2 、という具合

秘蔵UserCSS大放出祭

 世界の whywaita Advent Calendar 2021 - Adventar から、今日は 10日目です。昨日の id:gurapomu さんの記事は図から写真から迫力があってすごい、何かのタイミングで参加させてください。

 ここ最近の id:whywaita さんとの会話の場所は主に大学のサークルの Slack なのですが、何かと YouTube をよく見ていそうだな〜と感じています。
 僕も近頃はよく見ていて、いろいろ思うところがありますが、基本的に安寧を重視した生き方をしているので、ミスって低評価押したりとかしたくないな〜と思ったりしています。あとは低評価の数も見たいことはないですね。
 なので、 YouTubeUserCSS を当てて、低評価ボタンを消して暮らしています。全肯定スタンスになる危険性に気をつけておけば、かなり快適です。ちなみに最近の変更で低評価の数は出なくなったんですかね? why さんは詳しそう。

f:id:hogashi:20211209022359p:plainf:id:hogashi:20211209022632p:plain
before / after
/* ng消す */
div.top-level-buttons ytd-toggle-button-renderer:nth-child(2) {
  display: none;
}

 YouTube の高評価/低評価ボタンには、それぞれを区別するようなわかりやすい識別子がないので、 :nth-child() - CSS: Cascading Style Sheets | MDN を使っています。もし YouTube の気が変わって DOM の感じが変わったら壊れるので気をつけましょう。ちなみに ytd-toggle-button-renderer タグというのが登場していてかっこいいですね。

 という感じで、最近ほんのちょっとだけ UserCSS を当ててそこそこ快適、という暮らしをしているので、いくつかご紹介しようと思います。

www.openssl.org

* {
  font-family: monospace !important;
}

 OpenSSL の公式サイト https://www.openssl.org/ は、フォントが基本的にローマン体です。 Changelog (https://www.openssl.org/news/changelog.html) を眺めたりするのに結構苦しむので、とにかく等幅にして読むことにしています。等幅フォントは Monaco が大好きです。 ai0 がこうあってほしい、が全部揃っていて最高。

f:id:hogashi:20211209013755p:plainf:id:hogashi:20211209013945p:plain
before / after

meet.google.com

[data-is-muted="true"] {
  background-color: greenyellow !important;
}

 大リモート時代っぽい話題ですが、 Google Meet のマイク/カメラのボタンが OFF のときの色を (赤ではなく) 黄緑に変えています。退出ボタンも赤で、たまにミスってミュート解除しようとして退出する、ということがあるので、わかりやすい違う色にしています。 Google Meet は data-is-muted 属性が付いていて識別しやすい。便利。

f:id:hogashi:20211209025003p:plainf:id:hogashi:20211209025114p:plain
before / after

github.com

textarea {
  overflow-anchor: none;
  /* font-family: monospace; */
}

#review-changes-modal .color-bg-default :nth-child(6) {
  background-color: greenyellow;
}
#review-changes-modal .color-bg-default :nth-child(7) {
  background-color: red;
}

 GitHub は話題が 2つ。
 まず issue などを書く際に textarea がガタッとずれて、目で追っていた内容が一気にどっかいってしまうことがあるので、これを防ぐためにアンカリングを無効にしています。あと基本的に等幅フォントで見たい内容ばかり書くので指定していましたが、最近 GitHub 公式でそういう設定が登場してお役御免となりました (GitHub のブログ記事: Fixed-width font support in Markdown-enabled fields | GitHub Changelog)。
 下のほうは単語の通りレビュー時のモーダルです。 Comment / Approve / Request Changes と並んでいますが、個人的に記憶している順序と違うため、「LGTM」とかいいつつ Request Changes する人になったりするので、色をつけてわかりやすくしています。これを初めてから間違えたことがない。

f:id:hogashi:20211209015255p:plain

 難しいのは、 YouTube 同様にこれといった識別子がないことです。この選択肢の div には class 名として form-checkbox が付いているものの、先行する input[type="hidden"] などとこの選択肢が横並びで、これまた nth-child を (しかも 6 とか 7 とか頼りない数字を) 使っています。

status.aws.amazon.com

.statusHistoryContentLeft th:first-child {
  min-width: 400px;
}

 https://status.aws.amazon.com/ は言わずと知れた AWS の Service Health Dashboard ですが、このページの後半の Status History で困ったことはありませんか? そうです。一番下まで見ていくと、いつの間にかめちゃくちゃずれているのです。そんなことある???

f:id:hogashi:20211209020235p:plain

 よ〜く見るとわかるのですが、実は名前が長すぎて 2段になることがあるんですね。このときに微妙〜にずれます。塵も積もれば 7行ずれるんですね……。

f:id:hogashi:20211209020545p:plain
ちょっとずつずれていっている

 聡明な皆さんなら「ちょっと待った、どういう文書構造なんだ?」とお思いのことでしょう。見てみてください。なんとこの table には tr が 1つ (1行) しかなく(!)、 td 1つ (1列)ごとになんと table が入っている(!!)というようなことになっているのです *1

名前の列 日ごとの様子の列 もっと読むの列
1列複数行の子table 複数列複数行の子table 1列複数行の子table

 この構造のおかげで、隣の列と調子を合わせる、ということがかなり難しくなっているので、とりあえず 2段に折れないくらい横幅を伸ばしています。この文書構造ではこうするのが楽かなと思います……。

f:id:hogashi:20211209020256p:plain


 今日は、普段愛用している UserCSS のいくつかをご紹介しました。皆さまのおうちの CSS もぜひ教えてください。

 世界の whywaita Advent Calendar 2021 - Adventar から、明日は id:mizdra さんです。

*1:よくわかりませんが何か事情があるのかな……

MacでCmd-Mでウィンドウがたたまれて困るのでSpotlightにした

f:id:hogashi:20211206014036p:plain

 「Cmd-M でウィンドウをたたむ」みたいな項目がどこにもなくてオフにできない! ので雑に「Spotlight を表示」に Cmd-M を設定してしまうことにした。たたまれて嬉しいことがまずなくて、ミスってたたまれるとすべての元気がなくなってしまうので、 Spotlight が出てごめんって ESC するだけで済むようになって快適。

Monaco Editorをシンタックスハイライトされたコードブロックとして使う

 昨日*1の続き。寝る前にぼんやりしながら glitch.me で頑張っていたのをやめて、机に座ってよく調べると、ちゃんと npm install とかをすっ飛ばして script タグだけで Monaco Editor が出せるらしい (How to run the Monaco editor from a CDN like cdnjs? - Stack Overflow)。
 が、これをブログ記事内に雑に貼ってもどうしてもうまくいかなさそう*2だったので、諦めて Netlify にデプロイして爆速 iframe 版 Monaco Editor を出せるようにした*3

 こういう感じで iframe で読み込む。

<iframe
  class="hogashi-iframe-monaco"
  src="https://amazing-dijkstra-3764b5.netlify.app/?value=function%20isLongString(hoge%3A%20string)%3A%20boolean%20%7B%0A%20%20console.log(hoge)%3B%0A%20%20return%20hoge.length%20%3E%2010%3B%0A%7D%0A">
</iframe>

 こう出る。すすっと書き換えてみて、型で怒られたりできて便利。

 ちょっと頑張って、例えば pre に入れてある内容をバリッと渡して表示することもできる*4。カーソルを合わせると型が出たり、ただの querySelector で引いてきたものは Element 型なので dataset は引けませんということがわかったりして便利。

const entryIdentifier = 'hogashi-iframe-monaco-entry';
const iframeClassName = 'hogashi-iframe-monaco';
const doneClassName = 'hogashi-iframe-monaco-inserted';
const entryContent = document.querySelector(`.entry-content #${entryIdentifier}`).parentElement;
if (entryContent && !entryContent.classList.contains(doneClassName)) {
  Array.from(entryContent.querySelectorAll('pre.code')).map(pre => {
    if (pre.classList.contains(doneClassName) || pre.parentElement.classList.contains(doneClassName)) {
      return;
    }

    const url = new URL('https://amazing-dijkstra-3764b5.netlify.app/');
    const value = encodeURIComponent(pre.textContent);
    const language = encodeURIComponent(pre.dataset.lang || '');
    url.search = `?language=${language}&value=${value}`;

    const iframe = document.createElement('iframe');
    iframe.src = url.href;
    pre.insertAdjacentElement('afterend', iframe);
    iframe.classList.add(iframeClassName);
    iframe.style.height = `${24 * (pre.textContent.split('\n').length + 1)}px`;

    pre.classList.add(doneClassName);
  });
  entryContent.classList.add(doneClassName);
}

 たまに自分の必殺技はブックマークレット正規表現ですって言うことがあるけど、ずっとそういう感じで、ブックマーク編集の input 内とか、ブログの編集画面とかに、生の JavaScript をバリバリ書いたりして、楽しんで暮らしている。そういう素朴な技でも、こういう高級なものが繰り出せるのは便利。

 どうでもいいけど amazing-dijkstra かっこいい。

*1:https://blog.hog.as/entry/2021/12/03/032619

*2:なぜうまくいかないかは丁寧に見てみたいけどいったん置いておく

*3:↑の Stack Overflow の HTML をこういう感じに書き換えて Netlify でデプロイ https://github.com/hogashi/20211204-monaco-editor-iframe

*4:16px に設定していると行の高さは 24px っぽいので雑に決め打ちで iframe の高さを決めてる

 HTML にコードを書くときのシンタックスハイライト、 highlight.js *1 みたいなのを使ったり、 Vim から HTML にしたり*2、色々やりようがあるけど、 TypeScript (特に TypeScript React) のシンタックスハイライトでめっちゃいい感じみたいなものがあんまりなさそうな気がする?
 TypeScript で賢いシンタックスハイライトといえば VSCode なので、 VSCode からうまいこと色付き HTML を出力したい。一応、 Mac でそのままコピペすると色付き HTML になるので、毎回手でうまくコピペするとそれなりにうまくいく(今回はクリップボード API を使ってみた)。ただ、背景色がついてたり、 class 名じゃなくて直に style が当たってたり (Vim でやると class 名に statement とか出てくれる?) 色変えたりしたいときは扱いづらい気がする。あと、自動でやりたいので Headless VSCode みたいなのがあると便利そう (HTML として出力してくれる API は必要そう)。

// VSCode でコピーした後、これをブラウザの開発者ツールに入れて、即座に document にフォーカスを当てると、
// 2秒後にクリップボード API 使って良いか聞かれるので、許可すると開発者ツールに色付き HTML がべろっと出る
setTimeout(() => navigator.clipboard.read().then(res => res[0].getType('text/html').then(b => b.text()).then(te => console.log(te))), 2000)

interface User {
  id: number
  firstName: string
  lastName: string
  role: string
}
 
function updateUser(id: number, update: Partial<User>) {
  const user = getUser(id)
  const newUser = { ...user, ...update }
  saveUser(id, newUser)
}

 ここまで書いて思ったけど、 Monaco Editor *3 にコードを入れて表示する、ということをやると、その場で編集もできて便利かもしれない?  しかし Monaco Editor を頑張って script タグで出そうと思うとかなり難しそうだった。 glitch.me で作って iframe で出すとかはできるけどあまりに面倒なので、なんとかうまく使えたい気がする。あと Monaco Editor が画面に登場するまで時間がかかる (glitch.me だから?)。

 最近は何もしない土日をよく過ごしていて、特にそうしたいという気持ちはないものの、やりたいぜということもない状態。それでも、こないだは寺を見に行ったり、今日は床に発泡シートを敷いたり、ちょっとずつ活動はしている。それが終わるとあとは何もしない。何もしないの結構難しくて、なんかやんないといけない焦りもなんとなくあり、しかし元気いっぱいでもないので、F1の録画を見たり、ゴルゴ13(1巻から読み直している)を読んだりしている。順調に目は疲れ続けている気がするので、休日に目を休める方策として瞑想を身につけてみたい。