6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2021年12月10日 JavaScript (JS Primer) 文字列とUnicode

CodePointごとに扱うもの

  • CodePointを名前に含むメソッド
  • u(Unicode)フラグが有効化されている正規表現
  • 文字列のIteratorを扱うもの(Destructuring、for...of、Array.fromメソッドなど)

正規表現の.とUnicode

uフラグをつけた正規表現は、文字列をCode Pointが順番に並んだものとして扱います。 そうすることで、Code Unitで認識出来ない文字列をCode Pointを参照することで認識出来るようになります。

以下はCode Unitは𩸽という文字を認識出来ずに文字化けする例です。(例では出力されていた場合の結果を記載してありますが、実際に出力すると文字化けします)

const [all, fish] = "𩸽のひらき".match(/(.)のひらき/);
console.log(all); // => "\ude3dのひらき"
console.log(fish); // => "\ude3d"
  • キャプチャリングを使用しています。
  • 配列の1つ目の要素に「マッチした全体の文字列」, 2つ目の要素に「キャプチャされた文字列」, (ここでいう(.)という正規表現パターンにマッチした文字列)それぞれの文字列が代入されます。
  • 正規表現の . はなんでもいい一文字を表します。

サロゲートペアをCodePointで返すためにuフラグをつけます。

const [all, fish] = "𩸽のひらき".match(/(.)のひらき/u);
console.log(all); // => "𩸽のひらき"
console.log(fish); // => "𩸽"

任意の1文字にマッチする.が𩸽という文字(Code Point)にマッチします。

基本的には正規表現にuフラグをつけて問題となるケースは少ないはずです。 なぜなら、サロゲートペアの片方だけにマッチしたい正規表現を書くケースはまれであるためです。

つまり基本的にuフラグをつけておけばいいです。

要点

  • 文字列はCode Unitが並んだものとして認識されています。
  • 1つのCode Unitで認識出来ない文字(サロゲートペア)を返すにはCode Pointで認識する必要があります。
  • CodePointで返すためには正規表現パターンにuフラグをつけます。

Code Pointの数を数える

  • 文字の個数を数える場合、lengthメソッドを使うとCode Unitの個数を返すため、サロゲートペアの文字は2個と認識されるため、視覚的な文字数を数えられないことがあります。

  • JSにはCodePointを数えるメソッドは用意されていません

  • 文字列をCode Pointごとに区切った配列へ変換して、配列の長さを数えるのが簡潔です

fromメソッドを使って文字列を一文字ずつで区切った配列にしCodePointの個数を数えています。

// Code Pointごとの配列にする
// Array.fromメソッドはIteratorを配列にする
const codePoints = Array.from("リンゴ🍎");
console.log(codePoints); // => ["リ", "ン", "ゴ", "🍎"]
// Code Pointの個数を数える
console.log(codePoints.length); // => 4

ただ、CodePointには制御文字などの特殊文字も定義されているため、視覚的な文字数を得られない場合があります。 特殊文字を含むが視覚的な文字だけを得たい場合は

文字として数えたくないものは無視するなど、視覚的な文字列の長さを数えるにはさらなる工夫が必要になります。 残念ながら、ビルトインメソッドにはこれらを簡単に扱う方法は用意されていません。

Code Pointごとに反復処理をする

次のコードでは、文字列中に登場する🍎の個数を数えています。

// 指定した`codePoint`の個数を数える
function countOfCodePoints(str, codePoint) {
    return Array.from(str).filter(item => {
        return item === codePoint;
    }).length;
}
console.log(countOfCodePoints("🍎🍇🍎🥕🍒", "🍎")); // => 2
  • strには第一引数の絵文字群("🍎🍇🍎🥕🍒")、itemに絵文字群の要素が入っています。
  • filterメソッドはitemの絵文字要素をひとつずつ取り出して関数に定義したテスト(return item === codePoint;)に合格した「🍎だけの配列」を返します。
  • 🍎だけの配列の長さを返します。

Array#filterは配列の要素を順番にコールバック関数へ渡し、コールバック関数がtrueを返した要素だけを集めた新しい配列を返す非破壊的なメソッドです。 配列から不要な要素を取り除いた配列を作成したい場合に利用します。

以下はfor...ofを用いた例です。反復処理も文字列をCode Pointごとに扱えます。

// 指定した`codePoint`の個数を数える
function countOfCodePoints(str, codePoint) {
    let count = 0;
    for (const item of str) {
        if (item === codePoint) {
            count++;
        }
    }
    return count;
}
console.log(countOfCodePoints("🍎🍇🍎🥕🍒", "🍎")); // => 2
  • strには第一引数の絵文字群("🍎🍇🍎🥕🍒")、itemに絵文字群の要素が入っています。
  • 要素とりんごが合致する度にcountの数値が増えます。
  • 返り値はcountの値です。

上記の流れでCode Pointの個数を数えています。

参考

サルにもわかる正規表現入門
JS primer CodePointを扱う