今日を乗り切るExcel研究所

Excel に働かされていませんか

【Excel LAMBDA】文字列操作編(その2)【ユーザー定義関数集】

文字列操作関数集その2

LAMBDA 関数を使った数式によるユーザー定義関数(カスタム関数)として、実用的でコピペで使える実装例をまとめていきたいと思います。

使い方は以下の記事を参照してください。

www.shegolab.jp

本記事では文字列操作関連のツールとしてよく使いそうな、検索、置換、変換等の機能を主に集めました。

 

  • 本記事に掲載している数式は LAMBDA 関数をサポートしてる Excel(現状、 Microsoft 365 版(サブスク版)Excel のみ)で使用可能です。
  • 本ブログに記載のLAMBDA 数式は全て単独で利用できます。必要な関数のみを「名前の定義」で「参照範囲」にコピー&ペーストしてください。
  • 関数名は任意ですが、再帰関数として実装されている関数は名前を変更されると動作しないので注意が必要です。 これらの関数名を変更するには、LAMDA 式内の再帰呼び出し関数名も合わせて適宜修正する必要があります。
  • 不具合、使い勝手の改善、欲しい機能等ご意見ご希望がありましたら、本記事のコメントか Twitter にてお知らせください。

区切り文字で分割

【名前の定義】

  • 名前

    WSPLIT

【注意】「SPLIT」という名前は Excel で予約されていて使用できないようです。

  • コメント

    文字列を区切り文字で分割した配列にします。

  • 参照範囲
=LAMBDA(文字列, 区切り, IF(OR(文字列="", 区切り=""), T(文字列),
    IF(ISERROR(FIND(区切り, 文字列)),
         T(文字列),
        LET(
            dlm, UNICHAR(1),
            txt, dlm & SUBSTITUTE(文字列, 区切り, dlm) & dlm,
            cnt, LEN(txt) - LEN(SUBSTITUTE(txt, dlm, "")),
            brs, SCAN(0, SEQUENCE(,cnt),
                LAMBDA(p, i, FIND(dlm, txt, p+1))) + 1,
            rng, MAKEARRAY(2, cnt-1,
                LAMBDA(r, c, INDEX(brs, 1, r-1+c))),
            MAP(INDEX(rng,1), INDEX(rng,2),
                LAMBDA(a, b, MID(txt, a, b-a-1)))
        )
    )
))

【構文】

WSPLIT(文字列, 区切り)

【引数】

引数 内容
文字列 分割したい文字列
区切り 区切りとなる文字(文字列)

【戻り値】

文字列を、中に含まれる区切り文字の位置で分割し横方向の配列として返します。 文字列が指定の区切りを含まない場合は、入力の文字列そのものが返ります。

【説明】

配列を縦方向にしたい場合には TRANSPOSE関数を使ってください。

【注意】 近い将来、Microsoft 365 版の Excel で同様の機能を提供する「TEXTSPLIT関数」が導入されることになっていてそちらの方が高機能です。上記関数はそれまでの一時しのぎとして使ってください。

【使用例】

一定文字数で折り返し

【名前の定義】

  • 名前

    WRAP

  • コメント

    文字列を指定文字数で折り返します。

  • 参照範囲
=LAMBDA(文字列, 文字数, IF(OR(文字列="", NOT(文字数>0)), T(文字列),
    LET(
        _r, LAMBDA(f, LAMBDA(x, f(f, x))),
        _push, LAMBDA(a, e, IF(SEQUENCE(ROWS(a)+1)<ROWS(a)+1, a, e)),
        _tail, LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a)-1)+1,1)),
        LET(
            BR, UNICHAR(10),
            txt, 文字列,
            len, LEN(文字列),
            lineWidth, 文字数,
            breakPositions,
                _r(LAMBDA(f, curr, LET(
                    next, curr + LEN(LEFT(MID(txt, curr, len), lineWidth)), 
                    brk, IFERROR(FIND(BR, txt, curr), len+1),
                    IF(len < next,
                        next,
                        IF(brk <= next,
                            f(f, brk + 1),
                            _push(f(f, next), next)
                        )
                    )
                )))(1),
            IF(AND(len < INDEX(breakPositions, ROWS(breakPositions), 1)),
                T(文字列),
                REDUCE(txt, _tail(breakPositions), 
                    LAMBDA(s, p, REPLACE(s, p, 0, BR)))
            )
        )
    )
))

【構文】

WRAP(文字列, 文字数)

【引数】

引数 内容
文字列 折り返したいテキスト
文字数 1行あたりの文字数

【戻り値】

指定の文字数で折り返された文字列

【説明】

文字列の1行あたりの文字数が指定の文字数に収まるように改行を挿入します。

文字列中に改行があった場合は、その次の文字から数え直します。

【使用例】

後ろから検索 🆕

【名前の定義】

  • 名前

    RINDEX

  • コメント

    検索文字列が最後に現れる位置を検索します。大文字と小文字は区別されます。

  • 参照範囲
=LAMBDA(対象文字列, 検索文字列, IFS(
    検索文字列 = "", 1,
    対象文字列 = "", FIND(検索文字列, 対象文字列),
    TRUE, LET(
        _reverse, LAMBDA(s, CONCAT(MID(s, SEQUENCE(LEN(s),,LEN(s),-1), 1))),
        txt, _reverse(対象文字列),
        sub, _reverse(検索文字列),
        LEN(txt) - (FIND(sub, txt) - 1) - (LEN(sub) - 1)
    )
))

【構文】

RINDEX(対象文字列, 検索文字列)

【引数】

引数 内容
対象文字列 検索対象となる文字列
検索文字列 検索したい部分文字列

【戻り値】

検索文字列が最後に見つかった位置を返します。

検索文字列が見つからなかった場合、エラーとなります。

【説明】

検索文字列が見つからなかった場合、FIND関数と同様に #VALUE! エラーとなります。

FIND関数とは引数の順序が異なりますので注意してください。

【使用例】

呼び出し例 結果 コメント
=RINDEX("もももすももももものうち", "もも") 8

パターン抽出

【名前の定義】

  • 名前

    CAPTURE

【注意】「EXTRACT」という名前は Excel で予約されていて使用できないようです。

  • コメント

    パターンにマッチする文字列を取得します。

  • 参照範囲
=LAMBDA(文字列, パターン, 最大マッチ, IF(OR(文字列="", パターン=""), T(文字列),
    LET(
        ord, IF(OR(最大マッチ=TRUE, ISOMITTED(最大マッチ)), -1, 1),
        pos, IFERROR(SEARCH(パターン, 文字列), 0),
        IF(pos<1, "", LET(
            lefts, MID(文字列, pos, SEQUENCE(LEN(文字列)-pos+1)),
            XLOOKUP(パターン, lefts, lefts, "", 2, ord)
        ))
        
    )
))

【構文】

CAPTURE(文字列, パターン, 最短マッチ)

【引数】

引数 内容
文字列 抽出元の文字列
パターン ワイルドカードを使った抽出したいパターン
最長マッチ TRUEなら最長マッチ(デフォルト)、FALSEなら最短マッチ

【戻り値】

文字列中でパターンにマッチした部分文字列を返します。

【説明】

CAPTURE関数はテキストからあいまい検索によりパターンに最初にマッチした部分を切り出します。

「パターン」は英字の大文字と小文字を区別せず、ワイルドカードも使用できます。

Excel で使用できるワイルドカードは以下の通りです。

  • 「?」: 任意の1文字
  • 「*」: 任意の0文字以上

詳しくは以下を参照してください

「最大マッチ」はパターンにワイルドカードの「*」が使われているときの抽出方法を指定します。 最大マッチがTRUEの時、パターンがマッチする最大長の文字列を抽出します。 FALSEが指定された場合、最初に最短でパターンがマッチする文字列を抽出します。 最大マッチを省略した場合、デフォルトでTRUEが指定されます。

文字列 パターン 最大マッチ 抽出結果
abcdABCD b*d TRUE bcdABCD
abcdABCD b*d FALSE bcd
abcdABCD *c TRUE abcdABC
abcdABCD *c FALSE abc
abcdABCD c* TRUE cdABCD
abcdABCD c* FALSE c

テキストにパターンがマッチする個所が複数あっても、最初にマッチした位置から文字列が切り出されます。 これは SEARCH関数を使って開始位置を1で検索したときに返される位置と一致しています。

【使用例】

呼び出し例 結果 コメント
=CAPTURE("Word Excel PowerPoint", "EXCEL", ) Excel 大文字小文字を区別しない
=CAPTURE("090(1234)5678", "(*)",) 1234 最初のかっこの範囲
=CAPTURE("{'abc''def'}", "'*'", TRUE) 'abc''def' 最初と最後のクオートの範囲
=CAPTURE("{'abc''def'}", "'*'", FALSE) 'abc' 最初のクオートの範囲
=TRIM(CAPTURE("山田 太郎", "* ",)) 山田 最初の半角スペースより前
=TRIM(CAPTURE("山田 太郎", " *",)) 太郎 最初の半角スペースより後ろ
=SUBSTITUTE(CAPTURE("taro@example.jp, "*@",), "@", "") taro メールアドレスからユーザ名を抽出

 



 

一括置換

【名前の定義】

  • 名前

    MULTIREPLACE

  • コメント

    パターンにマッチする文字列を取得します。

  • 参照範囲
=LAMBDA(文字列, 検索配列, 置換配列,
    LET(
        _apply, LAMBDA(x, f, f(x)),
        repls, MAP(
            検索配列,
            置換配列,
            LAMBDA(old, new, 
                LAMBDA(str, SUBSTITUTE(str, old, new)))
            ),
        REDUCE(文字列, repls, _apply)
    )
)

【構文】

MULTIREPLACE(文字列, 検索配列, 置換配列)

【引数】

引数 内容
文字列 抽出元の文字列
検索配列 置換対象の検索文字列の配列
置換配列 検索配列に対応した置換文字列の配列

【戻り値】

全ての置換対象が置換された文字列を返します。

【説明】

MULTIREPLACE 関数は配列で渡された複数の検索・置換の組をもとにテキスト中にある検索文字列を置換します。

例えば、メール本文のテンプレートへの顧客名や取引金額を差し込んだり、NGワードや非対応文字の置き換えなどに使えます。

「検索配列」にはテキスト中で置換対象となる文字列の配列やセル範囲を指定します。

「置換配列」には置換後の文言の文字列の配列やセル範囲を指定します。

テキストから検索文字列をひとつずつ検索し、文字見つかれば、置換配列の同じ位置の文字列で置き換えます。

検索配列と置換配列の配列の形は縦でも横でも矩形でもOKですが、両者の形(行数と列数)が同じになっている必要があります。

【使用例】

反転

【名前の定義】

  • 名前

    REVERSE

  • コメント

    文字列の文字順を反転します

  • 参照範囲
= LAMBDA(文字列, IF(文字列="", "",
    LET(
        _notLowSurrogate, LAMBDA(ch, LET(uc, UNICODE(ch), OR(uc < 56320, 57343 < uc))),
        _chars, LAMBDA(s, LET(
            chs, LEFT(MID(文字列, SEQUENCE(LEN(文字列)), 2)),
            FILTER(chs, MAP(chs, _notLowSurrogate))
        )),
        _revSeq, LAMBDA(n, SEQUENCE(n,, n, -1)),
        LET(
            chs, _chars(文字列),
            CONCAT(INDEX(chs, _revSeq(ROWS(chs))))
        )
    )
))

【構文】

REVERSE(文字列)

【引数】

引数 内容
文字列 反転したい文字列

【戻り値】

指定の文字列を逆順に反転した文字列を返します。

【使用例】

使用例 結果 コメント
=REVERSE("かるいきびんなこねこ") こねこなんびきいるか

シャッフル

【名前の定義】

  • 名前

    SHUFFLE

  • コメント

    文字列の文字をランダムな順序に並べ替えます

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _notLowSurrogate, LAMBDA(ch, LET(uc, UNICODE(ch), OR(uc < 56320, 57343 < uc))),
        _chars, LAMBDA(s, LET(
            chs, LEFT(MID(文字列, SEQUENCE(LEN(文字列)), 2)),
            FILTER(chs, MAP(chs, _notLowSurrogate))
        )),
        LET(
            chs, _chars(文字列), 
            CONCAT(SORTBY(chs, RANDARRAY(ROWS(chs))))
        )
    )
))

【構文】

SHUFFLE(文字列)

【引数】

引数 内容
文字列 ランダムに並べ替えたい文字列

【戻り値】

指定の文字列の文字をランダムに並べ替えた文字列を返します。

【使用例】

使用例 結果 コメント
=SHUFFLE("フルーツバスケット") トケツバスフーッル
=LEFT(SHUFFLE("ABCDEFGHIJKLNMOPQRSTUVWXYZ),8) TEUJMWCK パスワードなど

循環移動

【名前の定義】

  • 名前

    ROTATE

  • コメント

    文字列の文字を移動文字数で循環的にずらします

  • 参照範囲
=LAMBDA(文字列, 移動文字数, IF(文字列="", "",
    LET(
        _length, LAMBDA(s, SUM(N(1=LEN(LEFT(MID(s, SEQUENCE(LEN(s)), 2)))))),
        LET(
            len, _length(文字列),
            offset, MOD(移動文字数, len),
            RIGHT(文字列, len - offset) & LEFT(文字列, offset)
        )
    )
))

【構文】

ROTATE(文字列)

【引数】

引数 内容
文字列 循環させる文字列
移動文字数 移動したい文字数

【戻り値】

指定の文字列の文字をランダムに並べ替えた文字列を返します。

【説明】

指定の移動循環(左から右)します

移動文字数にマイナスの数値を与えると逆方向(右から左)に循環します

【使用例】

使用例 結果 コメント
=ROTATE("あいうえお", 1) いうえおあ
=ROTATE("あいうえお", 2) うえおあい
=ROTATE("あいうえお", -1) おあいうえ
=ROTATE("   🚙", 1)   🚙 
=ROTATE("   🚙", 2)  🚙  
=ROTATE("   🚙", 3) 🚙   
=ROTATE("   🚙", 4)    🚙

 



スネークケース

【名前の定義】

  • 名前

    SNAKECASE

  • コメント

    英単語をアンダースコア区切りにします。

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            LOWER(SUBSTITUTE(words, " ", "_"))
        )
    )
))

【構文】

SNAKECASE(文字列)

【引数】

引数 内容
文字列 スネークケースに変換する文字列

【戻り値】

指定の文字列をスネークケースに変換した文字列を返します。

【説明】

「スネークケース」とは英単語をスペースの代わりにアンダースコア(_)で区切る形式のことです。

this_is_snake_case

SNAKECASE関数は以下のような変換処理を行います。

  • 英字の単語区切りをアンダースコアにします
  • 英字を全て小文字にします
  • ハイフン(-)をアンダースコアに置き換えます
  • 変換可能な元の形式は以下の通りです
    • スペース区切り
    • キャメルケース
    • パスカルケース
    • ケバブケース
  • 英字以外の文字には影響ありません

【使用例】

使用例 結果
=SNAKECASE("Make me like snakes") make_me_like_snakes

キャメルケース

【名前の定義】

  • 名前

    CAMELCASE

  • コメント

    英単語を大文字区切りにします(先頭文字のみ小文字)。

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        _lowerAt,
            LAMBDA(s, p, REPLACE(s, p, 1, LOWER(MID(s, p, 1)))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            props, PROPER(words),
            _lowerAt(SUBSTITUTE(props, " ", ""), 1)
        )
    )
))

【構文】

CAMELCASE(文字列)

【引数】

引数 内容
文字列 キャメルケースに変換する文字列

【戻り値】

指定の英数字文字列をキャメルケースに変換した文字列を返します。

【説明】

「キャメルケース」とは、英単語の区切りをスペースではなく各単語の先頭を大文字にして区切る形式のことです。 ただし、文字列の先頭の文字は小文字とします。

thisIsCamelCase

CAMELCASE関数は以下のような変換処理を行います。

  • 半角スペース( )、アンダースコア(_)、ハイフン(-)を削除します
  • 各単語の先頭文字のみを大文字とします
  • 文字列の先頭文字は小文字とします
  • 変換可能な元の形式は以下の通りです
    • スペース区切り
    • スネークケース
    • パスカルケース
    • ケバブケース
  • 英字以外の文字には影響ありません

【使用例】

使用例 結果
=CAMELCASE("Hump me like camels") humpMeLikeCamels

パスカルケース

【名前の定義】

  • 名前

    PASCALCASE

  • コメント

    英単語を大文字区切りにします。

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            props, PROPER(words),
            SUBSTITUTE(props, " ", "")
        )
    )
))

【構文】

PASCALCASE(文字列)

【引数】

引数 内容
文字列 パスカルケースに変換する文字列

【戻り値】

指定の英数字文字列をパスカルケースに変換した文字列を返します。

【説明】

「パスカルケース」とは、英単語の区切りをスペースではなく各単語の先頭を大文字にして区切る形式のことです。 キャメルケースとの違いは先頭の文字も大文字となることです。

ThisIsPascalCase

PASCALCASE関数は以下のような変換処理を行います。

  • 半角スペース( )、アンダースコア(_)、ハイフン(-)を削除します
  • 各単語の先頭文字のみを大文字とします
  • 変換可能な元の形式は以下の通りです
    • スペース区切り
    • スネークケース
    • キャメルケース
    • ケバブケース
  • 英字以外の文字には影響ありません

【使用例】

使用例 結果
=CAMELCASE("Think like a reed") ThinkLikeAReed

ケバブケース

【名前の定義】

  • 名前

    KEBABCASE

  • コメント

    英単語をハイフン区切りにします。

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            LOWER(SUBSTITUTE(words, " ", "-"))
        )
    )
))

【構文】

KEBABCASE(文字列)

【引数】

引数 内容
文字列 ケバブケースに変換する文字列

【戻り値】

指定の英数字文字列をケバブケースに変換した文字列を返します。

【説明】

「ケバブケース」とは英単語をスペースの代わりにハイフン(-)で区切る形式のことです。

this-is-kabab-case

KEBABCASE関数は以下のような変換処理を行います。

  • 英字の単語区切りをハイフンにします
  • 英字を全て小文字にします
  • アンダースコア(_)をハイフンに置き換えます
  • 変換可能な元の形式は以下の通りです
    • スペース区切り
    • スネークケース
    • キャメルケース
    • パスカルケース
  • 英字以外の文字には影響ありません

【使用例】

使用例 結果
=KEBABCASE("Skewer me like kebab") skewer-me-like-kebab

AFE ソース

Excel に Advanced Formula Environment アドインを導入されている上級者のために、本記事掲載の全てのユーザー定義関数を一括登録できるソースを用意しました。

以下のソースを AFE の Editor にコピー&ペーストしてください。 関数名の衝突を避けるため、任意の名前で専用の Namespace を用意することをお勧めします。

// 今日を乗り切るExcel研究所 
// 【Excel LAMBDA】文字列操作編(その2)【ユーザー定義関数
// https://www.shegolab.jp/entry/excel-lambda-strings-02

//--------------------------------------------------------------------------
// 分割
//--------------------------------------------------------------------------

/**
 * 区切り文字で横方向セルに分割
 */
 WSPLIT
=LAMBDA(文字列, 区切り, IF(OR(文字列="", 区切り=""), T(文字列),
    IF(ISERROR(FIND(区切り, 文字列)),
         T(文字列),
        LET(
            dlm, UNICHAR(1),
            txt, dlm & SUBSTITUTE(文字列, 区切り, dlm) & dlm,
            cnt, LEN(txt) - LEN(SUBSTITUTE(txt, dlm, "")),
            brs, SCAN(0, SEQUENCE(,cnt),
                LAMBDA(p, i, FIND(dlm, txt, p+1))) + 1,
            rng, MAKEARRAY(2, cnt-1,
                LAMBDA(r, c, INDEX(brs, 1, r-1+c))),
            MAP(INDEX(rng,1), INDEX(rng,2),
                LAMBDA(a, b, MID(txt, a, b-a-1)))
        )
    )
))
;

/**
 * 固定文字数折り返し
 */
WRAP
=LAMBDA(文字列, 文字数, IF(OR(文字列="", NOT(文字数>0)), T(文字列),
    LET(
        _r, LAMBDA(f, LAMBDA(x, f(f, x))),
        _push, LAMBDA(a, e, IF(SEQUENCE(ROWS(a)+1)<ROWS(a)+1, a, e)),
        _tail, LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a)-1)+1,1)),
        LET(
            BR, UNICHAR(10),
            txt, 文字列,
            len, LEN(文字列),
            lineWidth, 文字数,
            breakPositions,
                _r(LAMBDA(f, curr, LET(
                    next, curr + LEN(LEFT(MID(txt, curr, len), lineWidth)), 
                    brk, IFERROR(FIND(BR, txt, curr), len+1),
                    IF(len < next,
                        next,
                        IF(brk <= next,
                            f(f, brk + 1),
                            _push(f(f, next), next)
                        )
                    )
                )))(1),
            IF(AND(len < INDEX(breakPositions, ROWS(breakPositions), 1)),
                T(文字列),
                REDUCE(txt, _tail(breakPositions), 
                    LAMBDA(s, p, REPLACE(s, p, 0, BR)))
            )
        )
    )
))
;
// - 改行を後ろから順に挿入しているロジックに注意
// - _r()
//   - 再帰ループを実現する最も軽量な仕掛け。
//   - LET変数に束縛した関数では再帰呼び出しができない。
//   - LAMBDA関数の仮引数なら再帰呼び出し可能。
//   - _r() は関数が自分自身を引数で持ちまわせるようにする。
//   - Zコンビネータとか、ちょっと、ね。
// - _push()
//   - IFのトリッキーな挙動を使っている。
//     - IF({1,1,0}, {1,2}, {3}) ⇒ {1,2,3}
//     - IF や CHOOSE と配列を組み合わせると面白いことが起こる。
//   - 配列要素を一個追加するのに MAKEARRAY関数はちょっとおっくう。
//   - 近い将来にサポートされる予定の VSTACK関数 や HSTACK関数が出たら
//     そちらに差し替える
// - 再帰でIFSを使うとエラーになる?(要調査)

//--------------------------------------------------------------------------
// 検索・置換
//--------------------------------------------------------------------------

/**
 * パターン抽出
 */ 
CAPTURE
=LAMBDA(文字列, パターン, 最大マッチ, IF(OR(文字列="", パターン=""), T(文字列),
    LET(
        ord, IF(OR(最大マッチ=TRUE, ISOMITTED(最大マッチ)), -1, 1),
        pos, IFERROR(SEARCH(パターン, 文字列), 0),
        IF(pos<1, "", LET(
            lefts, MID(文字列, pos, SEQUENCE(LEN(文字列)-pos+1)),
            XLOOKUP(パターン, lefts, lefts, "", 2, ord)
        ))
        
    )
))
;
// - SEARCH 関数でマッチした部分文字列を得るのは難しい。
// - XLOOKUP 関数の一致モード「2 - ワイルドカード文字との一致」を使うと、
//   全長が一致する文字列を検出することができる。
// 元のテキストが長いと重くなるのがの問題

/**
 * 一括置換
 */
MULTIREPLACE
=LAMBDA(文字列, 検索配列, 置換配列,
    LET(
        _apply, LAMBDA(x, f, f(x)),
        repls, MAP(
            検索配列,
            置換配列,
            LAMBDA(old, new, 
                LAMBDA(str, SUBSTITUTE(str, old, new)))
            ),
        REDUCE(文字列, repls, _apply)
    )
)
;
// - repls は関数の配列


/**
 * 後ろから検索
 */
RFIND
=LAMBDA(検索文字列, 対象, IFS(
    検索文字列 = "", 1,
    対象 = "", FIND(検索文字列, 対象),
    TRUE, LET(
        _reverse, LAMBDA(s, CONCAT(MID(s, SEQUENCE(LEN(s),,LEN(s),-1), 1))),
        rtext, _reverse(対象),
        rfind, _reverse(検索文字列),
        LEN(rtext) - (FIND(rfind, rtext) - 1) - (LEN(rfind) - 1)
    )
))
;
// 引数の順番を逆にしたいが、混乱をさけるためFIND関数に合わせる
// 検索文字列が見つからなかった場合にエラーになるのは使い勝手が悪いが、挙動をFIND関数に合わせる
// FINDに合わせて開始位置も付けたいが、LAMBDAは引数省略で,を省略できないので使い勝手が悪い

//--------------------------------------------------------------------------
// 加工・変換
//--------------------------------------------------------------------------

/**
 * 右削除
 */
RCUT
=LAMBDA(文字列, 文字数, IF(文字列="", "",
    MID(文字列, 1, LEN(文字列) - LEN(RIGHT(文字列, 文字数)))
))
;

/**
 * 左削除
 */
LCUT
=LAMBDA(文字列, 文字数, IF(文字列="", "",
    MID(文字列, LEN(LEFT(文字列, 文字数)), LEN(文字列))
))
;

/**
 * 反転
 */
REVERSE
= LAMBDA(文字列, IF(文字列="", "",
    LET(
        _notLowSurrogate, LAMBDA(ch, LET(uc, UNICODE(ch), OR(uc < 56320, 57343 < uc))),
        _chars, LAMBDA(s, LET(
            chs, LEFT(MID(文字列, SEQUENCE(LEN(文字列)), 2)),
            FILTER(chs, MAP(chs, _notLowSurrogate))
        )),
        _revSeq, LAMBDA(n, SEQUENCE(n,, n, -1)),
        LET(
            chs, _chars(文字列),
            CONCAT(INDEX(chs, _revSeq(ROWS(chs))))
        )
    )
))
;

/**
 * シャッフル
 */
SHUFFLE
= LAMBDA(文字列, IF(文字列="", "",
    LET(
        _notLowSurrogate, LAMBDA(ch, LET(uc, UNICODE(ch), OR(uc < 56320, 57343 < uc))),
        _chars, LAMBDA(s, LET(
            chs, LEFT(MID(文字列, SEQUENCE(LEN(文字列)), 2)),
            FILTER(chs, MAP(chs, _notLowSurrogate))
        )),
        LET(
            chs, _chars(文字列), 
            CONCAT(SORTBY(chs, RANDARRAY(ROWS(chs))))
        )
    )
))
;

/**
 * 循環
 */
ROTATE
=LAMBDA(文字列, 移動文字数, IF(文字列="", "",
    LET(
        _length, LAMBDA(s, SUM(N(1=LEN(LEFT(MID(s, SEQUENCE(LEN(s)), 2)))))),
        LET(
            len, _length(文字列),
            offset, MOD(移動文字数, len),
            RIGHT(文字列, len - offset) & LEFT(文字列, offset)
        )
    )
))
;
// - LEFT関数、RIGHT関数はサロゲートペアを1字として数える
// - MOD関数での負の剰余の結果は正から連続した循環となる
//   =MOD({4,3,2,1,0,-1,-2,-3,-4}, 3) ⇒ 1,0,2,1,0,2,1,0,1

//--------------------------------------------------------------------------
// 命名規則
//--------------------------------------------------------------------------

/**
 * スネークケース
 */
SNAKECASE
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            LOWER(SUBSTITUTE(words, " ", "_"))
        )
    )
))
;

/**
 * キャメルケース
 */
CAMELCASE
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        _lowerAt,
            LAMBDA(s, p, REPLACE(s, p, 1, LOWER(MID(s, p, 1)))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            props, PROPER(words),
            _lowerAt(SUBSTITUTE(props, " ", ""), 1)
        )
    )
))
;

/**
 * パスカルケース
 */
PASCALCASE
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            props, PROPER(words),
            SUBSTITUTE(props, " ", "")
        )
    )
))
;

/**
 * ケバブケース
 */
KEBABCASE
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _chars, LAMBDA(s, MID(s, SEQUENCE(LEN(s)), 1)),
        _replace,
            LAMBDA(s, ds, r, REDUCE(s, ds,
                LAMBDA(ss, d, SUBSTITUTE(ss, d, r)))),
        _isUpper, LAMBDA(ch, NOT(EXACT(LOWER(ch), ch))),
        _isLower, LAMBDA(ch, NOT(EXACT(UPPER(ch), ch))),
        _reverse,
            LAMBDA(a, INDEX(a, SEQUENCE(ROWS(a),, ROWS(a), -1))),
        LET(
            chs, _chars(_replace(文字列, {"_", "-"}, " ")),
            rev, SCAN(,_reverse(chs),
                LAMBDA(next, curr, IFS(
                    AND(_isUpper(curr), _isLower(next)), " " & curr,
                    AND(_isLower(curr), _isUpper(next)), curr & " ", 
                    TRUE, curr
                ))
            ),
            words, TRIM(CONCAT(_reverse(rev))),
            LOWER(SUBSTITUTE(words, " ", "-"))
        )
    )
))
;

//EOF

更新履歴

  • [2022/04/29] RINDEX を追加