関数プログラミングのアプローチ (29)

関数的な配列を破壊的に初期化する

前回は、配列を破壊的に操作する方法について説明しました。これをプログラム中で利用する場合には、主に、

  • 配列を破壊的に操作した後に、その結果の配列を関数的に取り扱う場合と、
  • 配列を最後まで破壊的に操作する場合の

2種類のパターンがあります。1つ目のパターンは配列の初期化処理としてよく使われるパターンです。今回は、このパターンについて見てみます。
関数的な配列を受け取って、要素をランダムにシャッフルした関数的な配列を返す関数を考えます。
最初に補助関数を用意します。一意配列の操作は前回の関数を用います。乱数生成器は第7回のものをThunkを用いて書き換えたものを使います。

def rand ():
    def f (x,y,z,w,t):
        t=(x^(x<<11)) & 0xffffffff
        x=y & 0xffffffff
        y=z & 0xffffffff
        z=w & 0xffffffff
        w=(w^(w>>19))^(t^(t>>8)) & 0xffffffff
        return w, Thunk(lambda: f(x,y,z,w,t))
    return Thunk(lambda: f(123456789, 362436069, 521288629, 88675123, 1))

一意配列から関数的な配列を取り出すには、fromUnique関数をそのまま利用します。一意配列を生成するには、配列の長さと要素を指定した次の関数を使います。

def createUniqueArray (elm, len):
    return Unique([elm] * len)

これらを用いて、配列をシャッフルする関数は次のように作ることができます。

def shuffle (arr0, rnd):
    arr = createUniqueArray(None, len(arr0))
    for i in range(0,len(arr0)):
        arr = update(arr, i, arr0[i])
    for k in [len(arr0) - k for k in range(0,len(arr0)-1)]:
        i,rnd = get(rnd)
        i = i % k
        t0,arr = select(arr, k-1)
        t1,arr = select(arr, i)
        arr = update(arr, k-1, t1)
        arr = update(arr, i, t0)
    return fromUnique(arr)

この関数は次のように呼び出すことができます。

ls = shuffle([0,1,2,3,4,5,6,7,8,9], rand())

shuffle関数は、入出力と同じように一意型を使っていますが、入出力とは大きく違う点があります。shuffle関数は、一意な値を引数にも返値にも取りません。これは、必ず一意なファイルハンドルを引数と返値に取っていた入出力の場合とは大きな違いです。shuffle関数は、外面的には全く関数的なのです。