Pythonのfor…in文の効果的な運用

Pythonのfor...in文の効果的な運用

Pythonのfor...in文について思うところがあって、効率的な運用方法
について考えてみました。

まず、for...in文は配列の要素を順番に取り出すことが出来ます。
例えば、配列の各成分を二倍して新たな配列を作るプログラムは以下のように書けます。

a = [12, 33, -10, 44, -49, 100, 21, 54]
b = []
for x in a:
    b.append(x * 2)

また、リスト内包表記で以下のように書くことも出来ます。

b = [x * 2 for x in a]

このような場合リスト内包表記は有用でコードを簡潔に書くことが出来ます。

次に配列の隣同士の差の配列を作るプログラムは以下のように書けます。

# for-loop
c = []
for i, x in enumerate(a):
    if i + 1 >= len(a):
        break
    c.append(a[i + 1] - x)

# リスト内包表記

c = [a[i + 1] - x for i, x in enumerate(a) if i + 1 < len(a)]

しかし、for...in文のような仕組みは配列の添字を意識することなく
コーディングできるから有用なのであって、このようにするのは
あまりスマートではありません。さらに、どことなく冗長な雰囲気が醸しだされています。
例えば、隣同士ではなく一つ飛ばした同士の差を計算することにコードを
変更することを考えてみましょう。

# for-loop
c = []
for i, x in enumerate(a):
    if i + 2 >= len(a):
        break
  c.append(a[i + 2] - x)

このように、for...in以下のコードに手を加えなければならなくなります。
このコード例は非常に単純ですが、配列にアクセスするコードが増えると
このような変更だけでも非常に煩雑でわかりにくいバグの温床になります。

そこで視点を変えて、配列の添字を使うのではなく最初からずらした配列を与える
ことを考えてみます。

# for-loop
c = []
for l, r in zip(a[1:], a):
    c.append(l - r)

# リスト内包表記

c = [l - r for l, r zip(a[1:], a)]

この書き方の利点は2つあります。

  • zipがよしなに処理してくれるのでIndexErrorを意識する必要がない
  • 与える配列を変更すればそれ以下は変更する必要がない

隣同士ではなく一つ飛ばした同士の差を計算するコードは

c = []
for l, r in zip(a[2:], a):
  c.append(l - r)

このように書け、for...in以下を変更する必要がありません。
またこれを応用して、2 * n番目の要素とn番目の要素とn +
1番目の要素との和を取る プログラムも簡単に書くことが出来ます。

# for-loop
c = []
for l, m, r in zip(a[::2], a, a[1:]):
    c.append(l + m + r)

# リスト内包表記
c = [l + m + r for l, m, r in zip(a[::2], a, a[1:])] 

結論

配列の隣同士の差の配列を作るような処理は
enumerateを使うのではなくzipを使ったほうが効率的にコーディングできます。