VBAHaskellに、1次元配列の部分列を作成するsubV
という関数を追加した。1
機能的にはsubM
関数の1次元限定バージョンに過ぎないが、こちらは2変数なので関数オブジェクト化ができる利点がある。
ここで書きたいのは、その実装方法とdll側に合理化を行ったことだ。
m = iota(1, 10000000) ' 大きな配列
printM subV(m, Array(2,4,6,8)) ' m(2), m(4), m(6), m(8)
3 5 7 9
上の通り、配列とインデックスを指定するとその位置の要素を取り出してくるだけなので、subV
を普通にループで実装することに問題はない。が、ループを避けて以下のように実装した。
'1次元配列の部分配列を作成する
Public Function subV(vec As Variant, ByRef index As Variant) As Variant
Dim fn As Variant
fn = p_getNth ' getNth はN番目の要素を取り出す関数
swap2nd fn, vec ' 第2引数をswapによって対象配列に束縛する
subV = mapF(fn, index) ' indexに対してmapする
swap2nd fn, vec ' swapした対象配列を戻す
End Function
Public Function p_subV(略
基本的には単一の要素を取得するgetNth
関数をmapF
で繰り返し適用しているだけだが、問題がある。引数vec
に渡される対象配列が巨大な場合に、そのコピーを生成してしまったらすごく非効率になるのだ。
まず、fn = p_getNth(, vec)
と普通に書いてしまうと、vec
のコピーが発生し、fn
は巨大な関数オブジェクトになる。効率の悪化を避けるためには上に書いたように、いったん裸の関数オブジェクトとして軽く生成しておいて、あとから第2引数にvec
をswap渡ししてやればいい。
swapとはAPI関数swapVariant
およびそれを使ったswap1st
やswap2nd
関数2 のことで、巨大な配列でもほとんど無視できるコストで交換できるものだ。ただし目的の処理が終わったらもう一度swapして戻してやらないと、対象配列が消えてしまう。
fn = p_getNth ' 裸の関数オブジェクト
swap2nd fn, vec ' 第2引数をあとからswapによって束縛
次にmapF
の中身だが、C++dllの中のいちばん根っこのところ3 でVariant変数を2回もコピーしていた。Variantの実体ではなくポインタで十分なところをポインタに直したところ、上に書いたsubV
が十分高速に動くようになった。
関数オブジェクトの大きさを気にしなくて済むとなると、できることが増えるかもしれない。
VBAHaskellの紹介 その16(ラムダ式?の生成)
VBAHaskellの紹介 その1(最初はmapF)
ソースコード
https://github.com/mYmd/VBA
dllバイナリ:
https://github.com/mYmd/VBA/blob/master/bin/mapM (32bit-Office用)
https://github.com/mYmd/VBA/blob/master/bin/mapM64 (64bit-Officey用)
VBAコード添付済みExcelブック
VBAHaskellほぼ全部入り.xlsm
-
functionExprクラス周り : VBA_NestFunc.hpp ↩