にゃみかんてっくろぐ

猫か百合を見守る壁になりたい

Linux コマンドの最長しりとりを求める

Linux コマンドでしりとりをすると、最長でいくつ繋がるのか?」

この疑問を解消すべく、最長しりとりを求めるプログラムを実装しました。

結論

最長しりとり問題

論文「最長しりとり問題の解法」

全パターン網羅して最長を決める… といきたいところですが、単語数が増えてくると数時間掛けても解けません。

調べてみると、なんと論文になっています。整数計画問題として定式化した上で、分枝限定法により解を得るものです。さらには、既に実装済みのWebサービスソースコードまであります。

しかし、 Crystal を触りたい、という別の目的から車輪の再発明をすることにしました。

Crystal 実装

ソースコード

論文を読み解きつつ再発明した実装は、 GitHub に公開しています(ソルバーに GLPK を使用しています)。やみくもに木で探索するものも入っています。

Crystal 言語

Crystal は、 Ruby に非常に似た文法を持つ、型推論の付いた静的型付け言語です。

private def self.find_closed_path(v : Char, x : Hash(String, Int32), k : Int32)
  edges = x.select { |k, v| v > 0 }.map { |e| e[0] }
  find_closed_path_recursively(v.to_s, edges, k)
end

型の指定方法、キャスト、プロパティの指定方法などは公式ドキュメントを参照しましたが、それ以外は Ruby のつもりで書けました。それくらい似ています。

ただし、 gem は使えません( C バインディングは使えます)。「文法が似ている全く別の言語」であることを思い出させてくれます。

結果

各種 Docker イメージの PATH に含まれているコマンドを抽出し、最長しりとりを求めました(いずれも数秒程度で解き終わりました)。

Docker image:tag コマンド数 しりとり長 使用率
alpine:3.10.3 317 173 54.6%
amazonlinux:2.0.20191016.0 261 146 55.9%
centos:8 573 326 56.9%
debian:10.2 409 231 56.5%

なんと CentOS 8 では 300 以上のコマンドが繋がります。 Amazon Linux が Alpine Linux よりもコマンド数が少ないなど、しりとりとは無関係に興味深い点もあります。

以下に、しりとりの内容を記載します。 w または z で始まり、数字で終わっている点はどのディストリビューションも共通です。

Alpine Linux 3.10.3

Length: 173
Shiritori: whoami => ipcs => strings => setlogcons => setkeycodes => swapoff => fgrep => powertop => pmap => pgrep => pwd => depmod => dd => dc => crontab => brctl => lzma => add-shell => login => nologin => nsenter => rmdir => rm => microcom => md5sum => mkdir => reset => timeout => test => ttysize => ether-wake => env => volname => eject => touch => halt => tr => realpath => hdparm => modinfo => openvt => truncate => expr => raidautorun => nl => logger => rfkill => lzopcat => tunctl => lzcat => tail => ln => nmeter => remove-shell => lsusb => bc => chown => nproc => comm => mpstat => tac => crond => dnsdomainname => expand => dirname => ed => deluser => rmmod => date => echo => od => deallocvt => tar => readahead => dumpkmap => passwd => delgroup => ping => gzip => patch => hexdump => pkill => lzop => printenv => vconfig => gunzip => pwdx => xzcat => top => pscan => nc => cksum => mkpasswd => du => unlzma => arping => getopt => true => egrep => printf => fsync => cal => lsof => fdflush => hostid => diff => fbsplash => head => df => fstrim => mesg => getconf => fsck => killall => losetup => poweroff => flock => klogd => dmesg => grep => pidof => findfs => sysctl => ls => setserial => less => sum => mkmntdirs => sha512sum => mkdosfs => su => unix2dos => sync => clear => run-parts => slattach => hd => dumpleases => sendmail => ldconfig => groups => sha3sum => mktemp => ps => sha256sum => mkswap => pipe_progress => sha1sum => mknod => dos2unix => xargs => shuf => fuser => rev => vi => ifconfig => getent => tty => yes => scanelf => fatattr => readlink => kill => lspci => ipcalc => cryptpw => whois => swapon => nameif => factor => rdev => vlock => killall5

Amazon Linux 2.0.20191016.0

Length: 146
Shiritori: zic => curl => ln => nohup => pwd => dd => db_load => diff => fg => gpg => gpg-error => rmdir => rpm => md5sum => mkdir => rpmkeys => sotruss => stat => tzselect => tsort => tset => trust => tput => timeout => test => tabs => split => tty => yes => signver => reset => tr => rpmdb => bg => gsettings => sum => makedb => bashbug => groups => stdbuf => false => ex => xmlcatalog => glib-compile-schemas => sprof => factor => rview => whoami => iconvconfig => getpcaps => shuf => fold => df => find => db_printlog => gdbm_load => dir => read => db_tuner => realpath => hostid => db_recover => rm => mknod => db_stat => truncate => expand => db_checkpoint => touch => head => dircolors => sha512sum => mkfifo => oldfind => db_hotbackup => pldd => db_dump => paste => egrep => printf => fgrep => pinentry-curses => ssltap => pr => rvi => infotocap => printenv => vi => infocmp => pinky => yum => mktemp => ptx => xargs => sleep => python => nice => expr => runcon => numfmt => true => echo => od => du => users => sha384sum => mv => vdir => rpcgen => nl => ls => sha256sum => modutil => lua => applygnupgdefaults => setcap => pk12util => ldconfig => getopts => sdiff => fmt => tail => luac => chown => nproc => cp => pydoc => cut => tic => chkconfig => gdbus => sync => crlutil => localedef => fc => cmp => p11-kit => tac => cmsutil => logname => env => view => wc => certutil => ldd => diff3

CentOS 8

Length: 326
Shiritori: zic => cracklib-unpacker => runuser => rtpr => rmdir => rmmod => dmfilemapd => depmod => dd => dbus-send => db_load => dnf => fg => gpg => groupdel => loginctl => localectl => lastb => busctl => logname => egrep => pmap => pgrep => ps => systemd-tmpfiles => systemd-sysusers => systemd-cgls => strings => ss => sotruss => systemd-delta => as => sum => md5sum => mksquashfs => sha512sum => mkfs.cramfs => sha384sum => mkfs => systemd-run => nologin => newusers => su => users => systemd-machine-id-setup => prlimit => tzselect => tsort => trust => timeout => test => telinit => taskset => tracepath => hash => halt => top => pinky => yum => mktemp => pwunconv => vi => ipcs => swapon => nohup => pwconv => vipw => w => wipefs => sulogin => newuidmap => pwscore => elfedit => type => eject => truncate => echo => objcopy => ypdomainname => evmctl => lesspipe.sh => hostnamectl => login => nl => losetup => pkill => lsns => systemctl => lslogins => sysctl => lslocks => swaplabel => ls => skill => less => setfacl => lnstat => timedatectl => last => tail => ldattach => hostname => ethtool => ldconfig => gpgparsemail => lastlog => gsettings => sg => groups => systemd-hwdb => bg => gapplication => namei => ifcfg => groupmems => systemd-cgtop => pkg-config => glib-compile-schemas => strip => ping => getpcaps => setpriv => vmcore-dmesg => getfacl => lsmem => makedb => bashbug => getopts => systemd-path => hexdump => pwdx => xmlcatalog => gprof => fips-finish-install => localedef => fips-mode-setup => printf => fgrep => poweroff => fsck.cramfs => swapoff => findfs => stdbuf => fstrim => mesg => gpgconf => fsck.minix => xzless => sprof => fold => df => find => dbus-test-tool => lsmod => dwp => pwd => dmsetup => pldd => dracut => tload => dmesg => genl => lsinitrd => db_printlog => gdbmtool => ldd => dbus-uuidgen => nisdomainname => expand => dbus-run-session => nm => mknod => dirmngr-client => touch => hostid => db_hotbackup => pkgconf => fsck => kmod => du => update-crypto-policies => sha256sum => mkinitrd => db_dump => pidof => flock => kill => ld.gold => dhclient-script => tty => yes => sha224sum => mkdumprd => dhclient => true => ex => x86_64-redhat-linux-gnu-pkg-config => gdbus => sha1sum => modinfo => od => dirmngr => read => dir => routef => fix-info-dir => resolvconf => factor => runlevel => logger => rpm => mkhomedir_helper => rm => mkdir => runcon => nsenter => rpmdb => blkid => dbus-monitor => ranlib => blkdiscard => db_tuner => realpath => head => db_recover => rtmon => nice => expr => rview => watchgnupg => gtar => rpmkeys => sleep => pwhistory_helper => routel => ld.bfd => delpart => tr => rfkill => ld => dbus-update-activation-environment => tar => raw => whoami => iconvconfig => gpg-error => rdma => applygnupgdefaults => shutdown => newgrp => printenv => vigr => readelf => fdisk => kernel-install => ln => newgidmap => pivot_root => tee => env => vdir => rdisc => coreutils => sync => command => dbus-daemon => nproc => chpasswd => dmstats => sha512hmac => chmod => dircolors => sha384hmac => chgpasswd => dbus-cleanup-sockets => sha256hmac => cd => db_stat => tipc => chkconfig => getconf => fc => capsh => hwclock => kexec => comm => mountpoint => tac => colrm => mkfs.minix => xzdec => cracklib-packer => rvi => ipcalc => chcpu => update-alternatives => sha224hmac => cksum => mv => view => wc => cp => pr => resolvectl => lsipc => chgrp => ptx => xargs => sha1hmac => chmem => mkswap => partx => xz => zless => shuf => faillock => kdumpctl => lscpu => unxz => znew => whereis => setterm => mkfifo => objdump => p11-kit => tracepath6

Debian 10.2

Length: 231
Shiritori: znew => wc => ctrlaltdel => ldconfig => groupmod => dd => diff => fgrep => paste => e2image => expr => runuser => rmt-tar => rmdir => rm => md5sum => mkfs.cramfs => ss => swapon => nologin => newusers => switch_root => tzselect => tsort => tset => tput => timeout => test => taskset => tarcat => tune2fs => stat => tabs => su => users => split => tty => yes => sum => mkfs.bfs => sha512sum => mkfs => sha384sum => mke2fs => sha256sum => md5sum.textutils => sha224sum => mkhomedir_helper => runcon => nsenter => rtstat => tr => run-parts => sha1sum => mkdir => resize2fs => sort => tar => rtcwake => e4crypt => truncate => expiry => ypdomainname => e2mmpstatus => script => true => env => vigr => renice => egrep => pr => rgrep => pager => rtmon => nohup => ptx => xargs => sulogin => newgrp => printf => factor => routef => fsck.cramfs => swapoff => findfs => stdbuf => fold => df => find => dash => hostid => delgroup => pwd => dumpe2fs => shred => dircolors => setsid => debugfs => sed => du => usermod => dpkg-deb => b2sum => mknod => debconf => fstrim => mklost+found => dpkg-trigger => readprofile => expand => dpkg-maintscript-helper => realpath => head => debconf-set-selections => sleep => policy-rc.d => debconf-apt-progress => setcap => pldd => dir => rbash => hostname => echo => od => dpkg => groupadd => dmesg => getconf => filefrag => gzip => ping => groups => shadowconfig => groupmems => sg => getpcaps => savelog => getopt => tzconfig => gpasswd => debconf-copydb => bashbug => getent => toe => e4defrag => gunzip => pidof => faillog => groupdel => lastlog => genl => login => nl => losetup => perl => lsattr => routel => logger => rename.ul => lsns => swaplabel => lscpu => userdel => ldd => deluser => remove-shell => localedef => fmt => tail => ln => namei => installkernel => lslogins => shuf => findmnt => tempfile => e2label => lslocks => setterm => mesg => grep => passwd => deb-systemd-helper => rdma => add-shell => lsipc => chown => nproc => comm => mountpoint => tipc => cksum => mount => tic => choom => mktemp => prlimit => tc => chmem => mkswap => pivot_root => tac => cppw => wdctl => lsmem => mv => vipw => wipefs => sync => chcpu => useradd => debconf-show => wall => ls => sdiff => fdformat => tee => e2freefrag => getcap => pwunconv => vdir => raw => whoami => install => lastb => badblocks => start-stop-daemon => numfmt => touch => hwclock => killall5

ふたたび結論

紅葉スポットと紅葉状況をツイートから推定する

ひとり開発 Advent Calendar 2019 10日目の記事です。

成果物: ソーシャル紅葉見頃情報

Webサービスソーシャル紅葉見頃情報」を作りました。

f:id:no_clock:20191209230023p:plain

動機: 紅葉情報サイトの「見頃」表示が信用できない

以下の写真は、複数の紅葉情報サイトで「紅葉状況:見頃」だった時の御岳昇仙峡(山梨県)の様子です。

f:id:no_clock:20191209230525j:plain

正直、見頃はまだ先だと感じました。Twitterを調べても同様の感想を持った方が多いようで、甲府市観光課も「全体的に3分〜4分付き」と表現されている状況でした。

せっかく紅葉を見に行くなら、見頃のピークに行きたいものです。

しかし、紅葉情報サイトの「見頃」は判定が緩いようで使えません。Twitterで流れているツイートのほうが、はるかに信用できそうです……… ということは、ツイートを活用すれば、より厳しく「見頃」を判断出来る可能性がありそうです。

作ることにしました。

解説: サービス構成

サービスの全体像は以下の通り。ぜんぶ書き慣れている Ruby で楽しく書きました。

f:id:no_clock:20191208204338p:plain

それぞれ順を追って説明していきます。

ツイート収集

「紅葉」「見頃」「色づき」などのキーワードで、紅葉状況と思わしきツイートをひたすらに収集します。

紅葉スポットの自動抽出

紅葉スポットは、それこそ ウォーカープラスの紅葉情報ウェザーニュースの紅葉Ch. から集める方法もありそうですが、依存したくないので ツイートから紅葉スポット名も抽出 します。

形態素解析: MeCab + mecab-ipadic-NEologd

ツイートを形態素解析エンジン MeCab に掛けて 固有名詞 を拾います。辞書には、最新の固有表現が多数採録されている mecab-ipadic-NEologd を用います。例えば、福島県会津地方にある景勝地塔のへつり」も正しく抽出できます。

# 標準の辞書 (mecab-ipadic)
$ echo "福島県南会津郡下郷町、塔のへつり。" | mecab -d /usr/local/lib/mecab/dic/ipadic/
福島  名詞,固有名詞,地域,一般,*,*,福島,フクシマ,フクシマ
県  名詞,接尾,地域,*,*,*,県,ケン,ケン
南会津  名詞,固有名詞,地域,一般,*,*,南会津,ミナミアイヅ,ミナミアイズ
郡  名詞,接尾,地域,*,*,*,郡,グン,グン
下郷  名詞,固有名詞,地域,一般,*,*,下郷,シモゴウ,シモゴー
町  名詞,接尾,地域,*,*,*,町,マチ,マチ
、  記号,読点,*,*,*,*,、,、,、
塔の  名詞,固有名詞,地域,一般,*,*,塔の,トウノ,トーノ
へ  助詞,格助詞,一般,*,*,*,へ,ヘ,エ
つり  名詞,一般,*,*,*,*,つり,ツリ,ツリ
。  記号,句点,*,*,*,*,。,。,。
EOS
# 今回使用する辞書 (mecab-ipadic-NEologd)
$ echo "福島県南会津郡下郷町、塔のへつり。" | mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/
福島県  名詞,固有名詞,地域,一般,*,*,福島県,フクシマケン,フクシマケン
南会津  名詞,固有名詞,地域,一般,*,*,南会津,ミナミアイヅ,ミナミアイズ
郡  名詞,接尾,地域,*,*,*,郡,グン,グン
下郷町  名詞,固有名詞,地域,一般,*,*,下郷町,シモゴウマチ,シモゴーマチ
、  記号,読点,*,*,*,*,、,、,、
塔のへつり  名詞,固有名詞,一般,*,*,*,塔のへつり,トウノヘツリ,トーノエツリ
。  記号,句点,*,*,*,*,。,。,。
EOS

ここから固有名詞を拾って、「福島県」「南会津」「下郷町」「塔のへつり」が紅葉スポットの候補となります。

都道府県推定

紅葉スポットの候補に対し、都道府県を推定します。

方法はシンプルで、『京都の南禅寺』のように都道府県名が一緒になっているものを拾い上げ、その最頻値を推定結果としているだけです。ただし、「高尾山口」が「山口」になったり「東京都」が「京都」になったりしないよう、一部正規表現で避けています。

ノイズ除去

紅葉スポットの候補から、ノイズと思われる名称を除外します。

  • 都道府県の推定が分散しすぎている場合
    • 例えば「植物園」は全国にあり、特定のスポットを指していないので除外したい
  • 半角文字を含むか、漢字が1文字も含まれない場合
    • 「1日」「エモい」なども固有名詞として候補に挙がっているため除外したい

紅葉状況の推定

ここまでで紅葉スポットの候補が定まったため、紅葉状況を推定します。

スコア算出

ツイートに含まれる「紅葉状況っぽいキーワード」を抽出し、 0(青葉)〜1(見頃)〜2(落葉) の間で数値化します。

正直ここはかなりゴリ押しです。たとえば 「早かった」は0.4、「もう少し」は0.7、「ちょうど」は1、 のようにで数値を設定しています。複数あれば平均を取ります。

Webサービス

これで紅葉スポット・紅葉状況の情報が揃ったため、Webサービスとして提供します。息を吸うようにフロントエンドを書ける人間ではないので、古き良き非SPAでの実装です。

非紅葉スポットフィルタ(雑)

ノイズ除去をしても紅葉スポット「でない」固有名詞が大量に残るため、ここでフィルタします。ひたすらキーワードでフィルタを掛ける一時しのぎで、改善の余地が残る部分です。

place !~ /^(.+観光客|外国人.+|最盛期|今日この頃|.+気温|何度|四季桜|可能性|寒い朝|世界遺産|誕生日|今シーズン|黄緑|[0-9]+|皇室献上|.+観光|お久しぶり|開催中|全国的|敷地内|温暖化|五平餅|お勧め|青紅葉|.+|.+|.+流星群|.+|.+旅行|暑さ|.+|桜の木|桜を見る会|ロケ地|.+公開|女子力|仙山線|お題|お姉ちゃん|外国人|私たち|朝活|好きだ|平野部|数年|異常気象|トロッコ列車|知らんけど|山手線|予防接種|午前中|募集中|.週間|観光.+|冬桜|落葉高木|今月末|.+市内|目的地|質問箱|.+|.+年前|リア充|.|分からん|.+希望|オフ会|聖地巡礼|子どもたち|大嘗宮|代表者|落羽松|つけ麺|定点観測|入場料|競馬場|.|.|.?週末|冷たい雨||お客さん|時間帯|)$/

検証: 紅葉状況の推定結果

約3週間動かしたので、推定結果を見ておきます。と言っても、Twitterのツイート群からの推定結果とツイートを比べるので出来レースです。

高尾山

f:id:no_clock:20191208212934p:plain

東京 高尾山の推定結果ですが、11/27頃から見頃に近く、12/7頃に見頃を過ぎようとしているようです。高尾ビジターセンターのTwitterアカウントと比べてみます。

11/16 見頃間近

11/24 見頃

12/1 見頃

12/6 落葉進む

推定結果「11/27頃から見頃に近く、12/7頃に見頃を過ぎ」は実際の様子とかなり近そうです。

東福寺

f:id:no_clock:20191208212922p:plain

京都 東福寺は、11/23〜11/30頃で見頃間近〜見頃を行ったり来たりしています。12/1以降は見頃過ぎのようです。公式Twitterアカウントがないため、ユーザのツイートと比べてみます。

11/16 見頃間近

11/23 見頃

11/27 見頃

12/1 落葉始まり

12/7 ほぼ終わり

推定結果「11/23〜11/30頃で見頃間近〜見頃」と期間的には合致しました。スコアがもう少し安定していれば、良い結果と言えそうです。

課題

概ね出来上がりましたが、まだまだ課題は多いです。

  • サンプル数の不足。マイナーな紅葉スポットではほとんど推定出来ない
  • 紅葉スポットのノイズ。紅葉スポット以外が大量に含まれている
  • 紅葉以外への対応。春までに桜の開花状況に対応したい

まとめ

  • 紅葉スポットと紅葉状況を推定するWebサービスを開発した
  • 有名所の推定精度はそれなりにありそうだった
  • 精度向上や桜への対応なんかは今後の課題

ソースコードGitHubに公開しています。

私の転職活動は中学生から始まっていた

SIer脱出を語る Advent Calendar 2019 8日目の記事です。

「アウトプット大事」というHow toの話しかしません。 転職の経緯や転職後については SIerから自社サービス系に転職して半年ちょっと経過した (2019/05/15) をご覧ください。

目次

どんなアウトプットをしてきたか

現存するものを列挙するとこんな感じです。古いものだと、中学生の頃に公開したものもあります。

ソフトウェア

その他

アウトプットに救われた転職活動

「それ、職務経歴書に書いたほうが良いですよ」

初の転職活動は、右も左も分からず転職エージェントを2社活用しました。印象的だったのはIT特化のエージェントでした。

プログラミングを始めたきっかけから今後どうしたいかまでを洗いざらい話した後、こう言われました。

職務経歴書を1ページ増やしても良いので、業務外の活動やSIerで自主的にやっていたこと(Demo or Die(SI現場でいろいろ作った話))をもっと書いたほうが良いと思います

職務経歴書に業務外の活動、という矛盾するアドバイスでしたが、結論から言えばこれに救われました。

私に求められていたもの

転職先はいわゆるWeb系とか自社サービス企業と呼ばれるところですが、現職のメンバ(ソフトウェアエンジニア)には次の特徴があると感じています。

  • とにかく技術が好きで、吸収が早い
  • プロダクトやビジネスに興味を持っている
  • 当事者意識が強い

裏を返せば、メンバとして加わることになる私にも、これらが求められていました。

SIerのギャップとアウトプットのフィット

振り返ると、所属していたSIerでの「普段の仕事」は、求められていたものと大きなギャップがありました。

  • 属人性を排除し、標準化された技術を使う
  • 作業を細分化するか、細分化された作業を仕上げるかのどちらか
  • プロダクトやビジネスに影響を与えにくい

エージェントの方のアドバイスを受けて、上記の仕事にはほぼ触れず「業務外の活動やSIerで自主的にやっていたこと」を中心に据えてアピールをしたわけですが、それこそが「求められていたもの」に近いものでした。

それが評価されたことで、無事転職に至りました。

中学生の頃から始めていた業務外でのアウトプットが、転職に繋がりました。つまり、私の転職活動は中学生から始まっていたのです。

めでたしめでたし。

おまけ: アウトプットでの心がけ

私がアウトプットで心がけているのは2点です。

やりたいことをやる

「やらなきゃ(義務感」は辛くなるので、やりたいことをやってアウトプットにしています。

ポイントは「如何にやりたいことを増やすか」です。情報を仕入れたほうがやりたいことが増えるケースもあります。SIerの頃はWEB+DB PRESSSoftware Designを読んだり、Podcastを聞いたり(Rebuild.fmMisreading Chat、その他)していました。

最近は現職のメンバから刺激を受けることがめちゃくちゃ多いです。

稚拙でも良いので、正しさとリスペクトを意識する

ブログやQiita記事の場合は、次の2点を意識しています。

  1. 公式ドキュメントなどで正しさを確認する
  2. 参考にしたものや引用元を明記する

巨人の肩の上に立つ」という表現がありますが、それです。

かなり時間を使ってしまうことも多いですが、もっと良い方法を見つけたり、間違っていることに気づいたり、デタラメ記事の作成を避けたり出来るので、メリットは多いです。


さあ、アウトプットを増やしていきましょう。

Fargate Spotのお値段比べてみた

Fargate Spotが発表された.

aws.amazon.com

70%ディスカウントは魅力的であるものの,EC2と比べて高いのか安いのかがわからない.ということで表にした.

タイプ vCPU Mem EC2
オンデマンド
EC2
スポット
EC2
リザーブ
(1年前払い)
Fargate Fargate
Spot
m5.large 2 8 0.124 0.035 0.081 0.145 0.044
c5.large 2 4 0.107 0.033 0.072 0.123 0.037
r5.large 2 16 0.152 0.037 0.089 0.190 0.057

※ap-northeast-1(アジアパシフィック (東京)),2019年12月5日(木)23時時点,単位は USD/h

厳密には, C5 は M5, R5 とプロセッサのクロック周波数が異なるし,そもそもFargateのプロセッサシリーズは公表されていない点に注意.

見づらい表になってしまったが,Fargateはメモリ単価が少々高いという印象.

気象庁の天気予報文をパースする ☀☁☂

天気予報文

気象庁の天気予報には 予報文 があります。

北東の風 日中 東の風 くもり 昼過ぎ まで 時々 晴れ 所により 夜遅く 雨

気象庁 | 天気予報 : 東京都 (10/20 11:00発表 21日の天気予報文)

やや難解ですが、 気象庁|予報用語 と照らし合わせることで下記であることが分かります。

時間 天気
0〜15時 くもり 時々 晴れ
15〜21時 くもり
21〜24時 くもり 所により 雨

天気予報文と正規表現

ここからは根拠のない独自研究です。

文法

気象庁が発表する天気予報文には 文法 が存在し、どの予報文もいずれかの文法にマッチします。以下は例です。

文法
天候 晴れ
天候 頻度 天候 晴れ 時々 くもり
天候 時間帯 天候 くもり 昼過ぎ から 夕方
天候 時間帯 頻度 天候 くもり 昼前 まで 時々 晴れ
天候 頻度 天候 時間帯 天候 晴れ 時々 くもり 夜のはじめ頃 から
天候 所により 天候 くもり 所により
天候 所により 時間帯 天候 くもり 所により 明け方 まで
天候 所により 修飾 所により 雷を伴い 激しく 降る
天候 所により 時間帯 修飾 所により 夕方 から 雷 を伴う
天候 頻度 天候 時間帯 天候 所により 時間帯 修飾 晴れ 時々 くもり 夕方 から 所により 夜遅く 雷を伴い 激しく 降る

正規表現

文法を正規表現で表すと、次のようになります(雪 後 雪などのありえない表現もマッチしますが、今回は無視します)。

(晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)( (時々|一時|後) (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る))?( ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?)( (時々|一時))? (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る))?( (所により|[^ ]+ では)( ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?))? ((晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)|(雷 を伴う|激しく 降る|雷を伴い 激しく 降る|霧)))?

何を言っているんだという感じなので、擬似的に表現をすると次のようになります。

天候( 頻度 天候)?( 時間帯( 頻度)? 天候)?( (所により|地名)( 時間帯)? (天候|修飾))?
項目 正規表現
天候 (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)
頻度 (時々|一時|後) ※時間帯の後ろは (時々|一時)
時間帯 ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?)
地名 気象庁 | 気象警報・注意報や天気予報の発表区域 の「市町村等をまとめた地域」に準じる
修飾 (雷 を伴う|激しく 降る|雷を伴い 激しく 降る|霧)

※「雪 で ふぶく」等は修飾が入っていますが、簡単のために天候にまとめています。
※カバー出来ていない天気予報文が存在する可能性があります。

テスト

下記サイトで試してみるとマッチします。

f:id:no_clock:20191021004546p:plain

天気予報文とパーサ

正規表現のままパーサを書くのはつらいため、より簡単にして概ね次のルールでパーサを書きますました。

  • 入力は予報文、出力は0〜21時の3時間ごとの予報
  • 「時間帯」「所により(地名)」「何もない(終端)」が出現した場合に分割
  • 「所により」「頻度」が含まれる場合は予報を追記。それ以外は指定時間帯の予報として上書き

動作

$ ruby runner.rb

天気予報を入力: 晴れ 時々 くもり 夕方 から 雨 所により 夜遅く 雷を伴い 激しく 降る
解析結果:
{:wind=>nil,
 :weather=>
  {0=>"晴れ時々くもり",
   3=>"晴れ時々くもり",
   6=>"晴れ時々くもり",
   9=>"晴れ時々くもり",
   12=>"晴れ時々くもり",
   15=>"雨",
   18=>"雨",
   21=>"雨所により雷を伴い激しく降る"}}

天気予報を入力: 北東の風 日中 東の風 くもり 昼過ぎ まで 時々 晴れ 所により 夜遅く 雨
解析結果:
{:wind=>"北東の風 日中 東の風",
 :weather=>
  {0=>"くもり時々晴れ",
   3=>"くもり時々晴れ",
   6=>"くもり時々晴れ",
   9=>"くもり時々晴れ",
   12=>"くもり時々晴れ",
   15=>"くもり",
   18=>"くもり",
   21=>"くもり所により雨"}}

3時間ごとの天気予報がわかるようになりました。めでたしめでたし。

同じ構図で何枚も撮って,最も手ブレしていない 1 枚だけを残す

結論

  • ImageMagickcompare コマンドで, Perceptual Hash (pHash) により同一構図かチェックする.
  • ImageMagickconvertidentify コマンドで, Canny 法によりエッジ検出し同一構図の n 枚から最も鮮明な 1 枚を残す.
  • ソースはここに置いた: GitHub - typewriter/unblurred-photo-picker

背景

手ブレしたくないが, ISO 感度もあまり上げたくない(綺麗に撮りたい).その場合,ギリギリのシャッタースピードで何枚も撮る,ということをよくやります.しかし,撮影後の写真の選別が極めて面倒なうえ,かなり機械的な作業です.

  1. 同一構図で撮られた写真を探し出す
  2. そのうち最も鮮明と思われる写真 1 枚を残し,それ以外を重複とする.
  3. (重複写真は Flickr 上で非公開に設定する*1

機械的な作業… ということは機械で出来るのではないでしょうか.やってみました.

同一構図の判定

同一構図の判定は,画像の類似度が高いかどうかで行うこととします.ただし,画像処理はさっぱり分からないため, ImageMagick に全力で甘えます.

ImageMagick には compare コマンドがあり,お手軽に画像比較を行うことが可能です. 画像比較に使用できるメトリクスの一覧は ImageMagick - Command-line Options#-metric type にありますが,今回は PHASH (Perceptual Hash) を用います.

# ImageMagick 7 系.小さいほど類似度が高い.
$ magick compare -metric PHASH P9240031.JPG P9240035.JPG NULL:
13.7601

Perceptual Hash ではハミング距離を取って類似度とする例を見かけましたが, ImageMagick の数値はそうではないようです.まあ気にせずいくつかの画像で試してみます.

画像1 画像2 備考 出力値
f:id:no_clock:20191014202721j:plain f:id:no_clock:20191014202732j:plain 同じ構図 0.501766
f:id:no_clock:20191014202721j:plain f:id:no_clock:20191014202751j:plain 少し下に向けた 8.26181
f:id:no_clock:20191014202721j:plain f:id:no_clock:20191014202816j:plain 別構図 39.5355

同一構図では数値が小さく,異なる構図では数値が大きくなりました.使えそうです.

「最も鮮明な」写真の判定

やはり画像処理はさっぱり分からないため,こちらも ImageMagick に頼ります.

当初は「標準偏差 (Standard deviation) が大きければ値のバラつきが大きい→鮮明」と考えていましたが,手ブレした写真のほうが標準偏差が高いケースが複数枚でみられたため不採用です.

$ magick identify -format "{ \"Kurtosis\": %[kurtosis], \"StandardDeviation\": %[standard-deviation], \"Skewness\": %[skewness], \"Entropy\": %[entropy] }"  20171104_PB040552.JPG
{ "Kurtosis": 3.04856, "StandardDeviation": 11783.4, "Skewness": 1.91352, "Entropy": 0.760502 }
画像(切り抜き) 標準偏差
f:id:no_clock:20191014203238j:plain 11783.4
f:id:no_clock:20191014203249j:plain 11899.3

代わりに, Canny Edge Detection でエッジ検出した結果の標準偏差を用いることにします.

$ magick convert 20171104_PB040552.JPG -canny 0x1+10%+30% edge.png
$ magick identify -format "{ \"Kurtosis\": %[kurtosis], \"StandardDeviation\": %[standard-deviation], \"Skewness\": %[skewness], \"Entropy\": %[entropy] }"  edge.png
{ "Kurtosis": 13.5456, "StandardDeviation": 14823.4, "Skewness": 3.94279, "Entropy": 0.303514 }
画像(切り抜き) エッジ検出後(同左) 標準偏差
f:id:no_clock:20191014203238j:plain f:id:no_clock:20191014203811p:plain 14823.4
f:id:no_clock:20191014203249j:plain f:id:no_clock:20191014203853p:plain 11678.3

良さげな感じになりました.

スクリプト

Rubyで書きました。裏でImageMagickのコマンドガンガン実行します。

参考

*1:バックアップも兼ねているのでアップロードはしている

日用品のストック管理ツール Stokk を作った

日用品のストックを管理出来るツール「Stokk」を公開しました。

以下はポエムです。

技術選定

  • サーバをわざわざ建立するほどの機能はない
  • mBaaSと親和性が高そう
  • フロントエンドを書きたい

ということで、SPA + mBaaSで作ることにしました。

使ったもの

ツール: Adobe XD

初使用。無料の「スタータープラン」があり、アカウントさえあればシュッと使えます。

プロトタイプは作らず、ちょっと便利な図形描画ツール程度に使いました。事前にデザインや要件を整理でき、その後データやイベントの流れを練り練りするのにも大いに役立ちました。

f:id:no_clock:20190916220935p:plain
Adobe XDでのデザイン

言語: TypeScript

JavaScriptを操れる自信はなかったのでTypeScriptで書きました。エディタはVisual Studio Code

ただ、厳格に型付けをするのは諦めました。動く状態で、「thisはVueだからお前が定義したプロパティなぞ存在せんわ!」などと言われていました。「ぐ、Gradual Typingだから…」と意味不明な言い訳をしつつキャストしたりanyにしたりしました。

f:id:no_clock:20190916221003p:plain
怒られが発生している様子

フレームワーク: Vue.js + Vue Router, Bootstrap + BootstrapVue

ふわっと書きたかったのとReactは仕事で書く機会があるのとでVue.jsにしました。Vue CLIでTypeScriptも含めて一括で初期設定でき、とても楽でよいです。

デザインは安定のBootstrap。BootstrapVueも入れましたが、「BootstrapのListってBootstrapVueでどう書くんだっけ」と毎回マッピングしつつ書いていました。効率的に書けたかは分かりません。

その他ライブラリ: Lodash, Font Awesome

Lodashはdebounceに、Font Awesomeはアイコンに。

mBaaS: Cloud Firestore, Firebase Hosting, Firebase Authentication + FirebaseUI

作るサービスと親和性高そうなので全面的に依存しました。

FirebaseUIについては、 Firebase 6.6.1 と FirebaseUI 4.2.0 でドキュメント通りに書いたつもりで動作せず(firebase.auth()と書くとfirebaseがないと怒られる)。 メジャーバージョンを1つ落として FirebaseUI 3.6.1 を使用することで落ち着きました。何故なんだ。

そのほか

命名: Stokk

当初「にちまね!(日用品在庫マネージャ)」で けいおん!風にしようと思っていました。が、サブドメインを割り当てるタイミングで nichimane は無いな… と思って在庫(stock)を意図的に誤字らせました。

アニメーション

「動いているんだけど動いている感がない」という印象を受けたため、「小気味よく動いている」感を演出するべくアニメーションを設定しました。

  • アイテム追加、削除、並び替え時
  • 読み込み・保存時(スピナー表示)

f:id:no_clock:20190916221048g:plain

ソースコード

やっつけ感満載のソースコードGitHubにあります。