Googleスプレッドシート LAMBDA REDUCE関数の魅力 1(変換リストで一括置換)
GoogleスプレッドシートのLAMBDAヘルパー関数において、最強だけど最も難解である REDUCE関数の魅力と、ぶっちゃけどんな時に活用できる関数なのか? を書いてみたいと思います。
REDUCE関数登場前、旧関数だけでどのように対応していたのか? またGAS(JavaScript)のREDUCE関数についても合わせて少し触れたいと思います。
前回の noteは、少し変わったグラフを作成する方法について書きました。
そもそも REDUCE関数って?
Googleスプレッドシートでは、2022年9月から使えるようになったLAMBDAヘルパー関数の一つ REDUCE関数。
りでゅーす関数?ってなに?つおい?
って人でもなんとなくわかるように、最初に少しだけGoogleスプレッドシートのREDUCE関数について書いておきましょう。
シート関数としての REDUCEを振り返る
REDUCE(reduce)は、元々は GAS(JavaScript)などプログラミング言語の 配列メソッドとして人気があったもので、配列を順に処理しながら直前の結果に累積して最終的に単一の結果を返すという、いわゆる反復処理をする関数です。
Pythonだと 高階関数 なんて言い方をしますね。
たいていの場合 for ループでも同様の処理を記述できますが、 reduceやmapといった配列メソッドを使うことで、コードが簡潔でクールなものになります。
これをシート関数としていち早く取り入れたのがExcelで、REDUCE他 LAMBDAヘルパー関数軍団は、2021年7月に Office Insidersで先行導入され、現在では 365や Web版の Excelオンラインで利用できる関数となっています。(といっても使ってる人は多くない印象ですが)
今まで作業セルを使う方法でしか対応できなかった、もしくはVBAを使うことが当然とされていた 繰り返し処理が、REDUCEなどLAMBDAヘルパー関数の登場で、シート関数を組み合わせた一つの式で実現できるようになりました。
これは Excelの大きな転機となったと言えるでしょう。
そして約1年後の 2022年9月、Googleスプレッドシートが ExcelからREDUCE関数他 LAMBDAヘルパー関数を輸入したわけです。
いち早く覚えてアウトプットしたいなってことで、mirが note始めるきっかけの一つだったりもします。
Googleスプレッドシートの REDUCE関数
Googleスプレッドシートに 輸入された時の紹介でも、mir の厨二的 区分では キャッチーで扱いやすい
火の BYROW や
風の MAP に対して
光の REDUCE ということで、光属性に位置付けておりました。
(これはどうでもいい話w)
↓ REDUCE登場時の検証note
REDUCE関数の基本は、上の検証 noteを参照いただくとして、今回はより具体的な活用例を中心に書いていきたいと思います。
しかし、LAMBDAヘルパー関数の中でも REDUCEは最強かつ再難解(いわゆる 呪術廻戦 で言うところの五条先生の術式)ということで、全員が最新関数を使える Googleスプレッドシートにおいても、まだまだ活用が進んでないと感じます。
ネット上でも人気の Arrayformula や QUERY関数に比べると、Googleスプレッドシートの REDUCE関数の 具体的な活用事例を書いてるサイトは非常に少ないんですよね。
ちなみに note で
"Googleスプレッドシート REDUCE"
で検索するとヒットする記事の半分以上は mirのものだったりしますw
最新関数:変換表で一括置換する
REDUCEの活用事例としてちょうどいいものが、前々回の検索と置換の GAS TexFinderの回に登場しています。
変換表で一括置換する:GAS TextFinderで実行する場合
それが、この変換表に基づいて 対象範囲の文字列を一括置換(動き的にはループ置換)という処理です。
この処理をGASを使わず「検索と置換」の機能でやる場合は、手作業で繰り返すしかありません。
ちなみに、Excelだったらこれも PowerQuery(パワークエリ)で対処できます。ほんま万能ツールやで~。
しかしA列のデータを直接置換するのではなく、隣のB列に変換表に基づいて置換した結果を出力するで良ければ、シート関数でも実現することができます!
Q1. 関数で 一つのケースで一括置換する 基本問題
それでは、まずは簡単なお題からいってみましょう。
画像のように A1:A16に対象となる文章が入ったセルがあり、D3:E12に変換表(左の文字を検索し右の文字に置き換えたい)があったとします。
ゴールは変換表を使っての置換処理ですが、まずは対象の A1:A16 の文字列を、変換表の1つ目 D3(りんご)をE3(アップル)に変換する、これだけをやってみましょう。
検索文字と置換後は一組だけですが、複数セルの一括置換なので配列処理の基本の理解は必要です。
B1セルにどのような式を入れれば良いでしょうか?
↓ A列のデータ
りんご色のワンピース。
みかんジュースとりんごジュースを飲む。
りんご飴を食べる。めろん飴は ベロが緑になる。
みかんの皮をむく。りんごは皮ごと食べられる。
ぱいなっぷるの芯を抜く。
ウォーターめろんの種を吐き出す。
うめの花が咲く。
りんごの木に登る。
りんごの木の実を摘む。
ももの風味のケーキ。
ぶどうの香りが漂う。
れもんの酸味がする。
いちごの甘味がする。
りんごの栄養を摂る。
めろんの歴史を学ぶ。
りんごの伝説を調べる。
↓変換表
変換表
検索 置換
りんご アップル
みかん オレンジ
めろん メロン
すいか ウォーターメロン
ぱいなっぷる パイナップル
うめ プラム
もも ピーチ
ぶどう グレープ
れもん レモン
いちご ストロベリー
まずは自力でやってみましょう!
↓↓↓
ここから回答
A1. 関数で 一つのケースで一括置換する基本問題 回答
正解はこちら
もしくは
でもよいです。
ただ、今回は正規表現を使う高度な置換処理ではないので、メタ文字を意識せず使える SUBSTITUTEの方が良いかなと思います。
SUBSTITUTEはExcelでもお馴染みの 置換関数ですね。
シンプルにArrayformulaをつけることで配列に対して SUBSTITUTEが実行され、りんご が全て アップル に置換されました。
ここまでは大丈夫でしょうか?
Q2. 関数で 2つのケースで 一括置換する 基本問題
では、りんご → アップル 置換に加えて、変換表の2番目の D4(みかん)をE4(オレンジ)の置換 も含めた2つのケースを適用した一括置換だったら、どのような式を組めば良いでしょうか?
まだREDUCEは使わなくて大丈夫です。正解はちょっとごちゃついた式になりますが、過程なのでそれで大丈夫です。やってみましょう!
↓↓↓
ここから回答
A2. 関数で 2つのケースで 一括置換する 基本問題 回答
正解はこちら
SUBSTITUTEをさらにSUBSTITUTEと、式はごちゃごちゃしてますが、考え方はシンプルですね。
関数が入れ子になった式は内側からみていきます。
最初に D3 → E3 の りんご → アップル 変換を実施した結果を対象に
次に D4 → E4 の みかん → オンレジ 変換を実施いているわけです。
入れ子だとわかりにくいので、今だったら LETを使って手続きを可視化した式にするのも良いですね。
コチラの方が読む順番が普通に左からで、わかりやすいかも。
A1:A16のD3(りんご) をE3(アップル)に置換したものを 変数 a に格納、
次に aに対して D4(みかん)をE4(オレンジ)で置換。
という記述です。これも大丈夫ですね?
リストの2つ目まで置換が出来ました。
これを続ければ、リスト表に基づいた置換が出来そうですが、式は
Arrayformula(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE( … SUBSTITUTE(A1:A16,D3,E3)
と、延々SUBSTITUTEを繰り返し記述することになります。
これはちょっとナシですよね。そもそもリストが増減した時に対応しにくいという欠点もあります。
Q3. REDUCE関数で 変換表で 一括置換する 応用問題
このSUBSTITUTEで置換した結果をまたSUBSTITUTEする ・・・の繰り返し処理を実現するのが、まさに REDUCE関数です。
ようやく 本題に入ってきました。
A1:A16の文字列を、D3:E12に変換表(左の文字を右の文字に置き換えたい)で、一括置換した結果を B列に出力したい場合、B1セルに REDUCEを使ったどのような式を入れればよいでしょうか?
少し難しいかもしれませんが、まずは考えてみましょう!
↓↓↓
ここから回答
A3. REDUCE関数で 変換表で 一括置換する 応用問題 回答
正解はこちら
最初の REDUCE(A1:A16,D3:D12
この部分ですが、 REDUCEにおける
第1引数 初期値が A1:A16 これはいいですね。
第2引数 配列 D3:D12 の方がポイントで、
変換表 D3:E12の 左側、検索文字だけを使っています。
GAS(JavaScript)の reduceであれば、二次元配列を 渡すと 配列単位(シートでいうところの行単位)で処理を回してくれるのですが、
残念ながら シート関数の REDUCEは渡した個々のセル分だけ処理を繰り返します。
今回やりたい処理は、C3をD3に置換、C4をD4に置換と、C列D列を行単位でペアで扱いたいので、 D3:E12 を第2引数にしてしまうと希望の動作にはなりません。
というわけで 片側(検索)の方の D3:D12 だけを REDUCEの第2引数として後ろのLAMBDA にkey として 一つずつ渡し、置換後は OFFSET関数で keyの1つ右の値を取得、としています。
x は A1:A16を初期値として、常に一つ前の検索と置換を実行した 結果となるので、変換表に基づいたSUBSTITUTEの繰り返し処理が実現できるわけです。
面白いですね!
人によっては offsetは 揮発性関数ということで忌み嫌う方もいるので、一応他の解もあげておくと
REDUCEの配列には 連番配列を渡して 内部でINDEXで値を取得するこんな式や
xlooupで引っ張ってくる こんな式も同様の結果が得られます。
ちなみに xlookupは組み合わせたこの式は、いきなり答える備忘録さんが使われていました。人によって書き方が違って面白いですね。
REDUCEを使った 変換表に基づいた一括置換、理解できましたでしょうか?
実用的かつ REDUCEの動きを理解するには、なかなか良いお題かと思います。
ちなみにこのリストに基づいて置換するREDUCE式は、ARRAYFORMULAを外せば Excelでも同じように使えます。
ひと昔前の方法:変換表で一括置換する
LAMBDA REDUCEの登場で、この変換表に基づいた一括置換処理が簡単に出来るようになりましたが、これらが登場する前は Googleスプレッドシートの シート関数の組み合わせでは対応できなかったのか?
というと、そんなことはありません。
Excel職人、Googleスプレッドシート職人たちの 古の技法(ロスト・マギア)によって、無理やり 努力の結果 このお題も複雑な式を組んで対処していたわけです。
Q4. 旧関数で 変換表で置換 (1セル対象)
全ユーザーが最新関数が使えるのがGoogleスプレッドシートの魅力なのに、今さら古の方法なんぞ学んでも仕方ないんですが、何かに応用できるかもしれないのでお題いっときましょう。
複数セルはちょと難易度が高いので、A列の1セルだけを対象とした変換表による置換、どのような式を組めばよいでしょうか?
ワードが2つくらい欲しいのでA1セルを以下のように少し変えてます。
もちろん、LETやLAMBDA以降の最新関数の利用はNG。ひたすら SUBSTITUITEやREGEXREPLACEの入れ子も無しです。
これは・・・。興味のある人だけチャレンジしてみましょう!
↓↓↓
ここから回答
A4. 旧関数で 変換表で置換 (1セル対象) 回答
正解はこちら
ちょっと見づらいですね。インデントつけましょう。
=ARRAYFORMULA(
CONCATENATE(
IFERROR(
VLOOKUP(
SPLIT(
REGEXREPLACE(A1,"("&TEXTJOIN("|",TRUE,D3:D12)&")","_$1_")
,"_",TRUE,FALSE
),
D3:E12,2,false
),
SPLIT(
REGEXREPLACE(A1,"("&TEXTJOIN("|",TRUE,D3:D12)&")","_$1_")
,"_",TRUE,FALSE
)
)
)
)
SPLIT(REGEXREPLACE(… の部分が2回出てくるせいで長くなっています。どうしても LETが使えない時代の式は、今見ると煩雑に感じますね。
一応解説しておくと、文字列に対して繰り返し処理は無理なので キーワードで区切って配列化して、VLOOKUPで置換して再結合という処理をしています。
まず、変換表の左側を TEXTJOINで "|" (パイプ)を挟んで連結してカッコで括ります。
これを REGEXREPLACEで使うことで 正規表現の OR検索となります。見つかったワードはカッコで括ってるので、$1でキャプチャグループとして再利用できるので、前後に区切り文字を仕込んで置換処理をします。
これをSPLITで分割し配列化します。
空白除去してしまうと ケースによっては 後で文がズレることがあるので、あえて第4引数を FALSE として 空白を残しています。
この配列を使って VLOOKUPで 検索ワードを置換後に置き換えます。ここから配列処理になるので、Arrayformulaも必要。
ここで検索ワード以外の部分は、見つからないってことでエラーが返るので・・・
IFERRORを使って エラーなら 元の配列のままという式にします。
ここで同じ SPLIT(REGEXREPLACE(… が2回出てきてしまいます。
最後に CONCATENATE で結合すれば 完成です。
かなり大変ですね。
ちなみに この式は 弱点があります。
VLOOKUP で変換してるんで、下の画像のように 日本語の一致がアバウトなんです。。
Googleスプレッドシートの日本語一致のアバウトさについては、XLOOKUPの回で検証しています。
それでも 既存関数を組み合わせて、このように対処していた時代があったわけです。
まさに先人の知恵と工夫ですね。
余談:一つの式で複数セルの 変換表 一括置換
これをさらに 1つの式で複数セルに対して一括処理しようとすると、超面倒です。
今はBYROWやMAPがあるので割と簡単ですが、この時代は 行単位で結合といった処理はかなりハードルが高いものでした。
こちらは お題ではなく参考程度ってことで、回答だけ記載しておきます。
置換対象のセル範囲が 1列であるという条件付きですが
=ARRAYFORMULA(
TRANSPOSE(
SPLIT(
CONCATENATE(
{
IFERROR(
VLOOKUP(
IFERROR(SPLIT(REGEXREPLACE(A1:A16,"("&TEXTJOIN("|",TRUE,D3:D12)&")","_$1_"),"_",TRUE,FALSE)),
D3:E12,2,false
),
IFERROR(SPLIT(REGEXREPLACE(A1:A16,"("&TEXTJOIN("|",TRUE,D3:D12)&")","_$1_"),"_",TRUE,FALSE))
),
IF(SEQUENCE(ROWS(A1:A16))>0,"_")
}
),"_"
)
)
)
このような式で実現できます。
置換対象の範囲と同じ行数の "_" (区切り文字)の縦1列の配列を用意。
先ほどの回答の式の結合する前の配列の右に区切り文字配列を連結。
これをTEXTJOINで一旦全部の文字列を一つに結合。
最後に区切り文字でSPLITしてからTRANSPOSEで縦並びにして完成。
昔よく使ってたテクニックですが、新関数でサクっといけちゃう今となってはもう出番はないですね。
供養って感じでここに書いておきますw
余談: LET他新関数を使ってスッキリ記述
ちなみに 上の式は 改行なしで303文字あります。現代であればこれは LET関数で重複箇所を変数化してスッキリ記述できます。
こんな感じ ↓
=ARRAYFORMULA(
LET(
a,A1:A16,
b,D3:D12,
c,D3:E12,
x,IFERROR(SPLIT(REGEXREPLACE(a,"("&TEXTJOIN("|",TRUE,b)&")","_$1_"),"_",TRUE,FALSE)),
TRANSPOSE(SPLIT(CONCATENATE({IFERROR(VLOOKUP(x,c,2,false),x),IF(SEQUENCE(ROWS(a))>0,"_")}),"_"))
)
)
重複部分となる
IFERROR(SPLIT(REGEXREPLACE(a,"("&TEXTJOIN("|",TRUE,b)&")","_$1_"),"_",TRUE,FALSE))
ここを x と置くことで 一気に短くなりました。
合わせて 範囲指定の部分を a,b,c と変数化することで、範囲変更の手間を簡略化しています。
これで228文字。LET関数も偉大ですね。
さらに、新関数の LAMBDA+BYROW、XLOOKUPを使えば
=ARRAYFORMULA(
LET(
a,A1:A16,
b,D3:D12,
c,E3:E12,
x,IFERROR(SPLIT(REGEXREPLACE(a,"("&TEXTJOIN("|",TRUE,b)&")","_$1_"),"_",TRUE,FALSE)),
BYROW(x,LAMBDA(r,CONCATENATE(XLOOKUP(r,b,c,r))))
)
)
このように一気に 180文字に圧縮できます。
行単位で セル内のテキストの結合が出来る BYROW + LAMBDA
そして見つからなかった時の値を引数で指定できる XLOOKUP
いずれの新関数も非常に強力です。
それでも XLOOKUPの 文字の一致判定は VLOOKUPと同じでカタカナとひらがなを区別できませんし、文字数的にも
=ARRAYFORMULA(REDUCE(A1:A16,D3:D12,LAMBDA(x,key,SUBSTITUTE(x,key,offset(key,0,1)))))
このREDUCEを使った式 84文字に比べれば多く、処理としても煩雑ですね。
現在においては、余談で紹介した式を使う必要性は一切ありません。この「変換表に基づいた置換」に対しては REDUCE 一択と言ってよいでしょう。
GAS自作関数:変換表で一括置換する
LAMBDA REDUCEの登場前は、シート関数で対処するには A4のような複雑な式を組むしかなかったんですが、もちろん他に GASでコードを書く 自作関数(カスタム関数)を使う方法もありました。
for文を使って 変換表で 一括置換するカスタム関数
この手の繰り返し処理は、プログラミングの得意とするところですね。ループの基本と言えば for文です。
/**
* 変換表に基づいて置換した結果を返す GAS関数
* @param {A1:A20} target 対象となる範囲または配列
* @param {D3:E12} list 変換表 左が検索ワード、右が置換後の文字の2列
* @customfunction
*/
function trans(target,list){
//二次元配列の行ループ
for(i=0; i<target.length; i++){
//二次元配列の列ループ
for(j=0; j<target[0].length; j++){
//変換表に基づく置換処理
for(k=0; k<list.length; k++){
target[i][j] = target[i][j].replaceAll(list[k][0],list[k][1]);
}
}
}
return target;
}
第1引数 で指定した 対象セルの文字列を
第2引数 (2列構成の)変換表を基に
一括置換する trans関数
これを for文を使って 自作関数化すると 上のようになります。
↑ 実際の変換処理は この部分で
対象範囲が 二次元配列となる為
1つ目のループ( i ) で一つ一つ いわゆる行を取得し
2つ目のループ( j ) で行の中から個々のセルの 文字列を 取得し
3つ目のループ( k ) で変換表の上から順に 繰返し replaceAll
処理の流れとしてはこんな感じです。
3重ループなんで 人によっては胃もたれしちゃうかもしれませんが、
処理自体はシンプルなんで、わかりやすいんじゃないでしょうか?
文字列に対してマッチした検索語を全て置換する replaceAllメソッドは、以前はGASで使えなかったので、replace で正規表現化して g フラグを付けて対処していました。
現在は普通に replaceAll 使えるんで楽ですね。
(でも エディタ上でなぜか候補にはでてこない・・・)
作成した trans関数 を使うことで、このように 複数行・複数列の範囲を 変換表に基づき一括置換することが出来ました~。
人によっては、シート関数をこねこねして複雑な式を作るよりもコード書いてカスタム関数作っちゃった方が簡単かもしれません。
reduceを使って 変換表で 一括置換するカスタム関数
上の カスタム関数のコードも reduce (とmap)を使って、クールに記述することが出来ます。
/**
* 変換表に基づいて置換した結果を返す GAS関数
* @param {A1:A20} target 対象となる範囲または配列
* @param {D3:E12} list 変換表 左が検索ワード、右が置換後の文字の2列
* @customfunction
*/
function trans2(target,list){
let result = target.map(r => r.map(v => list.reduce((pv,cv) => pv.replaceAll(cv[0],cv[1]),v)));
return result;
}
実質、処理部分は1行コードに圧縮できてますね。
target.map(r => r.map(v
↑ ここは forを使ったコードの
//二次元配列の行ループ
for(i=0; i<target.length; i++){
//二次元配列の列ループ
for(j=0; j<target[0].length; j++){
↑ この部分と同じ意味です。 二次元配列から 個々のセルの 文字列を取り出しています。
reduce部分は ↓ ですね。
list.reduce((pv,cv) => pv.replaceAll(cv[0],cv[1]),v)
構文の構成は若干違いますが、ぶっちゃけやってることは シート関数の REDUCEとほぼ同じです。
GAS(JavaScript)だと 配列単位で処理を回し、コールバック関数の処理内で cv[0] 検索文字、cv[1] 置換後 と、配列cvから欲しいものを取り出すことが出来るんで、よりシンプルに記述できます。
次回はもう1つの REDUCEの活用例を
JavaScript の reduce の わかりやすい事例って、配列内の数値の 合計を求めるって処理を紹介してることが多いんですが、正直 シート関数では普通に SUMで出来ることなんで ピンとこないんですよね。
今回はリスト表を使った 置換の繰り返し処理を お題として、REDUCE関数の活用事例を紹介しました。
合わせてREDUCE登場以前の処理方法や GASのreduceを使ったカスタム関数での対応方法についても触れました。
シート関数の REDUCE 、JavaScriptの reduce に対して 苦手意識のある人にとっては、実用性もあってわかりやすい お題だったんじゃないでしょうか?
毎回言ってますが、シート関数のREDUCEも 配列メソッドの reduceも、とにかく使って慣れることで徐々に腹落ちして 理解できるようになります。
シート関数なら
繰り返し処理が出てきたら、REDUCE使えないか?
と考えてみる、GASなら
for や forEach で書いたコードを map や reduce に置き換えてみる
といった感じで積極的に活用してみましょう!
ただし、前回の TextFinder のような シート上での操作(置換)を繰り返す処理だと for や forEachが 適していることもあります。
map や reduceは Googleスプレッドシートだと getValues で取得した 二次元配列処理で活用するのが良いかもしれません。(不慣れなうちは 一次元配列から使った方が良いかも)
次回は、もう1つ シート関数の REDUEが活用できる例として、直積(クロス結合・組み合わせ) をシート関数で処理する方法を紹介したいと思います。
この記事が気に入ったらサポートをしてみませんか?