Python3 – forループの最初と最後だけ処理を変えたい

やりたいこと

たとえば多数の計算結果を表示するとき、リストなら最初だけ"["がついて、各要素の後ろに", "が付加されて、最後の要素だけそのカンマがなくて"]"が表示される。このような処理をいろいろな形で実装したいというような場合。

単純に考えると次のように格好が悪い。

これを、<0, 1, 2, 3, 4>のように末尾だけ表示方法を変えたい、さらには、何個かごとに改行させて、2行目以降の行頭を1行目と変更したいときの処理を考える。

インデックスを利用する方法

1行で表示する場合

以下の例では、enumerateでコレクションのインデックスを取り出して、その値によって処理を分けている。行頭"<"の処理はforループの前でやってもよいが、次の複数行表示の準備としてif文で判定している。

複数行に分けて表示する場合

長いリストや多次元の配列などを複数行に分けて表示したい場合、行ごとの行末文字も変わってくる。そのような処理をforループで書いてみた。

ジェネレーターを使う方法

ジェネレーターを使った方法。このジェネレーターは、各要素のprefix、本体要素、suffixの3つを返す。

1文字目だけ処理を変えるため、コレクションをイテレーターとし、ループに入る前に最初の要素を取り出している。next()関数でイテレーターの内容を取り出すと、その後のforループでは以降の要素が順次取り出されることを利用している。

prefixは最初だけ"<"を使い、その後は""としてprefixなしとして扱っている。

1行で表示する場合

複数行に分けて表示する場合

複数行に分ける場合、1行当たりの要素数を設定し、カウンターで要素位置を検出している。カウンターの値から先頭要素と判定した場合にprefixを" "とし、その他の場合は""としている。

 

Python3 – yield文によるジェネレーターの実装

return文の確認

yield文はreturn文と同様に関数の中で使われ、戻り値を指定するが、その挙動は全く異なる。

まず準備として、通常のreturn文を持つ関数の動作を確認。呼び出されるたびに常に関数の先頭からreturn文まで実行される。

yield文にするとジェネレーターが生成される

このreturn文をyield文に変更してみると、関数の戻り値を返すのではなく、この関数がジェネレーターオブジェクトのコンストラクターとなっている。

ジェネレーターオブジェクトには、(Python3では)__next__()メソッドがあって、ジェネレーターで生成された値を順次取り出してくれる。そこで上のfunc1()でジェネレーターのインスタンスを生成し、直接値を取り出してみる。

このジェネレーターは値を1つしか生成しないので、2つ目を取り出そうとするとStopIteration例外を投げる。

yield文によるジェネレーターの挙動

以下の例は、3つの値を返すジェネレーターの例で、確認のためにyield文の前の処理をprint文で表示させるようにしている。なお、ここでは__next__()メソッドの代わりに組み込み関数next()を用いている。

関数で生成されるのはジェネレーターでStopIterationを投げるので、次のようにfor文で使える。

yiled文とreturn文を混ぜた場合

関数の中でyield文を書くと、return文があってもジェネレーターが生成される。

ただしreturn文があるとそこでStopIterationが投げられる。このとき、return文で指定した戻り値が得られるようだが、ジェネレーターとしては無視されるらしい。

このジェネレーターをfor文で使うと、以下のようにreturn文の手前まで実行される。

実装の例

たとえば引数を与えて、その数以下であるフィボナッチ数列を返すジェネレーターを考える。

 

二項分布の平均と分散

概要

二項分布B(n, p)の平均と分散は以下のようになる。

(1)    \begin{alignat*}{1} E(X) &= np \\ V(X) &= np(1-p) \end{alignat*}

これらを導くのに、有用なテクニックを使っているのでまとめておく。

直接定義式から導く方法

この方法は、平均、分散の定義式から直接導いていく過程で、意図的に二項展開の形に持ち込んでいく方法。

平均

平均の定義から、以下のように変形していく。

(2)    \begin{alignat*}{1} E(X) &= \sum_{k=0}^n k {}_n\mathrm{C}_k p^k (1-p)^{n-k} \\ &= \sum_{k=1}^n k \frac{n!}{(n-k)! k!} p^k (1-p)^{n-k} \\ &= \sum_{k=1}^n n \frac{(n-1)!}{\left( (n-1) - (k-1) \right)! (k-1)!} pp^{k-1} (1-p)^{(n-1)-(k-1)} \end{alignat*}

上式では、各項にkが乗じられていることから、以下の流れで変形している。

  • k=0のとき1番目の項はゼロとなるので、和の開始値をk=1とする
  • 分子にあるkを使って、{}_n C_kの分母においてk! \rightarrow (k-1)!とする
  • これに整合させるため、分母において(n-k)!((n-1) - (k-1))!と変形
  • さらに組み合わせの式に整合させるため分子をn (n-1)!と変形し、最終的に{}_{n-1} C_{k-1}を引き出している。
  • 後に二項定理を使うため、p, \; 1-pの指数も調整している

ここでk-1 = k'とおくと、カウンターの範囲はk=1 \sim nからk'=0 \sim n-1となることから、

(3)    \begin{alignat*}{1} E(X) &= np \sum_{k'=0}^{n-1} \frac{(n-1)!}{\left( (n-1) - k' \right)! k'!} p^{k'} (1-p)^{(n-1)-k'} \\ &= np (p + 1 - p)^{n-1} \\ &= np \end{alignat*}

上式では、変形した和の部分が二項展開の形になっていることを利用している。

分散

分散については、k^2が各項に乗じられるが、これをk(k-1) + kと変形して、階乗のランクを下げるところがミソ。

(4)    \begin{alignat*}{1} V(X) &= E(X^2) - [E(X)]^2 \\ &= \sum_{k=0}^n k^2 {}_n C_k p^k (1-p)^{n-k} - (np)^2 \\ &= \sum_{k=0}^n \left( k(k-1) + k \right) {}_n C_k p^k (1-p)^{n-k} - (np)^2 \\ &= \sum_{k=2}^n k(k-1) {}_n C_k p^k (1-p)^{n-k} + \sum_{k=1}^n k {}_n C_k p^k (1-p)^{n-k} - (np)^2 \\ \end{alignat*}

上式で、1項目はk(k-1)が乗じられているのでカウンターをk=2から、2項目は同じくk=1からとしている。

ここで1項目についてk'' = k - 2と置いて、平均の時と同じ考え方で以下のように変形。

(5)    \begin{alignat*}{1} &\sum_{k=2}^n k(k-1) {}_n C_k p^k (1-p)^{n-k} \\ &= \sum_{k=2}^n k(k-1) \frac{n!}{(n-k)! k!} p^k (1-p)^{n-k} \\ &= \sum_{k=2}^n n(n-1) \frac{(n-2)!}{((n-2) -(k-2))! (k-2)!} p^2 p^{k-2} (1-p)^{(n-2)-(k-2)} \\ &= n(n-1) p^2 \sum_{k'=0}^{n-2} \frac{(n-2)!}{((n-2) -k'')! k''!} p^{k''} (1-p)^{(n-2)-k''} \\ &= n(n-1) p^2 ( p + (1-p))^{n-2} \\ &= n(n-1) p^2 \end{alignat*}

2項目については、k' = k-1とおいて、

(6)    \begin{alignat*}{1} &\sum_{k=1}^n k {}_n C_k p^k (1-p)^{n-k} \\ &= \sum_{k=1}^n n \frac{(n-1)!}{\left( (n-1) - (k-1) \right) ! (k-1)!} p p^{k-1} (1-p)^{(n-1)-(k-1)} \\ &= np \sum_{k'=0}^{n-1} \frac{(n-1)!}{\left( (n-1) - k'\right) ! k'!} p p^{k'} (1-p)^{(n-1)-k'} \\ &= np \left( (p + (1-p) \right) ^{n-1} \\ &= np \end{alignat*}

以上を併せて、

(7)    \begin{alignat*}{1} V(X) &= n(n-1) p^2 + np - (np)^2 \\ &= np(1-p) \end{alignat*}

微分による方法

この方法は、kp^k, \; k^2 p^kの形に着目して、全事象の式を微分する方法。式展開が素直であり、平均の式を微分した結果がそのまま分散の式になってしまうところが美しい。

平均

二項分布の全確率の和は1となる。

(8)    \begin{equation*} \sum_{k=0}^n {}_n \mathrm{C}_k p^k (1-p)^{n-k} = (p + 1 - p)^n = 1 \end{equation*}

この式の両辺をpで微分する。

(9)    \begin{gather*} \sum_{k=0}^n {}_n \mathrm{C}_k \left( k p^{k-1} (1-p)^{n-k} - (n-k)p^k (1-p)^{n-k-1} \right) = 0 \\ \sum_{k=0}^n {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} \left( k(1-p) - (n-k)p \right) = 0 \\ \sum_{k=0}^n {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} (k - np) = 0 \\ \end{gather*}

両辺にp(1-p)をかける。

(10)    \begin{gather*} \sum_{k=0}^n {}_n \mathrm{C}_k p^k (1-p)^{n-k} (k - np) = 0 \\ \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k} = np \sum_{k=0}^n {}_n \mathrm{C}_k p^k (1-p)^{n-k}\\ \therefore E(X) = np \end{gather*}

分散

式(10)をもう一度pで微分する。

(11)    \begin{gather*} \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k} = np \\ \sum_{k=0}^n k {}_n \mathrm{C}_k \left( kp^{k-1} (1-p)^{n-k} -(n-k)p^k (1-p)^{n-k-1} \right) = n\\ \sum_{k=0}^n k {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} \left( k (1-p) -(n-k)p \right) = n\\ \sum_{k=0}^n k {}_n \mathrm{C}_k p^{k-1} (1-p)^{n-k-1} (k  - np) = n\\ \end{gather*}

両辺にp(1-p)をかける。

(12)    \begin{gather*} \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k} (k  - np) = np(1-p) \\ \sum_{k=0}^n k^2 {}_n \mathrm{C}_k p^k (1-p)^{n-k} - np \sum_{k=0}^n k {}_n \mathrm{C}_k p^k (1-p)^{n-k}= np(1-p) \\ E(X^2) - (np)^2 = np(1-p) \\ \therefore V(X) = np(1-p) \end{gather*}