Remrinのpython攻略日記

python3に入門しました。python3についてあれこれとサンプルコードとか。

ループ処理

ループ処理について。

for イテレータ(シーケンスなど)を添える
while イテレータの代わりに条件式。while True, while 1という形をよく見る
break ブロックから抜ける
continue それ以降のループブロックを実行せず、最初に戻る
else forブロックやwhileブロックで使う時は、ループの実行後の処理。ただし、breakすると無視

 

x = 0
while x < 10:
    x +=1
    print(x, end=" ")
else:
    print("finished")
# 1 2 3 4 5 6 7 8 9 10 finished

x = 0
while x < 10:
    x +=1
    print(x, end=" ")
    if x == 5:
        break
else:
    print("finished")
print("ended")
# 1 2 3 4 5 ended

x = 0
while x < 10:
    x +=1
    if x % 2 == 0:
        continue
    print(x, end=" ")
else:
    print("finished")
# 1 3 5 7 9 finished

 
・反復中のリスト自身を改変したいときは、スライスをループ要素とする。

nlist = list(range(6))

for i in nlist[:]:
    if i % 2 == 0:
        nlist.append(i)
        
print(nlist)
# [0, 1, 2, 3, 4, 5, 0, 2, 4]

 
ネストしたループから一気に抜けるには
・フラグ管理
・エラーをraiseして、try/catch
・関数化など。

def break_test(escape):
    counter = 0
    for i in range(10):
        for j in range(10):
            counter += 1
            print(counter, end=" ")
            if i * j == escape:
                print("i={}, j={}".format(i,j))
                return

break_test(8)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 i=1, j=8            

 

enumerate()

 ループカウンタとして使える。
 回数や条件を判断してループを抜けられる。
 

zip()

 2つのシーケンスを使ったループが作れる。

for i, j in zip(["A", "B", "C"], [1, 2, 3]):
    print(i, j)
# A 1
# B 2
# C 3

 

3つのif

ifの使い方について。
 
「1000未満の3または5の倍数の和を求める」場合
○条件分岐

limit = 1000
result = 0
for i in range(limit):
    if i%3 == 0 or i%5 ==0:
        result += i
print(result)

 
○内包表記

limit = 1000
result = sum([x for x in range(limit) if x%3 == 0 or x%5 ==0 ])
print(result)

 
○3項演算子

limit = 1000
result = 0
for i in range(limit):
    result += i if i%5 == 0 or i%3 ==0 else 0
print(result)

NumPyの使い方(19) 数え上げ

NumPyの数え上げ(要素のカウント)について。
 
0,1のみのデータから1や0の個数を数える方法

import numpy as np
a1 = np.array([0, 1, 1, 0, 1, 0, 0, 0])

# 1の個数をカウントする例
print(np.count_nonzero(a1))      # 3
print(a1.sum())                  # 3
print(len(np.where(a1 != 0)[0])) # 3

# 0の個数をカウントする例
print(np.count_nonzero(1 - a1))  # 5
print(len(a1) - a1.sum())        # 5

 
0と1を同時にカウントする例

# 例1    
print(np.unique(a1, return_counts=True))
# (array([0, 1]), array([5, 3]))

unique, count = np.unique(a1, return_counts=True)
print(dict(zip(unique, count)))
# {0: 5, 1: 3}

# 例2
from collections import Counter
c = Counter(a1)
print(c)
# Counter({0: 5, 1: 3})

# 例3
c = np.bincount(a1)
print(c)   # [5 3]

CounterについてはCollectionsライブラリを参照
 
0, 1以外のデータからのカウント

a2 = np.array([0, 1, 2, 4 , 2, 4, 4])

# np.uniqueは存在しない値をスルー
u, c = np.unique(a2, return_counts=True)
print(dict(zip(u, c)))
# {0: 1, 1: 1, 2: 2, 4: 3}

# np.bincountは存在しない値を0個とする
print(np.bincount(a2))
# [1 1 2 0 3]

 

素数

素数について。
 
素数生成のジェネレータ

def gen2(start=2, stop=1000000):
    pr = max(1, start - 1)
    while True:
        while pr < stop:
            pr += 1
            if all(pr%x != 0 for x in range(2, int(pr**0.5) + 1)):
                break
        yield pr

g = gen2()
for i in range(10):
    print(next(g), end=" ")
print()
# 2 3 5 7 11 13 17 19 23 29 

 
素因数分解

# 素因数分解
def factorial(n):
    if n in [0, 1]:
        return [n]
    result = []
    stop = int(n**0.5) + 1
    g = gen1()
    pr = next(g)
    while pr < stop:
        if n % pr == 0:
            result.append(pr)
            n //= pr
            continue
        pr = next(g)
    if n > 1:
        result.append(n)
    return result

g = gen2()
for i in range(10):
    print(next(g), end=" ")
print()

for i in range(0, 10):
    print(i, factorial(i))
# 0 [0]
# 1 [1]
# 2 [2]
# 3 [3]
# 4 [2, 2]
# 5 [5]
# 6 [2, 3]
# 7 [7]
# 8 [2, 2, 2]
# 9 [3, 3]
print(10**6, factorial(10**6))
# 1000000 [2, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 5]

import random
r = random.randint(1, 10**7)
# 5197227 [3, 7, 379, 653]

 

ジェネレータ

ジェネレータについて。
 
通常の関数のreturnの部分をyieldとするとジェネレータを自作できる。
  
偶数を生成するジェネレータ

def gene():
    even = 0
    while True:
        yield even
        even += 2
        
e = gene()
for i in range(5):
    print(next(e))

 
素数を生成するジェネレータ

def gen2(start=2, stop=1000000):
    pr = max(1, start - 1)
    while True:
        while pr < stop:
            pr += 1
            if all(pr%x != 0 for x in range(2, int(pr**0.5) + 1)):
                break
        yield pr
g = gen2()
for i in range(10):
    print(next(g), end=" ")
# 2 3 5 7 11 13 17 19 23 29 

 
フィボナッチ数列を生成するジェネレータ

def fib_gene():
    n = 1
    while True:
        f = ((1 + 5**0.5) / 2)**n / 5**0.5 + 0.5
        yield int(f)
        n += 1

f = fib_gene()        
for i in range(10):
    print(next(f), end=" ")
# 1 1 2 3 5 8 13 21 34 55

 
・ジェネレータは無限ループを作れる。
 というか、無限ループに陥ることもある。
 
・値をすべて取り出すとStopIterationのエラーが発生。

def gene():
    week = ["月", "火", "水", "木", "金", "土", "日"]
    for day in week: 
        yield day

day = gene()
for i in range(7):
    print(next(day), end=" ")
# 月 火 水 木 金 土 日 
        
day = gene()
for i in range(15):
    print(next(day), end=" ")
# StopIteration

 
StopIterationを回避するには取り出す個数を確認するか、whileループを使うなど。

def gene():
    week = ["月", "火", "水", "木", "金", "土", "日"]
    while True:
        for day in week: 
            yield day
        
day = gene()
for i in range(15):
    print(next(day), end="")
# 月火水木金土日月火水木金土日月

 
・値の取り出し
 ・next()関数
 ・__next__()メソッド
 ・イテレートする
 ・max(), list()など

・値の代入
 send()

PILの使い方(1)

PIL(Python Imaging Library)の使い方について。
 

画像の読み込み

img = Image.open("filepass")
のように、ファイルを読み込む。
この時点では参照されているだけで、必要になってからデータを読み込む。
 
下の例では作業ディレクトリの下のdataフォルダに該当ファイルがなければ
FileNotFoundError: [Errno 2] No such file or directory: 'data/img01.png'
というエラーが出る。
 

from PIL import Image

# 画像を読み込む
img = Image.open( "data/img01.png" )
print(type(img))  # <class 'PIL.PngImagePlugin.PngImageFile'>
print(img.size)   # (320, 240) 
print(img.mode)   # RGBA
img.show()        # Winodwsで関連付けされているアプリで画像を表示

f:id:rare_Remrin:20170520162837p:plain
 

変換

# グレースケールに変換
gray_img = img.convert("L")
gray_img.show()

f:id:rare_Remrin:20170520162928j:plain
 

保存

パス、ファイル名、形式を指定して保存

gray_img.save( "img/gray.jpg", quality=90 )

 

サムネイル化(縮小)

resize()は戻り値として新しい画像オブジェクトを返す。拡大もできる。
img.thumnail()で元の画像オブジェクトそのものを縮小。
元画像をまた使うならばcopy()してからサムネイル化。

img.thumbnail( (160, 120) )
img.show()

f:id:rare_Remrin:20170520164011p:plain
 

回転、反転

・transpose() 上下・左右反転、90度単位の回転
・rotate() 角度指定の回転、
  resampleオプションで近傍法の種類を選べる。

左右反転 transpose(Image.FLIP_LEFT_RIGHT)
上下反転 transpose(Image.FLIP_TOP_BOTTOM)
90度回転 transpose(Image.ROTATE_90)
180度回転 transpose(Image.ROTATE_180)
270度回転 transpose(Image.ROTATE_270)
90度回転? transpose(Image.TRANSPOSE)
角度指定の回転 img.rotate(10)など

 

貼り付け

paste(img_obj, box, mask)
img_obj貼り付けるイメージオブジェクト
box:2次元タプルで左上の座標、または4次元タプルで左上、右下の座標
mask:マスクの指定

img = Image.open("data/img01.png")
thum = img.copy()
thum.thumbnail( (160, 120) )
img.paste(thum.transpose(Image.FLIP_TOP_BOTTOM), (0, 0))
img.paste(thum.convert("L"), (0, 120))
img.paste(thum, (160, 120, 320, 240))
img.show()

 
f:id:rare_Remrin:20170520171255p:plain
 

色の取り出し、変換

split()でR, G, BやA(不透明度)を取り出せる。
merge()で再構成

img = Image.open("data/img01.png")
r, g, b, a= img.split()  # RGB形式なら r, g, b= img.split()
img = Image.merge("RGBA", (r, b, g, a))
img.save("data/img01_rgba.png")
img.show()

ブドウ?紫イモ?
f:id:rare_Remrin:20170520175127p:plain
 
カラフルすぎるソフトクリームだと気持ち悪いので折り紙画像を準備して 
f:id:rare_Remrin:20170520174623p:plain

img = Image.open("data/img03.png")
thum = img.copy()
thum.thumbnail( (160, 120) )
r, g, b= thum.split()
# R, G, Bを適当に入れ替えてみる
img.paste(Image.merge("RGB", (r, g, b)), (0, 0))
img.paste(Image.merge("RGB", (g, r, b)), (160, 0))
img.paste(Image.merge("RGB", (r, b, g)), (0, 120))
img.paste(Image.merge("RGB", (b, g, r)), (160, 120))
img.show()

 
f:id:rare_Remrin:20170520174903p:plain 
 

ヒストグラムの表示

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

img = Image.open("data/img01.png")
r, g, b, a= img.split()
rh = np.array(r).flatten()
gh = np.array(g).flatten()
bh = np.array(b).flatten()
plt.hist((rh, gh, bh), 20, color=("r", "g", "b"), rwidth=0.9)

f:id:rare_Remrin:20170520184532p:plain 

フィボナッチ数列

フィボナッチ数列について。
 

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return b

print([fib(i) for i in range(10)])  
# [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

 
メモ化した場合

fib_memo = {}
def fib(n):
    if n < 3: return 1
    if n not in fib_memo:
        fib_memo[n] = fib(n-1) + fib(n-2)
    return fib_memo[n]

print(fib(10)) # 55

 
このメモ化だと、fib_memoという辞書が使用後も残ってしまったり、
n>1000だとrecursive errorが出たりする。
 
@lru_cacheデコレータ(Least-recently-used cache decorator)を使う。
関数の引数と戻り値を記憶し、同じ引数で呼ばれたら関数を実行せずに値を返す。
同じ引数で何度も呼び出す関数だと劇的に速くなる。

counter = 0
def fib1(n):
    global counter
    counter += 1
    if n in [0, 1]:
        return 1
    return fib1(n - 1) + fib1(n - 2)

print(fib1(24))
print(counter, "回の関数呼び出し")
# 75025
# 150049 回の関数呼び出し
    
    
from functools import lru_cache
counter = 0
@lru_cache(maxsize=1024)
def fib2(n):
    global counter
    counter += 1
    if n in [0, 1]:
        return 1
    return fib2(n - 1) + fib2(n - 2)

print(fib2(24))
print(counter, "回の関数呼び出し")
# 75025
# 25 回の関数呼び出し

 
lru_cacheなしだと関数を15万回呼び出していたが、lru_cacheありだと25回で済む。
 
 
一般項の公式
f:id:rare_Remrin:20170519152551p:plain
より、ループなしでフィボナッチ数列の項を求めることもできます。

def fib(n):
    f = ((1 + 5**0.5) / 2)**n / 5**0.5 + 0.5
    return int(f)

print(fib(10)) # 55

 
2項間の比を調べてみると

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(20)
y = np.array([fib(i + 1) / fib(i) for i in range(20)])
plt.plot(x, y, "ro-")
plt.show()

 
f:id:rare_Remrin:20170519145736p:plain

1.62くらいに収束してます。