NAKED WETWARE

「わがままになるのが怖い奴に宇宙は拓けねェさ」

【Python/スニペット】コードリーディングで知ったことをまとめる

個人的なまとめ。

他人のコードを読んでいて勉強になったと感じる部分を記述していきます。

math.ceilを使わない天井関数のような処理

math.ceil(A/B) == (A+B-1)//B == int((A+B-1)/B)

理由

いま、非負整数 q,rに対して A = qB + rとする。

\displaystyle{
\begin{aligned}
\left \lfloor \frac{(A+B-1)}{B} \right \rfloor &= \left \lfloor \frac{(qB+r+B-1)}{B} \right \rfloor \\
 &= \left \lfloor \frac{(q+1)B+r-1)}{B} \right \rfloor \\
 &= \left \lfloor \frac{(q+1)B}{B} + \frac{r-1}{B} \right \rfloor \\
 &=  q + \left \lfloor 1 + \frac{r-1}{B} \right \rfloor
\end{aligned}
}

(1). BがAを割り切るとき、 r = 0 なので、

\displaystyle{
\begin{aligned}
\left \lfloor 1 + \frac{r-1}{B} \right \rfloor &= \left \lfloor 1 - \frac{1}{B} \right \rfloor 
\end{aligned}
}

これより 1 - \frac{1}{B} \lt 1 なので、 \left \lfloor 1 - \frac{1}{B} \right \rfloor = 0

よって、

\displaystyle{
\begin{aligned}
q + \left \lfloor 1 + \frac{r-1}{B} \right \rfloor  &= q + \left \lfloor 1 - \frac{1}{B} \right \rfloor  \\
 &= q + 0 \\
 &= q
\end{aligned}
}

(2). また、BがAを 割り切らない とき、非負整数 r r \lt B なので r-1 \lt Bとなる。

これより、 \frac{r-1}{B} \lt 1なので、

\displaystyle{
\begin{aligned}
q + \left \lfloor 1 + \frac{r-1}{B} \right \rfloor  &= q + 1 + 0 \\
 &= q + 1
\end{aligned}
}

配列を裏返す

# いつも使うやつ (破壊的配列操作)
N = len(arr)
for i in range(N>>1):
    arr[i], arr[-(i+1)] = arr[-(i+1)], arr[i]

# New!
arr2 = arr[::-1]

これは、配列に指定するStepを -1にすることで、「配列をひっくり返す」ということをしてる。

ループ逆回転

Pythonでループを回すときは、いつも昇順にしか回してなかった。

だから降順で回したいときは、インデックスをごにょごにょして頑張ってた。

もっとシンプル(インデックス操作を間違いにくい)方法があるんだと、他人のソースを読んで気が付いた。

# いつもの
N = len(arr)
arr2 = [arr[N-i-1] for i in range(N)]

# New! (1)
N = len(arr)
arr2 = [arr[i] for i in range(N-1,-1,-1)]


# New! (2)
N = len(arr)
arr2 = [arr[i] for i in range(N)[::-1]]

これも、先述の「配列をひっくり返す」と似ている。

パリティチェック

偶奇チェックのこと。

これを書いた人は、きっと物理レイヤー(Cとかアセンブリの世界)に近いところでお仕事してるんじゃないかって思います。

# いつもの
is_odd = True if num % 2 == 1 else Flase
# ちなみに、Pythonでは True == 1 (False == 0)なので以下でも行ける
is_odd = num % 2


# New!
is_odd = (num & 1) == 1
# または
is_odd = num & 1

理由

十進数を二進数表記して考えます。

二進数表記の時、最下位ビット( = 2 ^ 0 )は 0 1を表します。

最下位ビット以外は、すべて 2 ^ nと偶数になってしまうため、偶奇を決定しているのは実質的に最下位ビットです。

最下位ビットとandをとることが、すなわちパリティチェックになります。

言語によっては、コンパイラが最適化の段階で、モジュロ演算をビット演算に変換することもあるようです(wiki)

パフォーマンス

モジュロ演算とビット演算、どちらが速いのか気になったので計測してみた。

ソース

import sys
import os
import re
from math import ceil, floor
from datetime import datetime



if __name__ == '__main__':
    even_cnt = 0
    s = datetime.now()
    for i in range(100000):
        for j in range(1000):
            if j % 2:
                continue
            even_cnt += 1
    e = datetime.now()
    print("Modulo : ", e-s)

    even_cnt = 0
    s = datetime.now()
    for i in range(100000):
        for j in range(1000):
            if j&1:
                continue
            even_cnt += 1
    e = datetime.now()
    print("BitAnd : ", e-s)

結果

Modulo :  0:00:06.186608
BitAnd :  0:00:07.053160

モジュロ演算のほうが速かった。マジか。