シーゴの Excel 研究室

タイトル変更しました (旧称:今日を乗り切るExcel研究所)

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

文字列操作関数集その1

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

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

www.shegolab.jp

本記事では文字列操作関連のツールとしてよく使いそうな、文字、行、比較、判定、整形等の機能を主に集めました。

 

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

文字数

【名前の定義】

  • 名前

    CHARCOUNT

  • コメント

    文字列の文字数を取得します(拡張漢字や絵文字に対応)

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", 0,
    SUM(--(1=LEN(LEFT(MID(文字列, SEQUENCE(LEN(文字列)), 2)))))
))

【構文】

CHARCOUNT(文字列)

【引数】

引数 内容
文字列 文字数を得たい文字列

【戻り値】

テキストに含まれる拡張漢字や絵文字を1文字として数えたときの文字数を整数で返します。

【説明】

通常、文字数を得るには LEN関数を使いますが、一部の拡張漢字や絵文字などには、LEN関数で2文字として数えられてしまうもの (サロゲートペア) があります。 CHARCOUNT 関数はそれらを合わせて1字として数えたうえでの文字数を返します。

【使用例】

使用例 結果 コメント
=CHARCOUNT("abcde") 5
=CHARCOUNT("あいうえお") 5
=CHARCOUNT("😀") 1 LEN 関数では2になる文字(サロゲートペア)

【解説】サロゲートペア

全ての文字には一つずつ固有の番号が振られていて、それを文字コードといいます。 コンピュータ-はテキストデータを文字コードの並び(文字列)として扱います。
 
文字の中にはこの番号が大きすぎて、文字列に使う1文字分のデータの桁枠に収まり切らず、2文字分の桁枠を占有するものがあります。
 
このような文字データは専門的にサロゲートペア(代用対)と呼ばれます。
 
サロゲートペアになってしまうような文字には、一部の拡張漢字と絵文字が含まれます。
 
例えば、𠮷野屋の「𠮷(つちよし)」や「𩸽(ほっけ)」、SNS でよく使う「😅」や「🍣」はサロゲートペアになります。
 
サロゲートペアになる文字は、LEN 関数や MID関数など Excel の関数では2文字として扱われてしまうため、データ処理によっては不都合となる場合があります。
 
漢字とは言っても基本的に、めったに使われない難しい漢字や、人名用に微妙な違いにこだわった漢字ばかりで、これまではあまり見かけることはありませんでしたが、 絵文字が普及したことによって無視できなくなってきています。

 



 

文字取得

【名前の定義】

  • 名前

    CHARAT

  • コメント

    指定位置の文字を取得します(拡張漢字や絵文字に対応)

  • 参照範囲
=LAMBDA(文字列, 位置, IF(文字列="", "",
    LET(
        pos, IF(位置<=0, 位置,
            REDUCE(,SEQUENCE(位置),
                LAMBDA(p, n, p + LEN(LEFT(MID(文字列, p, 2)))
        ))),
        LEFT(MID(文字列, pos, 2))
    )
))

【構文】

CHARAT(文字列, 位置)

【引数】

引数 内容
文字列 取得元の文字列
位置 先頭からの文字数による位置

【返値】

文字列中の拡張漢字や絵文字を考慮した位置にある文字を1文字の文字列として返します。

位置が文字列の文字数より大きかった場合は空文字列("")を返します。

位置が0以下であった場合はエラーとなります。

【説明】

通常、文字列中にある文字を得るには MID関数を使いますが、一部の拡張漢字や絵文字などで サロゲートペア が含まれている場合に、位置が合わなかったり文字が壊れて表示されないことがあります。 CHARAT 関数はサロゲートペアも1文字として数えた位置をもとに文字を取得します。

【使用例】

文字分割

【名前の定義】

  • 名前

    CHARS

  • コメント

    文字列を文字の配列にします

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

【構文】

CHARS(文字列)

【引数】

引数 内容
文字列 文字配列にしたいテキスト

【返値】

文字列を文字に分解した横方向の配列を返します。

【説明】

指定の文字列を1文字ずつに分割した文字の配列を返します。 サロゲートペア となっている文字は壊されることなく1字として扱われます。

結果の配列は横方向の配列となり、動的にスピルします。 縦方向の配列にしたい場合にはさらに TRANSPOSE関数を使ってください。

【使用例】

【関連記事】

www.shegolab.jp

行数

【名前の定義】

  • 名前

    LINECOUNT

  • コメント

    改行区切りによる行数を取得します

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", 0,
    LEN(文字列) - LEN(SUBSTITUTE(文字列, CHAR(10), "")) + 1
))

【構文】

LINECOUNT(文字列)

【引数】

引数 内容
文字列 行数を取得したいテキスト

【返値】

文字列の行数を整数で返します。

【説明】

テキスト中の改行文字(U+000a LF)で区切られている部分文字列を行として数えます。

【使用例】

行分割

【名前の定義】

  • 名前

    LINES

  • コメント

    文字列を行の配列に変換します

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _BR, CHAR(10),
        txt, _BR & 文字列 & _BR,
        brCount, LEN(txt) - LEN(SUBSTITUTE(txt, _BR, "")),
        brPositions, SCAN(0, SEQUENCE(brCount),
            LAMBDA(p, i, FIND(_BR, txt, p+1))) + 1,
        lineRanges, MAKEARRAY(brCount -1, 2,
            LAMBDA(r, c, INDEX(brPositions, r-1+c))),
        MAP(INDEX(lineRanges,,1), INDEX(lineRanges,,2),
            LAMBDA(a, b, MID(txt, a, b-a-1)))
    )
))

【構文】

LINES(文字列)

【引数】

引数 内容
文字列 行の配列にしたいテキスト

【返値】

文字列を行に分解した縦方向の配列を返します。

【説明】

指定のテキストを改行文字(U+000a LF)で分割した行文字列の配列を返します。 結果は複数行範囲にスピルします。

【使用例】

あいまい比較

【名前の定義】

  • 名前

    LIKE

  • コメント

    ワイルドカードを使った文字列で比較します

  • 参照範囲
=LAMBDA(文字列, パターン,
    XLOOKUP(パターン, 文字列, TRUE, FALSE, 2)
)

【構文】

LIKE(文字列, パターン)

【引数】

引数 内容
文字列 行の配列にしたいテキスト

【返値】

文字列がパターンマッチした場合に TRUE を返します。

【説明】

文字列の比較に、厳密な一致ではなく、「*」や「?」といったワイルドカードを使ったパターンで判定することができます。

また英字の大文字と小文字も区別せずに一致します。

Excel で使えるワイルドカードについては以下を参照してください。

【使用例】

使用例 結果 コメント
=LIKE("ABC", "abc") TRUE 半角英字・全角英字は大文字小文字を区別しません
=LIKE("123-4567", "???-????") TRUE
=LIKE("D:\Data\Book1.xlsx", "D:*xlsx") TRUE

指定文字列を含むかの判定

【名前の定義】

  • 名前

    CONTAINS

  • コメント

    検索文字列が対象文字列に含まれているかどうかを判定します。

  • 参照範囲
=LAMBDA(対象文字列, 検索文字列,
    ISNUMBER(FIND(検索文字列, 対象文字列))
)

【構文】

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

【引数】

引数 内容
対象文字列 検索文字列を含むか判定したい文字列
検索文字列 検索したい文字列

【返値】

対象文字列中に検索文字列が含まれる場合にTRUEを返します。

【説明】

【使用例】

使用例 結果 コメント
=CONTAINS("abcde", "b") TRUE

下位サロゲートの判定

【名前の定義】

  • 名前

    ISLOWSURROGATE

  • コメント

    文字が下位サロゲートかどうかを判定します。

  • 参照範囲
=LAMBDA(文字, IF(文字="", FALSE,
    LET(
        ucs, IFERROR(UNICODE(MID(文字, 1, 1)), 0),
        MAP(ucs, LAMBDA(uc, AND(56320 <= uc, uc <= 57343)))
    )
))

【構文】

ISLOWSURROGATE(文字)

【引数】

引数 内容
文字 下位サロゲートかを判定したい文字

【返値】

指定の文字列の先頭文字が下位サロゲートなら TRUE を返します。

【説明】

MID関数などを使って取得した1文字が、一部の拡張漢字や絵文字のような サロゲートペア を構成する2文字のうち、2文字目に相当するの下位 16 ビットかどうかを判定します。

【使用例】

使用例 結果 コメント
=ISLOWSURROGATE(MID("あ🍣",1 ,1)) FALSE
=ISLOWSURROGATE(MID("あ🍣",2 ,1)) FALSE
=ISLOWSURROGATE(MID("あ🍣",3 ,1)) TRUE

先頭の空白文字の除去

【名前の定義】

  • 名前

    LTRIM

【注意】これは再帰関数です。関数名を上記から変更する際はソース中の再帰呼び出し箇所も合わせて修正するようにしてください。

  • コメント

    文字列の先頭にある空白文字を削除します

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _SP, CONCAT(UNICHAR({9, 10, 11, 13, 32, 8203, 12288})),
        _notSpace, LAMBDA(ch, ISERROR(FIND(ch, _SP))),
        IF(_notSpace(LEFT(文字列)),
            文字列,
            LTRIM(MID(文字列, 2, LEN(文字列)))
        )
    )
))

【構文】

LTRIM(文字列)

【引数】

引数 内容
文字列 先頭の空白を除去したいテキスト

【返値】

もし文字列の先頭に1個以上の空白文字があれば、それらが全て除去された文字列を返します。

【説明】

テキストの先頭(左側)にある空白文字を全て削除します。 除去対象としている空白文字は以下の通りです。

  • 半角スペース
  • 全角スペース
  • ゼロ幅スペース1
  • 改行(CR:復帰)
  • 改行(LF:行送り)
  • タブ
  • 垂直タブ

【使用例】

末尾の空白文字の除去

【名前の定義】

  • 名前

    RTRIM

【注意】これは再帰関数です。関数名を上記から変更する際はソース中の再帰呼び出し箇所も合わせて修正するようにしてください。

  • コメント

    文字列の末尾にある空白文字を削除します

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _SP, CONCAT(UNICHAR({32, 10, 12288, 13, 9, 8203})),
        _notSpace, LAMBDA(ch, ISERROR(FIND(ch, _SP))),
        IF(_notSpace(RIGHT(文字列)),
            文字列,
            RTRIM(LEFT(文字列, LEN(文字列) -1))
        )
    )
))

【構文】

RTRIM(文字列)

【引数】

引数 内容
文字列 先頭の空白を除去したいテキスト

【返値】

もし文字列の末尾に1個以上の空白文字があれば、それらが全て除去された文字列を返します。

【説明】

テキストの先頭(左側)にある空白文字を全て削除します。 除去対象としている空白文字はLTRIM関数と同様です。

【使用例】

左埋め補填

【名前の定義】

  • 名前

    LPAD

  • コメント
  • 参照範囲
=LAMBDA(文字列, 文字数, 左埋め,
    LET(
        txt, LEFT(文字列, 文字数),
        tmp, LEFT(txt & REPT(左埋め, 文字数), 文字数),
        MID(tmp, LEN(txt)+1, LEN(tmp)) & txt
    )
)

【構文】

LPAD(文字列, 文字数, 左埋め)

【引数】

引数 内容
文字列 切り詰め対象のテキスト
文字数 最終的な文字数
左埋め 左側に不足分を埋める文字(列)

【返値】

指定文字数で切り詰めた文字列を返します。文字列の長さが足りない場合は左埋め文字で左側(先頭)を補填します。

【説明】

テキストを指定の文字数で切り詰めます。 テキストの長さが指定の文字数より短かい場合、不足分を左側(先頭側)に補填文字で埋めます。 サロゲートペアは1文字として数えられます。

【使用例】

右埋め補填

【名前の定義】

  • 名前

    RPAD

  • コメント

    右詰めで指定の文字を補填します

  • 参照範囲
=LAMBDA(文字列, 文字数, 右埋め,
    LEFT(文字列 & REPT(右埋め, 文字数), 文字数)
)

【構文】

RPAD(文字列, 文字数, 左埋め)

【引数】

引数 内容
文字列 切り詰め対象のテキスト
文字数 最終的な文字数
右埋め 右側に不足分を埋める文字(列)

【返値】

指定文字数で切り詰めた文字列を返します。文字列の長さが足りない場合は右埋め文字で右側(末尾)を補填します。

【説明】

文字列を指定の文字数で切り詰めます。 文字列の長さが指定の文字数より短かい場合、不足分を右側(末尾側)に補填文字で埋めます。 サロゲートペアは1文字として数えられます。

【使用例】

 



濁音・半濁音の正規化

【名前の定義】

  • 名前

    KANANORM

【注意】これは再帰関数です。関数名を上記から変更する際はソース中の再帰呼び出し箇所も合わせて修正するようにしてください。

  • コメント

    濁点・半濁点の分離を解消します。

  • 参照範囲
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _toDakuon, LAMBDA(ch, LET(
            basekanas, "かきくけこさしすせそたちつてとはひふへほカキクケコサシスセソタチツテトハヒフヘホうゝウワヰヱヲヽ",
            dakuons,   "がぎぐげござじずぜぞだぢづでどばびぶべぼガギグゲゴザジズゼゾダヂヅデドバビブベボゔゞヴヷヸヹヺヾ",
            p, FIND(LEFT(ch), basekanas),
            IF(ISNUMBER(p), MID(dakuons, p, 1), ch)
        )),
        _toHandakuon, LAMBDA(ch, LET(
            basekanas,  "はひふへほハヒフヘホ",
            handakuons, "ぱぴぷぺぽパピプペポ",
            p, FIND(LEFT(ch), basekanas),
            IF(ISNUMBER(p), MID(handakuons, p, 1), ch)
        )),
        _l, LAMBDA(p, LEFT(文字列, p-2)),
        _m, LAMBDA(p, MID(文字列, p-1, 2)),
        _r, LAMBDA(p, RIGHT(文字列, LEN(文字列) - p)),
        dakuten, IFERROR(FIND(UNICHAR(12441), 文字列), LEN(文字列)+1),
        handakuten, IFERROR(FIND(UNICHAR(12442), 文字列), LEN(文字列)+1),
        IFS(
            dakuten < handakuten,
                _l(dakuten) & _toDakuon(_m(dakuten)) & KANANORM(_r(dakuten)),
            dakuten > handakuten,
                _l(handakuten) & _toHandakuon(_m(handakuten)) & KANANORM(_r(handakuten)),
            dakuten = handakuten,
                文字列
        )
    )
))

【構文】

KANANORM(文字列)

【引数】

引数 内容
文字列 濁点・半濁点の分離を解消したいしたい文字列

【返値】

濁音・半濁音の結合文字列を合成済み文字に変換した文字列を返します。

【説明】

濁点・半濁点が分離された仮名の結合文字列を、濁音・半濁音の合成済み文字に正規化(正規合成)します。

「か」+「゛」 ⇒「が」
「ハ」+「゜」 ⇒「パ」

鼻濁音やアイヌ語表記に用いられる半濁点(「か゚」など)はそのまま温存されます。

上記関数は再帰関数として実装されていますが、再帰関数を多用すると Excel が少し不安定になるような気がしています。 念のため再帰呼び出しを使わない非再帰関数版の同実装も用意しましたので気になる方は試してみてください。 実装は後の節の AFE 用ソースにコメントアウトで追記してあります。

【使用例】

【解説】結合文字列

macOS を使って作成されたというテキストデータをもらって、比較や並べ替えで正しく処理されないトラブルを経験した人がいるかもしれません。 特にテキストに「が」や「パ」といった濁音・半濁音の仮名が含まれるときに起こりがちです。
 
そのときの濁音・半濁音の文字を Excel の LEN関数で調べると、1文字ではなく2文字として数えられているのが分かります。
 
これは濁音・半濁音の文字コードが、仮名本体(「か」、「ハ」)と濁点・半濁点(「゛」、「゜」)で別々に分けられ、2字ひと組を合成して1字として表現されているためです。
 
このような文字を結合文字列といいます。見た目は通常の文字と区別できず、テキストをデータとして扱う際にトラブルの原因となります。
これはサロゲートペアとはまた別の問題です。  
結合文字列にもいろいろあるのですが、上記 KANANORM関数はとりあえず日本語仮名の濁音・半濁音の結合文字列を1文字に変換(正規化)するものです。

AFE 用ソース

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

以下のソースをAFEの Editor にコピー&ペーストしてください。適当な名前で専用の Namespace 用意することをお勧めします。

// シーゴのExcel研究室
// 【Excel LAMBDA】文字列操作編(その1)【ユーザー定義関数
// https://www.shegolab.jp/entry/excel-lambda-strings-01

//--------------------------------------------------------------------------
// 文字
//--------------------------------------------------------------------------

/**
 * コードポイント文字数
 */
// 【非再帰実装版】
CHARCOUNT
=LAMBDA(文字列, IF(文字列="", 0,
    SUM(--(1=LEN(LEFT(MID(文字列, SEQUENCE(LEN(文字列)), 2)))))
))
;
// - MID関数はサロゲートペアを関知せず、別々の文字として扱う
//    長さ1で切り出すと文字が壊れてしまう
// - LEFT関数はサロゲートペアを正しく1文字として扱う
// - LEFT関数は単独の下位サロゲートを1文字として扱う
// - LEN関数はサロゲートペアを2文字として数える
// - 論理値を数値に変換するとTRUE⇒1, FALSE⇒0 になる
//    数値に変換するにはNUMBER関数、1を掛ける、0を足す、マイナス(-)を2回かけるなど
// - 文字列を文字の配列に展開してから、サロゲートペア(長さ2の文字)の分を除いた合計を求める
//   ⇒ サロゲートペアとは別に下位サロゲートのみも要素として含まれているので
//      長さ2のサロゲートペアを差し引けば結果的に文字数となるはず

/*
// 【再帰実装版】
CHARCOUNT
=LAMBDA(文字列, IF(文字列="", 0,
    CHARCOUNT(MID(文字列, LEN(LEFT(文字列)) + 1, LEN(文字列))) + 1 
))
;
// - 再帰呼び出しにすると解りやすいけど、文字列がコピーされるコストが気になる
*/

/**
 * コードポイント文字取得
 */
CHARAT
=LAMBDA(文字列, 位置, IF(文字列="", "",
    LET(
        pos, IF(位置<=0, 位置,
            REDUCE(,SEQUENCE(位置),
                LAMBDA(p, n, p + LEN(LEFT(MID(文字列, p, 2)))
        ))),
        LEFT(MID(文字列, pos, 2))
    )
))
;
// - REDUCE関数は第1引数を省略されると、配列の先頭が初期値になる
// - 範囲外を指定されたときの挙動はMID関数に合わせた
//    位置<=0     ⇒ #VALUE!
//    位置> 文字数 ⇒ ""

/**
 * 文字の配列化
 */
CHARS
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _notLowSurrogate,
            LAMBDA(ch, LET(uc, UNICODE(ch), OR(uc < 56320, 57343 < uc))),
        chs, LEFT(MID(文字列, SEQUENCE(, LEN(文字列)), 2)),
        FILTER(chs, MAP(chs, _notLowSurrogate))
    )
))
;
// - 下位サロゲート領域 OxDC001(56320)~0xDFFF(57343)  
//   https://ja.wikipedia.org/wiki/Unicode#%E6%8B%A1%E5%BC%B5%E9%A0%98%E5%9F%9F
// - _notLowSurrogateにはOR関数があるので引数に配列を渡すことができず、MAP関数を介す必要がある。


//--------------------------------------------------------------------------
// 行
//--------------------------------------------------------------------------

/**
 * 行数
 */
LINECOUNT
=LAMBDA(文字列, IF(文字列="", 0,
    LEN(文字列) - LEN(SUBSTITUTE(文字列, CHAR(10), "")) + 1
))
;
// - セル内改行の改行文字は U+000a(10) LF のみ

/**
 * 行の配列化
 */
LINES
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _BR, CHAR(10),
        txt, _BR & 文字列 & _BR,
        brCount, LEN(txt) - LEN(SUBSTITUTE(txt, _BR, "")),
        brPositions, SCAN(0, SEQUENCE(brCount),
            LAMBDA(p, i, FIND(_BR, txt, p+1))) + 1,
        lineRanges, MAKEARRAY(brCount -1, 2,
            LAMBDA(r, c, INDEX(brPositions, r-1+c))),
        MAP(INDEX(lineRanges,,1), INDEX(lineRanges,,2),
            LAMBDA(a, b, MID(txt, a, b-a-1)))
    )
))
;

//--------------------------------------------------------------------------
// 比較・判定
//--------------------------------------------------------------------------

/**
 * あいまい比較
 */
LIKE
=LAMBDA(文字列, パターン,
    XLOOKUP(パターン, 文字列, TRUE, FALSE, 2)
)
;
// 文字列やパターンに配列を渡したときどうなれば使い勝手が良いか要検討。


/**
 * 包含判定
 */
CONTAINS
=LAMBDA(対象文字列, 検索文字列,
    ISNUMBER(FIND(検索文字列, 対象文字列))
)
;

/**
 * 下位サロゲート判定
 */
ISLOWSURROGATE
=LAMBDA(文字, IF(文字="", FALSE,
    LET(
        ucs, IFERROR(UNICODE(MID(文字, 1, 1)), 0),
        MAP(ucs, LAMBDA(uc, AND(56320 <= uc, uc <= 57343)))
    )
))
;
// - 下位サロゲート領域 OxDC001(56320)~0xDFFF(57343)  
//   https://ja.wikipedia.org/wiki/Unicode#%E6%8B%A1%E5%BC%B5%E9%A0%98%E5%9F%9F
// - 上位サロゲートの方を単独で判定するのは難しい


//--------------------------------------------------------------------------
// 整形
//--------------------------------------------------------------------------

/**
 * 先頭空白除去【再帰実装】
 */
LTRIM
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _SPSET, CONCAT(UNICHAR({9, 10, 11, 13, 32, 8203, 12288})),
        _notSpace, LAMBDA(ch, ISERROR(FIND(ch, _SPSET))),
        IF(_notSpace(LEFT(文字列)),
            文字列,
            LTRIM(MID(文字列, 2, LEN(文字列)))
        )
    )
))
;
// - 除去対象
//   U+0009(9)  HT 水平TAB
//   U+000a(10) LF 改行(行送り)
//   U+000b(11) VT 垂直TAB
//   U+000d(13) CR 改行(復帰)
//   U+0020(32) SP スペース
//   U+200b(8203) ZWSP ゼロ幅スペース ← A5:SQL Mk-2 対応
//   U+3000(12288) 全角スペース

/**
 * 末尾空白削除【再帰実装】
 */
RTRIM
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _SP, CONCAT(UNICHAR({9, 10, 11, 13, 32, 8203, 12288})),
        _notSpace, LAMBDA(ch, ISERROR(FIND(ch, _SP))),
        IF(_notSpace(RIGHT(文字列)),
            文字列,
            RTRIM(LEFT(文字列, LEN(文字列) -1))
        )
    )
))
;

/**
 * 左埋め補填
 */
LPAD
=LAMBDA(文字列, 文字数, 左埋め,
    LET(
        txt, LEFT(文字列, 文字数),
        tmp, LEFT(txt & REPT(左埋め, 文字数), 文字数),
        MID(tmp, LEN(txt)+1, LEN(tmp)) & txt
    )
)
;

/**
 * 右埋め補填
 */
RPAD
=LAMBDA(文字列, 文字数, 右埋め,
    LEFT(文字列 & REPT(右埋め, 文字数), 文字数)
)
;

/**
 * 濁音文字・半濁音文字の正規合成変換
 */
//【再帰実装版】
KANANORM
=LAMBDA(文字列, IF(文字列="", "",
    LET(
        _toDakuon, LAMBDA(ch, LET(
            basekanas, "かきくけこさしすせそたちつてとはひふへほカキクケコサシスセソタチツテトハヒフヘホうゝウワヰヱヲヽ",
            dakuons,   "がぎぐげござじずぜぞだぢづでどばびぶべぼガギグゲゴザジズゼゾダヂヅデドバビブベボゔゞヴヷヸヹヺヾ",
            p, FIND(LEFT(ch), basekanas),
            IF(ISNUMBER(p), MID(dakuons, p, 1), ch)
        )),
        _toHandakuon, LAMBDA(ch, LET(
            basekanas,  "はひふへほハヒフヘホ",
            handakuons, "ぱぴぷぺぽパピプペポ",
            p, FIND(LEFT(ch), basekanas),
            IF(ISNUMBER(p), MID(handakuons, p, 1), ch)
        )),
        _l, LAMBDA(p, LEFT(文字列, p-2)),
        _m, LAMBDA(p, MID(文字列, p-1, 2)),
        _r, LAMBDA(p, RIGHT(文字列, LEN(文字列) - p)),
        dakuten, IFERROR(FIND(UNICHAR(12441), 文字列), LEN(文字列)+1),
        handakuten, IFERROR(FIND(UNICHAR(12442), 文字列), LEN(文字列)+1),
        IFS(
            dakuten < handakuten,
                _l(dakuten) & _toDakuon(_m(dakuten)) & KANANORM(_r(dakuten)),
            dakuten > handakuten,
                _l(handakuten) & _toHandakuon(_m(handakuten)) & KANANORM(_r(handakuten)),
            dakuten = handakuten,
                文字列
        )
    )
))
;
// - LEFT関数は結合文字列を分解して2文字として扱う。

/*
//【非再帰実装版】
KANANORM
=LAMBDA(文字列, IF(文字列="", "",
    LET(  
        _offset, LAMBDA(ch, offset, UNICHAR(UNICODE(ch) + offset)),
        _contains, LAMBDA(ch, s, ISNUMBER(FIND(ch, s))),
        _tr, LAMBDA(ch, a, b, IFERROR(MID(b, FIND(ch,a), 1), ch)),
        DAKUTEN, UNICHAR(12441),
        HANDAKUTEN, UNICHAR(12442),
        EXKANAS, "うゝウワヰヱヲヽ",
        EXDAKUONS, "ゔゞヴヷヸヹヺヾ",
        bigram, MAKEARRAY(LEN(文字列), 2,
            LAMBDA(r, c, MID(文字列, r+c-1, 1))),
        chs, MAP(
            INDEX(bigram,,1),
            INDEX(bigram,,2),
            LAMBDA(a, b, IFS(
                b = DAKUTEN, IFS(
                    AND("カ" <= a, a < "ナ"), _offset(a, 1),
                    AND("ハ" <= a, a < "マ"), _offset(a, 1),
                    _contains(a, EXKANAS), _tr(a, EXKANAS, EXDAKUONS),
                    TRUE, a & b
                ),
                b = HANDAKUTEN, IFS(
                    AND("ハ" <= a, a < "マ"), _offset(a, 2),
                    TRUE, a & b
                ),
                a = DAKUTEN, "",
                a = HANDAKUTEN, "",
                TRUE, a
            ))
        ),
        CONCAT(chs)
    )
))
;
*/
// - MID関数は結合文字列を分解して2文字として扱う。
// - 比較演算子(<,<=,>,>=)は平仮名と片仮名を区別せずに辞書順(Unocode順ではなく)で評価する
// - 同じ音のでは片仮名が平仮名より優先(昇順で上位)となる
// - ちなみに等号と不等号(=, <>)平仮名と片仮名を区別する
// - 鼻濁音やアイヌ語表記で見るような半濁点を持つ仮名は Unicode に定義されず合成文字で表現される
//  上記関数はそれらを温存する

// EOF

更新履歴

  • [2022/04/23] ソースのコピーアイコン追加

  1. ゼロ幅スペース(U+200B)とは空白文字の一種ですが、文字幅が0なので、見かけ上、文字間に隙間(空白)を与えません。通常は入力されることはないのですが、A5:SQL Mk-2 というポピュラーなデーターベースクライアントソフトで Excel にデータを出力したときに、ゼロ幅スペースが入ることがあるようなのであえて処理対象に入れておきます。