卵が割れる階数を最小回数で求める問題の解答に対する証明について

きっかけ

もうだいぶ前の話だが、Twitterでこういう問題が流れてきた。(Googleの面接だったらしい。)

「あなたには卵が二つ与えられている。卵は二つともn階( 1 \leq n \leq 100)から落とすと割れてしまう。さて、nを求めるにはこの二つの卵を最低何回落とせば良いだろうか?」

この問題を見るのは初めてではないし、答えの数字まで覚えてしまっているが、僕には一つ疑問があった。ということで、適当に検索して上の方にあった「いかがでしたか?」と言わんばかりの解説サイトを見る。例えばこれだ。

xz4u.com

そして予想通り、この解説には問題点があることを改めて確認した。この解説では、「14回落とせばチェックができる」と書かれている。しかし、「14回が最小であること」の証明はどこにも書かれていない。これでは数学の証明としては明らかに0点だ。

そう、これが疑問である。 「この問題において、『14回』が最小であることはどうすれば証明できるか」というのを考えていこう。

経過

※この章は意欲ある訪問者の前にいきなり解答が出てくることを防ぐための「ネタバレ防止」のための章である。忙しい人は僕のつまらない失敗をすっ飛ばしてスクロールすると良いだろう。

さて、僕は最初物理学科らしく(?)、「卵を1回落とす試行あたりの情報量」を最大化するようなアルゴリズムを考えれば良い、と考えた。

卵が割れなかった時、0を書き、割れた時に1を順番に書いていくことを考える。卵を落とすアルゴリズムがfixされている時、この0と1という数字の羅列は1から100までの値に対応する。

つまり、「1が最大2回まで出現する長さnのビット列」で1から100を表現しているのだ。なので、1bitあたりの情報量をアルゴリズムの関数として表現し、変分法の要領で最大化すれば答えが出るのではないかと考えた。

...まあ想像に難くないと思うが、「1bitあたりの情報量をアルゴリズムの関数として表現」のあたりで僕は結局詰まってしまった。

実は、この考え方はかなり惜しいところをついており、次の次に述べる「別解」はこの考え方でいけるのだが、この時は結局、数学科の友人がちゃんとした証明を上げてくるまでずっと考えても分からなかった。

数学科の知り合いによる解法

僕が変分法で不毛な不等式評価ばかりを量産していた頃、突如として数学科の友人からメンションが届いた。あまりにも短かったので、最初の数秒間なんでこれが解答になってるかわからなかったほどだ。

しかし、このように一般化して考えると、卵2個のケースも簡単に解けてしまうのだ。やってみよう。

まず、卵m個とn回の試行で調べられる最大の階数を f(m, n)と表すことにしよう。(以下m,nは全て正の整数とする)

すると、問題は以下の問いに言い換えることができる。

 f(2, n) \geq 100となる最小のnはいくつか?」

ここで「最大の階数」と定義したのがポイントだ。例えば卵が90階で割れて f(2, n)が例えば85だった場合、卵2個ではどう頑張っても85階までに割れるケースしか調べられないので、このケースだと足りない。

さて、話を戻そう。mとnのどちらかが1のケースではすぐに以下のことがわかる。


f(m, 1) = 1 \\
f(1, n) = n

まず、上のケースはいいだろう。卵が何個使えたとしても、1回しか試行が許されないのならば1階分しか調べることはできない。

次の卵1個n回のケースでは、下の階から順番に卵を落としていくことによって、最大でn階までチェックすることができる。

さて、帰納法を回すためにはm,nについてのfの漸化式が必要になるが、実は以下の事実がわかる。


f(m+1, n+1) = f(m+1, n) + f(m,n) + 1

これはなぜかというと、1回目の試行で卵が割れたか割れないかによってその後調べられる階数が決まるからである。例えば、1回目の試行をL階で行ったとしよう。

卵が割れた場合、求める階数はL以下であることがわかる。なので、 L \lt f(m, n)である場合、正確に割れる階数を求めることができる。(卵が割れているのでm+1がmになっていることに注意)

割れなかった場合、求める階数はLより大きいことがわかる。残りの卵がm+1個、残りの試行回数がn階なので、 f(m+1, n)階分に追加で上の方向を調べることができる。

これを合わせると、 f(m+1, n) + f(m,n) + 1になる。

ここまでくればあとは簡単である。 f(2, n + 1) = f(2, n) + f(1, n) + 1、つまり、 f(2, n + 1) = f(2, n) + n + 1かつ f(2, 1) = 1なので、漸化式を解くことができて、すぐに f(2, n) = \frac{n(n+1)}{2}であることがわかる。

そのあとは f(2, n) \geq 100を解いてやれば無事、 n \geq 14であることがわかる。

この方法の妙は、求める者である「試行回数」を先にこちら側で決めてしまうことだ。そうすることによって「頭の中で」試すことができるようになる。また、この考え方の優れているところは、任意のmとnについて、「何が最小回数なのか」「どうやれば最小回数になるか」も構成的にわかることである。さすがは数学科、というべきであろう。

小学生でも証明できる別解

さて、先程の方法は数学的帰納法(もしくは漸化式)なので高校生でないとわからない。しかし、実は「証明する」だけであれば小学生でもなんと理解できるのだ。

そのためには、「経過」で述べた「1が最大2回まで出現する長さnのビット列」という考え方が重要になる。

卵を落としながらnを求める試行を観察し、「卵が割れた時だけは1、割れなかったら0」というビット列を作ると、同じアルゴリズムを使っている限り、nが違うのであれば違うビット列が対応するはずである。(同じ試行結果で違う階数が対応するのであれば、それは違うやり方であるため)

ということは、「1が最大2回まで出現する長さnのビット列」の「ビット列の種類」が100以下の場合、「n回では(被りが出るので)調べ尽くせない」ということになる。

「1が最大2回まで出現する長さnのビット列が何種類あるか」は組み合わせを計算できるならば小学生でも計算することができる。 n\mathrm{C}_2 + n + 1だ。これが100を超えるのは「n=14」の時であるため、「少なくとも14回は必要」ということがわかる。

...ところで、「14回でできる」ということは先程のブログに書いてあった。この2つを組み合わせることで、「最小の回数は14回である」ということを証明できるのである。ロジックとしては、「不等式で下から評価して、等号が成立していればそれが最小値」というところである。

この方法であれば、(具体的なやり方を思いつかなければいけないが)小学生でも理解ができる。

これを思いついたのは翌日だった気がする。中々気持ちよかった。

終わりに

ということで証明であった。...ところで、「2階から卵を落としたら絶対割れるじゃん!」というのもありだろう。Googleの面接には合格できない気がするが。まあいいか。

2020年を雑に振り返る

アカウントを作って3年以上放置して、初めての投稿が振り返りというのもなんだか変な話だけど、個人的にはまだ今年の仕事が残っているという方が面白い。 この記事を投稿したら、画面のモックを書いて仲間に新年の仕事を振らなければならない。現実逃避の時間でこれを書いている。

うつ病との付き合い

昨年の12月にうつ病の診断が降りて今月で1年になる。1年間ずっと抗うつ薬を飲み続けてきたわけだ。症状自体は昨年の5月にはもう出ていたから、既に1年半うつ病と付き合ってきたことになる。

今年の1月から4月ごろまではどんどんと病状が改善していったが、4月の後半から授業と非常事態宣言による自粛のダブルパンチで病状が巻き戻り、6月の末には30分に1回仮眠を取ってようやく集中力が維持できるかできないかというレベルだった。7月からはまた快方に向かっており、現在は薬を飲みさえすれば、なんなら飲み忘れてもしばらくは通常の思考ができるほどに回復している。(今も書くことによって薬を飲んでいないことを思い出して飲んだくらいだ。)

うつと言うと「depression」とあるように、「気分の落ち込み」が主なものだと思っていたが、実際なってみると、思考能力や認知能力全般がダメージを受ける。また、気力などもほとんど続かなくなる。これはプログラマーに取ってはかなり致命的で、一時期僕の生産性(時間当たりコードの行数)は全盛期の30分の1程度まで落ちている。この状態でも普通に置いてくれた会社には感謝である。

個人的に一番しんどかったのは家族、特に親の無理解だった。しんどくて寝てるところを寝るなと言われてもどうしようもない。何度言ってもわからないし、個人的にうつ病と親の無理解のどちらが辛かったのかわからないほどである。まあだいぶよくなった今ではこのような無理解も減ったが、さっさと独立したいという思いは強まった。

学生と正社員の兼ねあい

うつの診断が降りた瞬間に目標を卒業から「必修コンプリート」にシフトしたのは今でも英断だったと思っている。というのも、僕の学科の必修は4年生のくせに週9コマも消費するためだ。そして、今年は残った単位をちびちびと回収するという年になった。

大学の方が比較的手薄になったおかげか、4月から正社員になってもだいぶ両立はできた。同期たちも修士(M1)かつ正社員という状態だったが、周りの様子をみる限り、修士か学部かどうかよりも、授業のコマ数の方が兼業には効いてくるらしい。特に、研究もプログラミングもどちらもある意味知的生産業のため、労働時間を自由に配置できる権限を与えられている僕らはかなりやりやすかったと言える。

正社員としてやったことなどは後で述べるとして、今年学生として一番大きかったのは、「大学院でやろうとしていることの明確な言語化」だったのだろうと思う。

普通、物理学の素養がある人間はプログラミングを道具以上のものとしては使わないことが多い。逆に、情報科学の分野などで型理論など、体系だって学問的にプログラミングを捉えることのできる人物は通常、量子力学の素養が無い。たまたま両者の分野の入り口を両方とも踏んだことのある自分ができることがあるのなら嬉しい。

やや気が早いが、プログラミングでの「コントリビューション」の概念に触れて思ったのは、「学問の世界でもそんなに超難問とか大きな貢献をするのではなく、『誰にでもできるけど、誰もやらなかった』小さな貢献を積み重ねていけばいいのでは?」という気持ちだった。このマインドで院をやっていきたい。

テックリードとして

今年の5月に弊社のCTOが辞めて、後任を僕の同期が引き継ぐことになった。一方で、CTOの責務を22歳でやるのはいくらベンチャーでも流石に無理があるということで、CTOと名乗るのは1人だが、責務は3人で分割してやろうという話になった。それで、比較的コミュニケーションやマネジメントが苦手で、純粋に技術オタクの色が強い僕は技術部分、つまりテックリードということになった。

と言っても、基本的な役割は新しい技術を紹介しては「うーん。まだ採用できないねえ。」と言われて「そうねえ」と返したり、社内ライブラリを作ってコードベースのエントロピーを下げるくらいだったが、10月に社内ツールを作ろうという話になってだいぶ流れが変わった。

以前も社内ツールは何個か作ってきたが、どうしても片手間でやるためにメンテナンスがなされずそのまま打ち捨てられるという背景があった。しかし、エンジニアが3人から10人近くまで増え、仕事のプロセスも安定してくると、社内ツールのためのリソースも安定して割くことができるようになる。そのため、人をちゃんとアサインしてやることになった。(冒頭で述べた仕事の残りもこれである。)

社内ツール開発の、通常のtoCの開発と最も違うところはその規模感である。つまり、使う人が圧倒的に少ないかつ使用する人と密にやりとりができるため、プロダクトの完成度以外にもマニュアルを整備して運用でどうにかしたり、そもそもスプレッドシートで済ませたり、と取れる手札が多い。

さらに、アプリケーションの規模も小規模なため、技術的負債のリスクの影響が小さく、好きに試せると言うのが大きい。実際、今まで社内で開発スピードや学習コストなどの理由で取り入れることのできなかったAWS CDK, ECS, grpc, nodeバックエンド, Reactフロントエンド(with Rocon, Recoil)などさまざまな要素をふんだんに取り入れている。また、リソースが割けると言っても全てのメンバーは他にメインの仕事を抱えているため、マネジメントもやや特殊になる。その話はまたいつかしたい。

技術コミュニティーとの関わり・アウトプット

昨年度は会社で勉強会を主催して、5回まで続いたが、人が集まらなくなったことと新型コロナウイルスの影響で開催できなくなってしまった。会社の勉強会だけでなく、ほとんどの勉強会は新型コロナウイルスの影響で死滅した一年であった。とはいえ、何もなかったわけではない。

Twitterの技術者のFFの割合は確実に増えたし、uenojsに参加させていただいたり、有志のコミュニティーに入れさせていただいたりと小さな進展はあったと言えるだろう。

勉強会が消滅するとともに登壇することも絶えて無くなった。昨年は6回登壇したが、今年は1回だけだ。とはいえ、9月から社内で持ち回りの発表会が開かれるようになり、それがいい刺激になっている。

話す方のアウトプットはどうにかなっているが、記事としてのアウトプットは筆不精により全然できなかった。宣言してもダメだったところをみると、どうやら自分は記事としてのアウトプットは相当に苦手らしい。一方で、社内向けの技術記事などはちょっとずつ書いていたので、フィードバックを得ながら小さく発信していくのがいいのかもしれない。

自分の技術スタックの差分・方向性

2020年でエンジニアとして一番大きかったのは、基本設計の段階から関わってきたプロダクトがリリースされたことだろう。

lions.orical.jp

TypeScript Compiler APIによる型情報の生成、Scoped SlotsによるContainer Componentの分離、Atomic Designの徹底、NuxtにおけるTypeScript / Vue Composition APIの使用など、2019年9月当時では非常に先進的な取り組みが詰まったコードベースとなった。どれひとつをとっても中々質の良い記事になるであろう事柄がいくつも詰め込まれている。特にチュートリアル部分はかなり優秀な設計になっていると思うので、これはまたちゃんといつか記事にしたい。

2017年に本格的にフロントエンドの仕事をするようになって以来、僕は基本的に「フロントエンドの設計」というかなりニッチな分野をメインウェポンとしてやってきたが、これによりひとまずの区切りをつけられたのでは無いかと思っている。というか正直飽きてきたというのがある。

そのため、今年の後半はRustを勉強してみたり、React Nativeでモバイルアプリをちょっとやってみたり、AWS CDKでInfrastructure as a Codeするなど、色々フロントエンド以外に軸足を移せるように模索していた。来年もこのように色々挑戦ができたらなと思う。

終わりに

留年と新型コロナウイルスでほぼほぼ大学に行かず、今年はなんだか「人生の冬休み」だったのではないかと思っている。冬休みらしくのんびりした一年だった。皆さんの一年はどうだっただろうか。 なにはともあれ、よいお年を。