今年の1月末頃から「ソフトウェアエンジニアリング協会」の方々に[[Arai 60]]を題材にコーディングを見てもらっていました。[[Weekly News(2024-04-01)#Arai60を解き終わりました|4月の頭]]に一旦ひと通り終わったので、この練習方法についてまとめておきます。
## 練習の目的
まず始めに練習の目的ですが、LeetCodeの問題を10分以内に解き、入出力が合うことを最低限のラインとし、その上でコードを見たときに講師陣と同じような反応や関連する知識を連想できるようになることです。
具体的には、以下のような能力を身につけることを目的としています。
- LeetCodeのMedium程度の問題であれば何も見ずに書けるし修正できる能力
- 関連するライブラリの中身を問われればそれも書けて欲しい
- 言語のバージョン間の差異、言語仕様か環境依存かなども含めて気を配れる能力
- 他人に読みやすいように書く能力
- 大規模なコードを読解し理解する能力
- 頭の中でコードをシミュレートし、デバッグできる能力
もう少し抽象度を上げると講師陣から見たときに「仕事が一緒にできる人」になるというのが目的でしょうか。ここらへんは、合わせて[[スタートラインに立つためにはどうするのか]]にも目を通していただけると良いと思います。
## 具体的な練習方法
講師の方からは以下のルールを提示され、基本的にはこのルールに沿って練習しました。
> 新井さんの問題順番にやっていきます。一日一問で最初はいいでしょう。答えを見ずに考えて、5分考えて分からなかったら答えを見てください。答えを見て理解したと思ったら、答えを隠して書いてください。筆が進まず5分迷ったら答えを見てください。そして、見ちゃったら一回全部消してやり直しです。答えを送信して、正解になったら、まずは一段階目です。次にコードを読みやすくするようにできるだけ整えましょう。これで動くコードになったら二段階目です。そしたららまた全部消しましょう。今度は、時間を測りながら、もう一回、書きましょう。書いてアクセプトされたら文字消してもう一回書きましょう。これを10分以内に一回もエラーを出さずに書ける状態になるまで続けてください。3回続けてそれができたらその問題はひとまず丸です。
実際には提示されたルールに加えて、レビューのフィードバックへの対応と独自で追加したルールからなる5ステップくらいで練習していました。また、途中からは他の人のレビューも行うようにしていました。
**1ステップ目**
基本的には提示されたルール通りにやっていました。個人的には[[Weekly News(2023-10-16)#NeetCode 150の問題を解き終わりました|昨年の10月]]に[[NeetCode 150]]を一周していることもあり、解法の方針すら立たないという問題はほとんどなかったです。数問ありましたが、20分くらい考えていました。あとは良い解法が思いつかなくても全く手も足も出ないことはないので、とりあえず計算量と課題の制約的にTLEすると分かっていてもとりあえずコードを書き切り、そのうえで解答を見るようにしていました。
また、コードを書き始める前に解法が複数ある場合は、それぞれについて軽く考えることも意識していました。また余裕があるときは1ステップ目の段階で複数解法について解いていました。
**2ステップ目**
こちらも基本的には提示されたルール通りにやっていました。このタイミングでLeetCodeの解法や同時期に他の人もこの練習法で取り組んでいたので、書き直すタイミングでは他の人の回答やそれに対する講師陣のレビュー内容も参考にしました。複数解法ある場合で1ステップ目で書いていないものがあれば、このタイミングでそれらについても書きました。
また、途中からは使用する関数やライブラリについてのドキュメントもこのタイミングで読むようになりました。
**3ステップ目**
このステップはかなりルールに忠実にやっていました。
終盤は1ステップ目、2ステップ目の段階で3ステップ目の段階と同じようなコードを時間内に解けきれるようになっていることが多かったので、少し手を抜いてこのフェーズを1回に減らしたりなどはしていました。
**4ステップ目**
3ステップ目まで終わったタイミングで講師陣にレビューを依頼し、レビューを元にコードを書き直しました。また、LeetCodeの問題とは直接関係ないですが、関連するライブラリの再実装などを行いました。たとえば、`@lru_cache`や`heapq`ライブラリなどを使用した解法があったので[[Least Recently Used|LRU]] Cacheの実装や[[Binary Heap]]の実装なども行いました。
また、言語はPythonで行っていたので途中からは[[CPython]]のソースコードを読んだりしていました。
**5ステップ目**
5ステップ目は完全に独自で追加したルールで、以下のルールでやっていました。
3ステップ目と同じルールで一度解けた問題について、3日後に挑戦します。OKなら7日後に挑戦します。OKなら30日後に挑戦して、3連続でOKならクリアとします。一度でも失敗したら最初からやり直します。
エビングハウスの忘却曲線を意識しています。期間は調整する余地はあるかもしれないです。
## 実際やってみての感想
「LeetCodeの問題を10分以内に解き、入出力が合うこと」という観点では、[[Arai 60]]くらいの問題であれば5分程度でノーミスで書けるようになりました。何問かは、コードの分量が多くなるものがありましたが、それでも概ね10分程度には収まるようになりました。この練習前を行う前の状態はちゃんと計測していないのですが、倍くらいは掛かっていたような感触があります。
そんなの無理だろと思われる方もいるかもしれないですが、たかだか100行もいかない量のコードなので写経するだけならタイピング速度にもよりますが10分以内に書ききれないはずがないです。とはいえ暗記しているわけでも暗記する努力もした訳ではないですが、3ステップ目や5ステップ目で何回書いてもだいたい同じコードになる感覚をこの練習を初めて1ヶ月くらいで持つようになりました。
講師の方がよく「将棋や囲碁のアマ初段くらいで、1時間位の対局がまるっと再現できる」とよく仰っていてこれと同じ話だと思います。コーディングをする上で変数の命名にしろループを使うにしろ複数の選択肢があると思うのですが、その中で自分はこれを選ぶなというのがあってその選択肢を取っていくと結果として毎回似たようなコードが出力されます。練習の3ステップ目がすごい役立っていると思っていて、何回も間違える箇所というのはそもそも素直に処理が書けていなかったりして3回連続で通すように書き直すことでコードが整理されますし選択肢の好みも固まっていったように思えます。実際にこの練習方法を始めてしばらくの間は2ステップ目を終わった時点と3ステップ目が完了した時点では結構異なるコードになることが多かったです。
また、他の人のコードのレビューも途中からしていて、よくレビューをする人であればある程度その人の好みもなんとなく分かって、やろうと思えばその人が書きそうなコードも再現できるような感覚があります。
この練習方法の取り組みは[Github](https://github.com/hayashi-ay/leetcode)にも上げているので良ければ参考にしてもらえればと思います。