itertoolsで多重ループを回避してみる

# なにをしたいのか
多重forループは実行速度を低下させる原因になるので、itertoolsでこれを回避し、速度の比較をしてみる。
（おまけ：numpyのbroadcastを全力で使ってfor文に対抗する）

# 方法
- 2重のforループ、3重のforループ、4重のforループのそれぞれと、itertools.productで1重にした場合とを比較する。
- 0から99の数字を総当たりで掛け合わせて、これらの合計を計算する。
- 一応resultとして検算しておきます。

# 結果
うわっ…私の実行速度、遅すぎ…？
itertoolsの方が処理が遅いんですがなんででしょうか。

--- double_for ---
result 24502500
time :  0.000659942626953125
--- double_product ---
result 24502500
time :  0.0007491111755371094

--- triple_for ---
result 121287375000
time :  0.08507108688354492
--- triple_product ---
result 121287375000
time :  0.10134720802307129

--- quadruple_for ---
result 600372506250000
time :  11.622879981994629
--- quadruple_product ---
result 600372506250000
time :  13.198912858963013

# 実行したスクリプト

```nested_loop.py
from itertools import product
import time


base_list = list(range(100))


def double_for():
    result = 0
    for i in base_list:
        for j in base_list:
            result += i * j
    print("result", result)


def double_product():
    result = 0
    pools = product(base_list, base_list)
    for i, j in pools:
        result += i * j
    print("result", result)


def triple_for():
    result = 0
    for i in base_list:
        for j in base_list:
            for k in base_list:
                result += i * j * k
    print("result", result)


def triple_product():
    result = 0
    pools = product(base_list, base_list, base_list)
    for i, j, k in pools:
        result += i * j * k
    print("result", result)


def quadruple_for():
    result = 0
    for i in base_list:
        for j in base_list:
            for k in base_list:
                for l in base_list:
                    result += i * j * k * l
    print("result", result)


def quadruple_product():
    result = 0
    pools = product(base_list, base_list, base_list, base_list)
    for i, j, k, l in pools:
        result += i * j * k * l
    print("result", result)


if __name__ == '__main__':
    print("-" * 3, "double_for", "-" * 3)
    t = time.time()
    double_for()
    print("time : ", time.time() - t)

    print("-" * 3, "double_product", "-" * 3)
    t = time.time()
    double_product()
    print("time : ", time.time() - t)

    print("-" * 3, "triple_for", "-" * 3)
    t = time.time()
    triple_for()
    print("time : ", time.time() - t)

    print("-" * 3, "triple_product", "-" * 3)
    t = time.time()
    triple_product()
    print("time : ", time.time() - t)

    print("-" * 3, "quadruple_for", "-" * 3)
    t = time.time()
    quadruple_for()
    print("time : ", time.time() - t)

    print("-" * 3, "quadruple_product", "-" * 3)
    t = time.time()
    quadruple_product()
    print("time : ", time.time() - t)
```

# おまけ（これってnumpyで処理したほうが早いじゃん）
圧倒的ですね。

--- double_for ---
result 24502500
time :  0.0006780624389648438
--- double_numpy ---
result 24502500
time :  0.00026702880859375

--- triple_for ---
result 121287375000
time :  0.09470272064208984
--- triple_numpy ---
result 121287375000
time :  0.005362987518310547

--- quadruple_for ---
result 600372506250000
time :  12.442141771316528
--- quadruple_numpy ---
result 600372506250000
time :  0.5840020179748535

```nested_loop_np.py
import numpy as np
import time


base_list = list(range(100))


def double_for():
    result = 0
    for i in base_list:
        for j in base_list:
            result += i * j
    print("result", result)


def double_np():
    a = np.arange(100).reshape(1, 1, 100)
    b = a.T
    result = np.sum(a * b)
    print("result", result)


def triple_for():
    result = 0
    for i in base_list:
        for j in base_list:
            for k in base_list:
                result += i * j * k
    print("result", result)


def triple_np():
    a = np.arange(100).reshape(1, 1, 100)
    b = a.reshape(1, 100, 1)
    c = a.reshape(100, 1, 1)
    result = np.sum(a * b * c)
    print("result", result)


def quadruple_for():
    result = 0
    for i in base_list:
        for j in base_list:
            for k in base_list:
                for l in base_list:
                    result += i * j * k * l
    print("result", result)


def quadruple_np():
    a = np.arange(100).reshape(1, 1, 1, 100)
    b = a.reshape(1, 1, 100, 1)
    c = a.reshape(1, 100, 1, 1)
    d = a.reshape(100, 1, 1, 1)
    result = np.sum(a * b * c * d)
    print("result", result)


if __name__ == '__main__':
    print("-" * 3, "double_for", "-" * 3)
    t = time.time()
    double_for()
    print("time : ", time.time() - t)

    print("-" * 3, "double_numpy", "-" * 3)
    t = time.time()
    double_np()
    print("time : ", time.time() - t)

    print("-" * 3, "triple_for", "-" * 3)
    t = time.time()
    triple_for()
    print("time : ", time.time() - t)

    print("-" * 3, "triple_numpy", "-" * 3)
    t = time.time()
    triple_np()
    print("time : ", time.time() - t)

    print("-" * 3, "quadruple_for", "-" * 3)
    t = time.time()
    quadruple_for()
    print("time : ", time.time() - t)

    print("-" * 3, "quadruple_numpy", "-" * 3)
    t = time.time()
    quadruple_np()
    print("time : ", time.time() - t)

```