2024/04/02

【Excel関数】再帰LAMBDA

event_note4月 02, 2024 edit By スタディPCネット 武蔵ヶ丘校 forum No comments

【Excel関数】ローカルのLAMBDA関数を再帰呼び出しする

作成:2024年04月02日
等差数列の和を、再帰関数で求める例で解説します。
「10」だったら、10+9+・・・+2+1=55、というやつです。
  1. VBAだったら
  2. 名前の定義で再帰関数を作る
  3. セル内で再帰関数を定義して使う(その1)
  4. セル内で再帰関数を定義して使う(その2)

1. VBAだったら

まぁ、プログラミング言語だったら、こんなもんでしょう。
Function func(引数 As Integer) As Integer
  If 1 <= 引数 Then
    func = 引数 + func(引数 - 1)
  End If
End Function

2. 名前の定義で再帰関数を作る

「名前の定義」の場合、VBAと同じ感じで再帰呼び出しができます。
=LAMBDA(引数,IF(1<=引数,引数+func(引数-1)))
funcという名前を作成し、参照範囲は「自分自身を呼び出すLAMBDA関数」を設定します。

3. セル内で再帰関数を定義して使う(その1)

本題のローカル関数です。VBAと同じ感じで書くとエラーになります。
=LET(
  func, LAMBDA(引数, IF(1<=引数, 引数+func(引数-1))),   ← エラー
  func(10)
)
LAMBDA内で func は未だ定義されていないので使えません。

なので、funcを定義した後で、funcの引数にfuncを渡してみましょう。
=LET(
  fOuter, LAMBDA(fInner,引数, IF(1<=引数, 引数+fInner(fInner,引数-1))),
  fOuter(fOuter,10)
)
LAMBDAの引数に自身のLAMBDAを渡して再帰させることができました。
ちなみに、VBAではLAMBDAは作れませんし、ユーザー定義関数にLAMBDAを渡すこともできません。

「fOuter(fOuter,10)」ではなく「fOuter(10)」という呼び出し方にしましょう。
=LET(
  fOuter, LAMBDA(fInner,引数, IF(1<=引数, 引数+fInner(fInner,引数-1))),
  func, LAMBDA(引数, fOuter(fOuter,引数)),
  func(10)
)

ちょっと名前を修正します。
=LET(
  f, LAMBDA(f,引数, IF(1<=引数, 引数+f(f,引数-1))),
  func, LAMBDA(引数, f(f,引数)),
  func(10)
)
LAMBDAの内外で名前(f)が重複しています。この場合、LAMBDA内でfを使うとLAMBDAの引数の方のfを指すことになるので、この書き方が許されます。

引数2個の場合はこうなります。
=LET(
  f, LAMBDA(f,引数1,引数2, IF(引数2<=引数1, 引数1+f(f,引数1-1,引数2))),
  func, LAMBDA(引数1,引数2, f(f,引数1,引数2)),
  func(10,2)
)
開始と終了の範囲を指定できるようになりました。上記は、10+9+・・・+3+2=54になります。

4. セル内で再帰関数を定義して使う(その2)

正統な方法はこうみたいです。
=LET(
  z, LAMBDA(f, LET(g, LAMBDA(x, f(LAMBDA(v, LET(xx, x(x), xx(v))))), g(g))),
  func, z(LAMBDA(func, LAMBDA(x, IF(1<=x, x+func(x-1))))),
  func(10)
)
どうなってるんでしょう。正直難しすぎてわかりません。
呪文と割り切って、もう使えたらいいじゃない・・・という意見もあるかもしれませんが、このままだと大きな問題を抱えているんです。
引数が増減した場合、どこを修正したらいいかわかりますか?わからないですよね!?

なので加工します。
=LET(
  z, LAMBDA(ff, LET(g, LAMBDA(gg, ff(LAMBDA(arg_1, gg(gg)(arg_1)))), g(g))),
  f, LAMBDA(fff, LAMBDA(引数1, IF(1<=引数1, 引数1+fff(引数1-1)))),
  func, z(f),
  func(10)
)
相変わらず何をやってるのかはわからないけど、引数が増減した場合にどこを修正したらいいのかはわかるようになりました。

引数2個の場合はこうですね。
=LET(
  z, LAMBDA(ff, LET(g, LAMBDA(gg, ff(LAMBDA(arg_1,arg_2, gg(gg)(arg_1,arg_2)))), g(g))),
  f, LAMBDA(fff, LAMBDA(引数1,引数2, IF(引数2<=引数1, 引数1+fff(引数1-1,引数2)))),
  func, z(f),
  func(10,2)
)

ステップ実行してみよう

LAMBDA関数は「数式の検証」に対応しておらず、デバッグ(ステップ)実行ができません。かといって、VBAではLAMBDAが使えない。
ということで、PowerShellで書いてみました。興味ある方はデバッグ実行してみてください。
[scriptblock] $z = {
  Param([scriptblock] $ff)
  [scriptblock] $g = {
    Param([scriptblock] $gg)
    return $ff.Invoke({
      Param([int] $arg_1, [int] $arg_2)
      return $gg.Invoke($gg)[0].Invoke($arg_1, $arg_2)[0];
    }.GetNewClosure())[0];
  }.GetNewClosure();
  return $g.Invoke($g)[0];
};

[scriptblock] $f = {
  Param([scriptblock] $fff)
  return {
    Param([int] $引数1, [int] $引数2)
    if ($引数2 -le $引数1) {
      return $引数1 + $fff.Invoke($引数1-1, $引数2)[0];
    }
  }.GetNewClosure();
};

[scriptblock] $func = $z.Invoke($f)[0];

$func.Invoke(10, 2);

0 comments:

コメントを投稿