2019年6月4日火曜日

凡ミス集

凡ミス集

競プロにおける自分の(ヒューマン)エラーを逐一記録していく。

  • 失敗例を集積することで、新しくわかることがあるかもしれない
  • コンテスト中にバグが取れないときに役立つかもしれない

というねらいがある。

問題を読み間違える

コンテスト3回につき1回くらいのペースで問題を読み間違えて大幅に時間を消費する。「問題をよく読む」が意味のある指針でないのは明らかだが、ではどうすればよいかというと、よくわからない。大したコストをかけずに誤読率を減らす方法って何があるだろう。

  • Kenken Race: すぬけ君とふぬけ君が左右のどちらにも進めると読み間違える。
  • Cell Distance: N×M2×105N \times M \le 2 \times 10^5N,M2×105N, M \le 2 \times 10^5と読み間違える。
  • RGB Boxes: r+g+b=N,rR,gG,bBr+g+b = N, r \le R, g \le G, b \le Bを満たすr,g,br, g, bを数える問題と読み間違える。
  • Snuke the Wizard: マスではなくゴーレムに文字が割り振られていると読み間違える。
  • Strings of Impurity: 部分列を連続部分列と読み間違える。

エラーの出ない構文の誤り

引数の順番を間違える

たいていは型エラーになるが、ならない場合が厄介である。

(logbitp index integer)は順番がarefと逆である上に、入れ替えても型エラーにならないので注意を要する。

16進法で出力してしまう

10進法で出力するつもりでなぜか(format t "~X ~X~%" a b)と書いた。ディレクティブを使い分けずに常に~Aにすれば回避できそうだが、それはそれで(小数の出力などで)別のミスを生む可能性がある。小数に関しては、必ずdouble-floatを使うと決めてテンプレートに*read-default-float-format*を設定する手もあるけど、そうしたほうがいいのかな。ただ、sqrtとかもあるし、絶対にsingle-floatが登場しないという前提を置くのはそれはそれで危険に思える。

コピペミス

同じような処理が2つある時にコピペして変更箇所を間違えるというミスはよくやる。2つくらいだと無理に共通化するのもそれはそれで間違うリスクを増やすので、根本的な解決策は思いつかない。

  • DPする関数が2つあるときに、recurという名で1つ書いた後、もう1つを適当にsubrecurと名づけて処理のほとんどをコピペした。subrecursubrecurで再帰しないといけないのに、recurを呼ぶ部分が直っていなくて、エラーも出ないので気づくのに時間がかかった。

ただ、DPに絡んだバグに関しては、メモ化マクロにトレース機能を付けてからだいぶ解決が早くなった気がする。

投稿に関するミス

デバッグ出力を消し忘れてREする

ローカルではcl-debug-printを使っているのだが、うっかり#>を残したまま投稿するとリーダーエラーになる。

これは明らかに解決可能なのだが、放置していた。とりあえず

#-swank (set-dispatch-macro-character #\# #\> (lambda (s c p) (read s nil (values) t)))

という一行をテンプレートに加えて、本番環境では#>が空のリーダーマクロになるようにした。

スタックサイズを増やし忘れる

再帰が深い場合はSBCLのデフォルトのスタックサイズ(2MB)では足りなくなるので増やす必要があるのだが、たまに増やし忘れてcontrol-stack-exhaustedする。それで常にREが出るのなら大きな被害はないが、なぜかTLEに化ける場合があって、判断が遅れたりする。

スタックサイズを増やす処理を最初からテンプレートに入れてしまえばいいのだが、やっていることがバッドノウハウすぎるのでなんとなく気がすすまないだけである。うーん……