2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】競プロで考えるリーダブルコード

Posted at

リーダブルコードとは

言わずと知れた名著です。
Amazonでも500人近くから4.5のグローバル評価を得ています。

こちらの内容は既に多くの方が触れており、若輩者の私には加えて言うことはございません。

より良いコードを書くためのテクニックが網羅されています。

今回考えること

この本を最近読み直した時、競プロというスピード重視の環境でもコードの読みやすさは重要であると感じました。

主に下記の理由からです。

  • 複雑なデータ構造やアルゴリズムを実装する際はコードが肥大化しやすい
  • バグ処理を効率的にこなす必要がある
  • 未来の自分や他人に見せられるコードを普段から意識できる

ということで、競プロにおいてリーダブルコードを意識するとどういった実装になるのかについて考えていこうと思います。

メモがてら書いているため、随時追記・修正していく予定です。

命名規則

[2.2] tmpやretvalなど汎用的な名前を避ける

競プロでは汎用的な変数を使うことが多いです。
問題文の入力の時点で、N,W,Hといった固定された変数で表記されます。
forループではi,j,k,...を用いて、数えるときはcnt、答えはansに格納しがちです。

スピード勝負の中で、いちいち固有性のある変数名をつける余裕がない場合、どうしていけば良いか?

ここで重要なのは下記のことでしょう。

  • 特徴的な処理をしている場合は変数名に明記する
    二点間の距離の2乗: distdist_square

  • ループイテレータをわかりやすくする

for pi, person in enumerate(People):
    for ci, cost in enumerate(Costs):
        check[pi][ci]=True
        dist[pi][ci]=person*cost

[3.3] 限界値を含めるときはminとmaxを使う

最大値最小値を扱うことが多いので、意識しておくべきことです。
個人的にはDPで最大値を考えるときもDP=[0]*Nと書いてしまっていますが、DP_max=[0]*Nとした方が扱いやすいのかもしれません。

[3.6] bool値の名前

フラグ管理をするときはきちんとどちらの状態がTrueかを分かりやすくしましょうということですね。
BFSやDFSでの頂点ごとのフラグ管理もvisitedisUsedとして使うようにしたいです。

美しさ

[4.7] コードを「段落」に分割する

これは特に記述量の増える難問で意識していきたいことです。

どんな問題でも下記のような流れで解くことになります。
入力前処理アルゴリズム後処理出力
段落分けでもいいですし、関数やクラスで分割するのも良いですね。

[4.8] 個人的な好みと一貫性

一貫性があるということはとても重要です。

競プロでいえばforループの書き方はもちろん、入力出力の書き方も多岐にわたる方法があります。

きちんと自分のスタイルを確立した方がバグの可能性を減らせますね。

コメント

[5.2] 自分の考えを記録する

先ほどの段落分けのように、どのような順で何をしようとしているかは短くともコメントしていきたいですね。

過去の自分のコードを読み返していて、当時は解けていたはずなのに考えがコードに反映されておらず読みづらいという経験がありました。

[5.6] 入出力のコーナーケースに実例を使う

def euclidean(a,b):
# eueuclidean(3,1)→return 3, 1
    if b==1:
        return a,b
    
    # euclidean(8,3)→return 3, 2
    return b, a%b

競プロではコーナーケースを厳密に検証することが多いです。

そんな時にきちんとコメントで書いておくことで、見逃していないパターンがないかを網羅しやすいです。                   

制御フロー

[7.1] 条件式の引数の並び順

本文ではif(length>=10)といった調査対象を先に書くことを勧めています。

ただ、Pythonの場合は、if 5<=length<=10:といった記法が可能なため、時と場合によるかもしれません。

[7.2] if/elseブロックの並び順

if a!=b:
    print("No")
else:
    print("Yes")
if a==b:
    print("Yes")
else:
    print("No")

どちらの方が分かりやすい処理でしょうか。
時と場合によりますが、今回なら後者だと思います。
競プロでは例外処理が必要な場合は多いので、明確な優先順位をつけるべきです。

上記とは異なり、例外の時に処理を飛ばす場合はcontinueを使うと良さそうです。

for i in range(N):
    if i == K:
        continue

[7.5] 関数から早く返す

フラグ管理を扱っているときは、条件分岐する時点できちんとreturnを使って早めに値を返す方がバグを減らしやすいです;

for ni in range(N):
    if W[ni]>K:
        return False
    # 処理

下位問題を抽出する

[10.4] 汎用コードをたくさん作る

これは競プロを取り組むうえで一番大切なことかもしれません。

SegmentTreeやUnionFindなどその場で実装しやすいデータ構造、BFSやDFSなどコードの固有性の高いアルゴリズムもあります。
しかし、これらを初めから雛型として用意してあげることでコーディングの負担を大きく減らせます。
Pythonだとあまり意識しませんが、C++だとマクロや構造体の用意は必須です。

AtCoder Library(ACL)も用意されてますが、内容の理解のためにも自分で一度は実装してみた方が良いと思います。

[13.4] 身近なライブラリに親しむ

標準ライブラリの理解はレートに直結するレベルで重要です。

こちらのサイトはよく参考にさせてもらっています。
競プロで理解しておくべき標準ライブラリが網羅されています。

Pythonでは、リスト内包表記で書く方が速くなる、文字列連結は+ではなくjoinを使う方が速いといった知識も細かい部分ですが、知っておいて損はないです。

[14.4] エラーメッセージを読みやすくする

これは正直自分でもちゃんとできてない部分なので気を付けたいです。

どうしても焦りからprintのみでテストしてしまうことがあります。
Pythonには便利なassert文があるため、きちんと活用していくようにしたいです。

for ni in range(N): 
    x=W[ni]*C[P[ni]]
    assert 0<=x<=K
    # 処理

まとめ

途中いい例が思いつかず、適当なサンプルコードで誤魔化してしまっています……。ごめんなさい。

スピード重視の競プロだからこそバグの可能性を減らし、冷静に俯瞰できるような読みやすいコードを書くべきだと思いました。

まだまだ言語への理解も浅いので、より良いコードを目指して精進していきたいです!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?