22
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Excel の LAMBDA 関数(ベータ版)を試す

Last updated at Posted at 2021-02-14

「Excel の数式がチューリング完全になった」でお馴染みの LAMBDA 関数。うちの環境でも使えたのでちょっと遊んでみました。

導入

LAMBDA 関数は 2021-02-14 現在、Office365 サブスクリプションに加入して、Office Insider の「ベータチャネル」にサインアップすると使用可能になります。

image.png

文法

LAMBDA 関数は 関数を返す関数 です。(引数なしでもイケますが)引数の名前を列挙していき、最終項を返す関数をつくることができます。

LAMBDA関数の書式
=LAMBDA([arg1, arg2,...] calculation)

昨年末に正式導入された LET 関数とよく似た書式ですね。LET 関数ではローカル変数を宣言・初期化する必要があり、LET の中で完結していましたが、LAMBDA 関数では初期化せず、変数への代入は 呼び出し時 に行ないます。

LET関数の書式
=LET(arg1, val1, [arg2, val2,...], calculation)

たとえば三角形の面積を求める関数を作ってみましょう。LAMBDA 関数の方は、エクセルでは見慣れない カッコの連続 になっていることが分かります。「 LAMBDA 関数は関数を返す 」というのはこういう意味で、ここでは LAMBDA(底辺,高さ, 底辺*高さ/2) の部分が SUMAVERAGE のような関数と同じような部品になっていて、呼び出すときにはカッコを伴って呼び出されるということです。

三角形面積公式
// LET関数は内部に参照先を置く
=LET(底辺,A1, 高さ,B1, 底辺*高さ/2)

// LAMBDA関数は外部に参照先を置く
=LAMBDA(底辺,高さ, 底辺*高さ/2)(A1,B1)

これだけだと「そうなんだ……」という感じで、LAMBDA 関数単体ではあまり使いどころがありません。関数の定義と呼び出しを同じ箇所で行なっている 無名関数 の状態で、このままでは他のセルから呼び出すことができないからです。

関数の登録

ふつうのプログラミングをしている際の「 関数を定義する 」を行なうには、この LAMBDA 関数式を「登録」する必要があります。エクセルには「名前の登録」機能があり、いわゆるグローバル変数を定義することができますが、そこに LAMBDA 式を登録するのです。たとえば先ほどの関数を DAIKEI_AREA という名前で登録してみましょう。

image.png

「数式」メニューから「名前の定義」を行ないます。「名前」欄には作成したい関数の名前、「参照範囲」に LAMBDA 関数式を入力して OK を押すと登録完了です。

image.png

こうすることでブックの任意の場所からユーザー定義関数を使えるようになります。オートコンプリートも効きますし、コメントも表示されます。

無題.png

普通の関数と同じような使用感です。

image.png

用法

公式は 4 つの例を挙げています。

  • 華氏⇒セ氏の温度変換
    • こうした定数を含む公式を関数化しておけると便利ですね。
ToCelsius
=LAMBDA(temp, (5/9) * (temp-32))
  • 三角形の斜辺導出(ピタゴラスの定理)
Hypotenuse
=LAMBDA(a, b, SQRT((a^2+b^2)))
  • 語数カウント
    • 英語は分かち書きをするので、半角スペースの数から語数を算出していますね。
CountWords
=LAMBDA(text, LEN(TRIM(text)) - LEN(SUBSTITUTE(TRIM(text), " ", "")) + 1)
  • ある年の感謝祭の日付を表示
    • アメリカでは 11 月の第 4 木曜日が感謝祭にあたります。11/1 の曜日から第 4 木曜日の日付を逆算する感じですね。
ThanksgivingDate
=LAMBDA(year, TEXT(DATE(year, 11, CHOOSE(WEEKDAY(DATE(year, 11, 1)), 26, 25, 24, 23, 22, 28, 27)), "mm/dd/yyyy"))

先ほども述べた通り、引数をとらない関数も作成できます。LET を併用するとさらにシンプルな記述ができるようになりますね。

Greeting
=LAMBDA(LET(h, HOUR(NOW()),
        IFS(OR(h<=6, h>=23), "スヤァ……",
            h<=12,           "おはようございます!",
            h<=17,           "こんにちは!",
            TRUE,            "こんばんは!")))
// コメント:時刻に合ったあいさつを表示します。

ループ表現

LAMBDA 式の中に自身の名前を入れ込むことで、再帰呼び出しを定義することができます。これは非常に意義深いことで、これによってループ処理を実装することができます。

Microsoft の Brian Jones はブログで以下のような例を挙げています。ある文字列 (testString) から、特定の文字たち (illegalChars) を除去したい、というタスクです。

testString illegalChars (desired result)
Brian Jones 206 1234567890 Brian Jones
excel85 abcdefghijklmnopqrstuvwxyz 85
River!@# On The Water *!@#$% River On The Water

上のようなデータセットが与えられたとき、ふつうのプログラミングなら(正規表現で置換するか)illegalChars でループを回して文字数分だけ置換を繰り返すと思いますが、従来の Excel 数式でこのロジックを書くのは非常に骨でした。ループが存在しなかったからです。

LAMBDALAMBDA を用いて、illegalChars の一文字目を削りながら再帰的に値を渡していくことで、ループを実現することができます(インデント等は引用者が変更)。

ReplaceChars
=LAMBDA(textString, illegalChars,
        IF(illegalChars="", textstring,
           REPLACECHARS(SUBSTITUTE(textString, LEFT(illegalChars, 1), ""), 
                        RIGHT(illegalChars, LEN(illegalChars)-1)
)))

この仕組みを使えば、階乗や FizzBuzz も簡単に自作できます。

KAIJO
=LAMBDA(num, 
        IF(num=1, 1,
           num * KAIJO(num-1)))
// コメント:与えられた自然数の階乗を返します。
FIZZ_BUZZ
=LAMBDA(num,
        IF(num=1,            TEXT(1,"0"),
           IF(MOD(num,15)=0, TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),"FizzBuzz"),
           IF(MOD(num,5) =0, TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),"Buzz"),
           IF(MOD(num,3) =0, TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),"Fizz"),
                             TEXTJOIN(", ",TRUE,FIZZ_BUZZ(num-1),TEXT(num,"0")))))))
// コメント:与えられた自然数まで FizzBuzz を出力します。

image.png

もっとも、この程度であれば出力方法によっては再帰を使わず表現可能です。たとえば FizzBuzz についても、SEQUENCE 関数で配列を作成して入力として利用することで、疑似的にループを実行することができます。

FIZZ_BUZZ2
=LAMBDA(num,
        LET(index, SEQUENCE(1,num),
        IFS(index=1,         TEXT(1,"0"),
            MOD(index,15)=0, "FizzBuzz",
            MOD(index,5) =0, "Buzz",
            MOD(index,3) =0, "Fizz",
            TRUE,            TEXT(index,"0"))))
// コメント:与えられた自然数まで FizzBuzz を出力します(スピル)。

image.png

ローカル関数

LAMBDA 関数の少し前に LET 関数というものが導入され、私も使い方について少し記事を書きました。

LET 関数はブロックを作り、その中でローカル変数を定義することを可能にします。実は LAMBDA も LET 内で定義可能です。

image.png

もちろん配列入力もできます。この機能によってネ申エクセルはさらに高次元へと押し上げられるでしょう。

ローカル関数での再帰について

2021/04/18 追記:以前の版では以下のような例を挙げて、LET 内でも再帰的な LAMBDA 定義が可能であると記述していました。

誤った例
=LET(
    x,   123,
    y,   84,
    GCD, LAMBDA(a, b, IF(b=0, a, GCD(b,MOD(a,b)))),
    res, GCD(x,y),
    res
)

ところが、コメントで @lensouko さんから以下の指摘をいただきました。

ローカル関数の再帰は使えないですね
例に上がっているGCDは既存の関数なので使えているだけみたいですね
再帰で使いたい場合は再帰関数を定義してから使用するしかないようです

これは GCD が既存関数であることを忘れていた筆者の確認不足でした。上の「誤った例」では、4 行目の右辺で既存の GCD 関数が呼ばれており、それが左辺のローカル変数 GCD に代入されている という挙動になります(既存関数の名前をローカル変数が「上書き」することは可能)。つまり、実は「誤った例」では再帰の1層目だけしか定義できていなくて、それ以降の計算は既存の GCD 関数が担っていたから、見かけ上正しく動いていたわけですね。

というわけで、既存関数に名前が被っていない KAIJO だと普通に名前解決に失敗して #NAME? を吐きます。

名前解決できない
=LET(
    x,   5,
    KAIJO, LAMBDA(num, IF(num=1, 1, num * KAIJO(num-1))),
    res, KAIJO(x),
    res
)

2021/06/26 追記: @ytaki0801 さんにローカルで再帰関数を定義する方法を教えていただいたので、記事にしました。

カリー化

複数の引数をとる関数の一部を埋めて(部分適用)、引数の個数を少なくすることができます。たとえば自然言語の二項述語を定義したあとで、目的語のみ固定した一項述語を定義することができます。この場合 LAMBDA は 2 引数をとる関数から 1 引数をとる関数を返しています。

カリー化
LOVES
=LAMBDA(sbj, obj, CONCAT(sbj, " loves ", obj, "."))
// =LOVES("Jiro", "Saburo") とすると Jiro loves Saburo. を返す

LOVES_TARO
=LAMBDA(sbj, LOVES(sbj, "Taro"))
// =LOVES_TARO("Kanako") とすると Kanako loves Taro. を返す

HANAKO_LOVES
=LAMBDA(obj, LOVES("Hanako", obj))
// =HANAKO_LOVES("Kana") とすると Hanako loves Kana. を返す

YUKI_LOVES_TARO
=LAMBDA(LOVES_TARO("Yuki"))
// =YUKI_LOVES_TARO() とすると Yuki loves Taro. を返す

高階関数

これまでの例から察されるとおり、関数は別の変数に格納することができるだけでなく、関数の引数に与えることもできます。ただし 既存の Excel 関数 を渡したい場合は一度 LAMBDA を介する必要があるようです。

以下のように、第一引数の値に対して第二引数の関数を適用する MAP 関数を考えてみましょう。定義自体は問題なくできますし、LAMBDA で別途登録したユーザー定義関数を渡すと想定通りに動きますが、既存の Excel 関数の名前を与えても動きません。冗長ですが、いったん LAMBDA を挟んで関数であることを「分からせ」てやる必要があります。

高階関数
MAP
=LAMBDA(array, func, func(array))
// 1~10 の配列を与えて、各数値の階乗を出力したいとき、
// =MAP(SEQUENCE(1,10), FACT) だと動かない
// =MAP(SEQUENCE(1,10), LAMBDA(x, FACT(x))) だと動く

image.png

まとめ

  • LAMBDA 関数でユーザー定義関数を作れる。
  • 再帰的呼び出しが可能になるので、ループを表現できる。
  • LET ブロックと組み合わせることで、構造化プログラミングができる。
  • 無名関数、カリー化、高階関数が実現可能で、関数型プログラミングができる。
22
16
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?