

以下のような関数を作成したいと思っているのですが、任意の回数ループ処理を行う方法についてご教示賜りたいと存じます。

１．引数として、①任意の次元数のnumpy配列、②各次元に対応する処理を行う関数の２つをとる。  
②すべての配列要素に対して処理を行う。ただし、その処理は引数として指定した関数に基づく。

この時、任意の回数のネストした処理の作成方法がわかりません。  
例えば4次元配列なら、汚いですが

[code]

    x = NDARRAY.shape
    for i in range(x[0]):
        for j in range(x[1]):
            for k in range(x[2]):
                for l in range(x[3]):
    
[/code]

とでもできますが、一般にn次元の時、どのようにすればよいでしょうか。  
メタプログラミングの知識は必要でしょうか。


CC BY-SA 4.0
2019-08-18T06:49:55.353
57425
2020-02-22T08:03:50.690
35491
question
python numpy
pythonにおける任意のネスト数のループ処理について


こんなのはどうでしょうか。アイデアとしては、添え字を生成してから、1段のループですべての要素をスキャンするというような感じです:

[code]

    import itertools
    import numpy as np
    
    
    # main
    def f(A, g):
      for idx in itertools.product(*[range(n) for n in A.shape]):
        g(A, A[idx], *idx)
    
    
    # callback
    def h(Arr, Elm, *idx):
      assert Arr[idx] == Elm
      print('Arr[%s] = %f' % (str(idx), Elm))
    
    
    # for example...
    shape = (2, 3, 4, 5)
    X = np.arange(np.product(shape)).reshape(shape)
    f(X, h)
    
[/code]

出力例:

[code]

    Arr[(0, 0, 0, 0)] = 0.000000
    Arr[(0, 0, 0, 1)] = 1.000000
    Arr[(0, 0, 0, 2)] = 2.000000
    Arr[(0, 0, 0, 3)] = 3.000000
    Arr[(0, 0, 0, 4)] = 4.000000
    ...
    
[/code]


CC BY-SA 4.0
2019-08-18T11:19:35.500
57430
2019-08-18T11:19:35.500
57425
answer


nditerをmulti_indexフラグ付きで使うといいですね。  
<https://docs.scipy.org/doc/numpy/reference/generated/numpy.nditer.html>

[code]

    import numpy as np
    
    x  = np.random.randn(2, 3)
    
    it = np.nditer(x, flags=['multi_index'])
    
    while not it.finished:
        # x と it.multi_index を使って何かをする
        print(it.multi_index)
        print(x[it.multi_index])
    
        # 次に進める
        it.iternext()
    
[/code]

[code]

    (0, 0)
    0.23465161779473015
    (0, 1)
    0.22383801010291296
    (0, 2)
    -0.8051323435933129
    (1, 0)
    0.8931324229893808
    (1, 1)
    -0.4271553422304515
    (1, 2)
    -2.146703943718106
    
[/code]

[code]

    # 次元が違うデータ
    x  = np.random.randn(2, 3, 2)
    
    # 以下のコードはまったく同じ
    it = np.nditer(x, flags=['multi_index'])
    
    while not it.finished:
        print(it.multi_index)
        print(x[it.multi_index])
        it.iternext()
    
[/code]

[code]

    (0, 0, 0)
    0.30500812464361104
    (0, 0, 1)
    0.7671660280535492
    (0, 1, 0)
    -0.87198212525212
    (0, 1, 1)
    -0.6311542834847657
    (0, 2, 0)
    0.5198028854150701
    (0, 2, 1)
    0.8153458301759646
    (1, 0, 0)
    -0.7942063485178719
    (1, 0, 1)
    -1.568633897435036
    (1, 1, 0)
    -1.3299054158710617
    (1, 1, 1)
    -0.4096393538574391
    (1, 2, 0)
    -0.03538806394741229
    (1, 2, 1)
    0.5462354038739015
    
[/code]


CC BY-SA 4.0
2019-08-19T00:34:00.230
57432
2019-08-19T00:34:00.230
12274
57425
answer


任意の深さのネストを再帰で表現した実装例です。  
**【実装例】**

[code]

    import numpy as np
    def looploop(func, na, dims=None, indexes=[], level=0, no=0, first=True):
        if dims is None:
            dims = na.shape
        if first:
            indexes = [0] * len(dims)
        if len(dims) <= 0:
            func(na, indexes)
            return
        for i in range(dims[0]):
            indexes[level] = i
            looploop(func, na, dims[1:], indexes=indexes, level=level+1, no=i, first=False)
    
    def func1(na, indexes):
        print("Hello:indexes=" + str(indexes))
    
    na = np.ndarray((2,3,4,5))
    
    looploop(func1, na)
    
[/code]

  * 処理名  
looploop

  * ①任意の次元数のnumpy配列  
na

  * ②各次元に対応する処理を行う関数  
func

  * funcの第１引数(numpy配列)  
na

  * funcの第２引数(インデックス)  
indexes

**【注意】**  
KLCさんの回答にある、多次元配列のデータを **１次元配列で管理した方が速度面で有利** だと思います。

【多次元】

[code]

    サイズ　     tttt[SIZE_1][SIZE_2][SIZE_3][SIZE_4]
    インデックス tttt[i1][i2][i3][i4]
    
[/code]

【１次元】

[code]

    サイズ　     t[SIZE_1 * SIZE_2 * SIZE_3 * SIZE_4]
    インデックス t[i]
    i = i1 * SIZE_2 * SIZE_3 * SIZE_4
      + i2 * SIZE_3 * SIZE_4
      + i3 * SIZE_4
      + i4
    
[/code]

iから各インデックスを求める場合は以下の式で計算します。

[code]

        i1 = (i % (SIZE_1 * SIZE_2 * SIZE_3 * SIZE_4)) // (SIZE_2 * SIZE_3 * SIZE_4)
        i2 = (i % (SIZE_2 * SIZE_3 * SIZE_4)) // (SIZE_3 * SIZE_4)
        i3 = (i % (SIZE_3 * SIZE_4)) // (SIZE_4)
        i4 = (i % SIZE_4)
    
[/code]


CC BY-SA 4.0
2019-08-22T09:27:28.270
57541
2020-02-22T08:03:50.690
2020-02-22T08:03:50.690
35558
35558
57425
answer