なまえは まだ ない

思いついたことをアウトプットします

年の瀬に阿呆なプログラムを書いた

こんにちは。早いものでもう年末です。

皆さんは世界のナベアツという芸人をご存知でしょうか?

2007年頃に爆笑レッドカーペットという番組で「3の倍数と3が付く数字のときだけ阿呆になります」というネタを披露し、一斉を風靡した芸人さんです。現在は「桂三度」の名前で落語家として活動されています。

3の倍数と3が付く数字のときだけ阿呆になります

数字を1から順に数えていき、3の倍数と3が付く数字の時に「サァンwww」と奇声を発しながら変顔をするというネタです。 3の倍数だけでなく「3が付く数字」でも阿呆になるというところがミソで、13や23といった3の倍数ではない数字も含まれます。

最初にこのパターンが登場するのは13のときで、「ジュウニwwwジュウサァンwww」と連続で阿呆になるわけです。 ここでこのネタの意図に気付かせ、クライマックスとなる30〜39では10連チャンで阿呆になり続けます。 本当によく考えられたネタで、当時高専生だった私は初めて見た時涙を流しながら笑ったのを覚えています。

で、世界のナベアツがどうしたって?

さて、前置きが長くなってしまいましたが、事の発端はTwitterに流れてきたこのツイートになります。

氏がどうやってこのライブラリに辿り着いたのか全く以て不明ですが、なるほどたしかに、と思いまして。 ちょうど仕事で行き詰まって頭を抱えていたときだったので、気分転換にと思ってGoで作ってみることにしました。

go-nabeatsu の紹介

作成したものがこちらです。

github.com

コマンドとして使う

Goがインストールされている環境であれば、次のコマンドで簡単にインストールできます。

go install github.com/furusax0621/go-nabeatsu/cmd/nabeatsu@v1.0.0

nabeatsu コマンドの引数として数字を渡すだけです。3の倍数と3の付く数字であれば世界のナベアツよろしく阿呆になり、それ以外では渡された数字をそのまま返します。

$ nabeatsu 3 // 3の倍数
サァンwww

$ nabeatsu 4 // 3の倍数ではない
4

$ nabeatsu 13 // 3の付く数字
ジュウサァンwww

ライブラリとして組み込む

コマンドで遊ぶだけじゃつまらない(?)と思い、世界のナベアツらしい要素をAPIとして提供しています。

pkg.go.dev

渡された文字列が阿呆になるべきか判定する IsFool と、渡された文字列を世界のナベアツみのある文言に変換する GetFoolExpression です。 なお、 GetFoolExpression 内部で IsFool を呼び出しているので、次のようなシンプルな記述をするだけで当時のネタを再現できます。

package main

import (
    "fmt"
    "strconv"

    "github.com/furusax0621/go-nabeatsu"
)

func main() {
    for i := 1; i <= 40; i++ {
        fmt.Println(nabeatsu.GetFoolExpression(strconv.Itoa(i)))
    }
}

Go Playground - The Go Programming Language

go-nabeatsu の実装について

阿呆なライブラリですが、ちょっとだけ考えた点を紹介します。

数字の扱いについて

Goのプリミティブ型である uint64 で扱える最大値はせいぜい20桁程度になります。 20桁(1019)というと千京になりますが、日本の数字の接頭辞はそれより上のものがいくつもあります。

数字でマトモに扱おうとすると簡単に桁あふれが発生してしまうので、あえて string 型で扱うことにしました。 string で扱うと当然数字以外の文字列を考慮しなくてはいけません。ここは単純に正規表現で数字の羅列かどうかを判定するようにしています。

var mustNumber = regexp.MustCompile(`^\d+$`)

よくGoの正規表現エンジンは遅いと言われますが、私はナベアツにそこまでの速度を求めていないので、これで十分だと思っています。

3の倍数の判定

先のツイートにもあったとおり、3の倍数かどうかを判定するには各桁の数字を足していき、その結果が3の倍数かどうかを判定すればよいです。 後述しますがこのライブラリでは千無量大数の桁まで扱えるようにしているので、最大で72桁になります。

理論上の最大値は各桁が9で埋まった時なので、3の倍数の判定は9 * 72 = 648までの値を扱えれば十分であるとわかります。 あとは加算していった結果を3で割り切れるかを判定すれば良いです。

また、先に書いたとおりこのライブラリ内で数字を文字列として扱っています。 strconv パッケージ等を使って stringint 変換をしても良かったのですが、APIの定義上エラーハンドリングを考えなくてはいけません。 こんな阿呆なライブラリのためにそこまで作り込みたくなかったので、Unicodeコードポイントの差分をとることで数字に変換するようにしています。

var sum uint64
for _, r := range s {
    sum += uint64(r - '0')
}

阿呆な読み方への変換

困ったことに日本語はかなり複雑な言語なので、仮数部によって接頭辞の読みが変わったり、逆に接頭辞によって仮数部の読みが変わったりします。 いくつか例を挙げると

  • 100は いち-ひゃく と読まず ひゃく と読む
  • 300は さん-ひゃく と読まず さん-びゃく と読む
  • 10,000,000は せん-まん または いち-せん-まん と読まず いっ-せん-まん と読む

スマートに解決する方法が見当たらなかったので、大分泥臭い組み立て方をしています。

一の位〜千の位までの読み上げ方は、万や億といった接頭辞がつくかどうかでも微妙に変化します。ここの共通化は諦め、まず1〜1,000までの数字の読み方を構築し、その後接頭辞(万、億、兆……)毎に区間を切って変換していく、という実装にしました。

ナベアツの性能限界

日本語として扱われている最大の接頭辞は(私が知る限り)無量大数になるので、千無量大数より桁が上がると読み上げ方がわからなくなってしまいます。ここらを性能限界と決め、これより大きい桁の数字が来たら無限大(ムゲンダァイwww)と判定してもらうことにしました。

まとめ

Go言語で世界のナベアツを再現するライブラリを開発しました。

実装に関して物申したい方がいらっしゃいましたら、Pull Requestをお待ちしております。

参考