こんにちは!
プログラミング未経験文系出身、Elixirの国に迷い込んだ?!見習いアルケミストのaliceと申します。
今回はStringモジュールについて学んだことをまとめます。
目次
1.Stringモジュールで遊んでみたシリーズ① -String.at ~ String.chunk の紹介
2.Stringモジュールで遊んでみたシリーズ② -String.codepoints ~ String.ends_with? の紹介
3.Stringモジュールで遊んでみたシリーズ③ -String.equivalent? ~ String.last の紹介
4.Stringモジュールで遊んでみたシリーズ④ -String.length ~ String.next_grapheme の紹介
5.Stringモジュールで遊んでみたシリーズ⑤ -String.next_grapheme_size ~ String.printable? の紹介(本記事)
6.Stringモジュールで遊んでみたシリーズ⑥ -String.replace ~ String.replace_suffix の紹介
7.Stringモジュールで遊んでみたシリーズ⑦ -String.replace_trailing ~ String.split の紹介
8.Stringモジュールで遊んでみたシリーズ⑧ -String.split_at ~ String.to_charlist の紹介
9.Stringモジュールで遊んでみたシリーズ⑨ -String.to_existing_atom ~ String.trim の紹介
10.Stringモジュールで遊んでみたシリーズ10 -String.trim_leading ~ String.valid? の紹介
目的
Stringモジュールに含まれる関数を触って機能を理解したい
実行環境
Windows 11 + WSL2 + Ubuntu 22.04
Elixir v1.17.3
Erlang v27.0
String.next_grapheme_sizeとは
String.next_codepoint(string)
はstring
内の次の書記素のサイズ(バイト単位)と、残りの文字列のタプルを返します。
例
String.next_grapheme_size("olá")
{1, "lá"}
残りの文字列が存在しない場合
String.next_grapheme_size("o")
{1, ""}
空文字の場合
String.next_grapheme_size("")
nil
string
がUTF-8でエンコードされた文字列ではないバイトシーケンスで始まる場合
string型と同様に、次の書記素のサイズ(バイト単位)と、残りの文字列のタプル(バイナリ型)で返されます。
※下記の\x80
はUTF-8でエンコードされた文字列ではない
string = "\x80\x80hogehoge"
String.next_grapheme_size(string)
{1, <<128, 104, 111, 103, 101, 104, 111, 103, 101>>}
類似の関数との比較
String.normalizeとは
String.normalize(string, form)
はstring
をform
形式でUnicode正規化1します。
無効な Unicode コードポイントはスキップされ、文字列の残りの部分が変換されます。
例
:nfd
form = :nfd
のときは文字を可能な限り細かく分解。2アクセントや修飾記号が独立した部品になります。
下記は目検ではインプットとアウトプットの文字列に違いがあるようには見えません。
converted_string = String.normalize("yêṩ", :nfd)
"yêṩ"
しかし、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
"yêṩ" == converted_string
false
インプットの文字列は5つのコードポイントによって構成されていました。
String.codepoints("yêṩ")
["y", "ê", "s", "̇", "̣"]
アウトプットの文字列はアクセントや修飾記号が独立した部品になっています。
今回の場合は^
(サーカムフレックス記号)が独立し、6つのコードポイントによって構成されています。
したがって、インプットとアウトプットは同じでないことが分かります。
String.codepoints(converted_string)
["y", "e", "̂ ", "s", "̣", "̇"]
:nfc
form = :nfc
のときは文字を可能な限り細かく分解した後、再度合成します。
下記も目検ではインプットとアウトプットの文字列に違いがあるようには見えません。
converted_string = String.normalize("leña", :nfc)
"leña"
しかし、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
"leña" == converted_string
false
インプットの文字列は5つのコードポイントによって構成されていました。
String.codepoints("leña")
["l", "e", "n", "̃ ", "a"]
アウトプットの文字列はアクセントや修飾記号が合成された文字列になっています。
今回の場合は̃
(チルダ記号)が合成され、4つのコードポイントによって構成されています。
したがって、インプットとアウトプットは同じでないことが分かります。
String.codepoints(converted_string)
["l", "e", "ñ", "a"]
:nfkd
form = :nfkd
のときは文字を可能な限り細かく分解。アクセントや修飾記号が独立した部品になります。
但し「互換性」を考慮して文字を変換します。3
つまり、装飾や特別な書式情報を取り除き、プレーンな文字にします。
下記は目検で合字(リガチャ)が分解されたように見えています。
converted_string = String.normalize("fi", :nfkd)
"fi"
さらに、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
"fi" == converted_string
false
インプットの文字列は1つのコードポイントによって構成されていました。
String.codepoints("fi")
["fi"]
アウトプットの文字列はアクセントや修飾記号が独立した部品になっています。
今回の場合は合字が分解され、2つのコードポイントによって構成されています。
したがって、インプットとアウトプットは同じでないことが分かります。
String.codepoints(converted_string)
["f", "i"]
:nfkc
form = :nfkc
のときは文字を可能な限り細かく分解。アクセントや修飾記号が独立した部品になります。
但し「互換性」を考慮して文字を変換します。3
つまり、装飾や特別な書式情報を取り除き、プレーンな文字にします。
さらに、分解後、再度合成します。
下記は目検で合字(リガチャ)が分解されたように見えています。
converted_string = String.normalize("fi", :nfkc)
"fi"
さらに、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
"fi" == converted_string
false
インプットの文字列は1つのコードポイントによって構成されていました。
String.codepoints("fi")
["fi"]
アウトプットの文字列はアクセントや修飾記号が独立した部品になっています。
今回の場合は合字が分解され、2つのコードポイントによって構成されています。
合字はプレーンな文字列ではないため、再度合成しても元の合字には戻りません。
したがって、インプットとアウトプットは同じでないことが分かります。
String.codepoints(converted_string)
["f", "i"]
まとめ
String.normalize(string, form)
のform
ごとの差分をまとめると下記の表の通りです
formの値 | 分解後に再度合成するか | 分解時に互換性を考慮するか |
---|---|---|
:nfd | ✕ | ✕ |
:nfc | 〇 | ✕ |
:nfkd | ✕ | 〇 |
:nfkc | 〇 | 〇 |
String.pad_leadingとは
String.pad_leading(string, count, padding \\ [" "])
はstring
の文字数を数えた上で、count
に満たない場合、string
の先頭から不足分をpadding
で満たします。
例
String.pad_leading("abc", 5)
" abc"
不足分に対しpadding
の一部を使用するだけで満たされる場合。
padding
の先頭から不足分にあたる文字数だけが使われる
String.pad_leading("abc", 4, "12")
"1abc"
不足分に対しpadding
を全て使用してもまだ不足がある場合。
padding
が繰り返し使われる
String.pad_leading("abc", 6, "12")
"121abc"
string
の文字数がcount
よりも多い場合
String.pad_leading("abcdef", 5)
"abcdef"
padding
はリストでもOK
String.pad_leading("abc", 10, ["1", "23", "45"])
"abcdef"
String.pad_trailingとは
String.pad_trailing(string, count, padding \\ [" "])
はstring
の文字数を数えた上で、count
に満たない場合、string
の末尾から不足分をpadding
で満たします。
例
String.pad_trailing("abc", 5)
"abc "
不足分に対しpadding
の一部を使用するだけで満たされる場合。
padding
の先頭から不足分にあたる文字数だけが使われる
String.pad_trailing("abc", 4, "12")
"abc1"
不足分に対しpadding
を全て使用してもまだ不足がある場合。
padding
が繰り返し使われる
String.pad_trailing("abc", 6, "12")
"abc121"
string
の文字数がcount
よりも多い場合
String.pad_trailing("abcdef", 5)
"abcdef"
padding
はリストでもOK
String.pad_trailing("abc", 10, ["1", "23", "45"])
"abcdef"
String.printable?とは
String.printable?(string, character_limit \\ :infinity)
はstring
に対してcharacter_limit
までの文字数が印刷可能であるか否かをチェックします。
例
String.printable?("abc")
true
バイナリ型は印刷不能なのでデフォルトではcharacter_limit = :infinity
のためfalseになります。
String.printable?("abc" <> <<0>>)
false
バイナリ型は印刷不能だがcharacter_limit
で文字数制限を付ければtrueにもなりえます。
String.printable?("abc" <> <<0>>, 2)
true
~Elixirの国のご案内~
↓Elixirって何ぞや?と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます
↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ!私もエンジニア未経験から学習中です。
↓We Are The Alchemists, my friends!4
Elixirコミュニティは本当に優しくて温かい人たちばかり!
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。5
-
https://ja.wikipedia.org/wiki/Unicode%E6%AD%A3%E8%A6%8F%E5%8C%96 ↩
-
文字を可能な限り細かく分解することを「正準等価」または「正規等価」というそうです。https://ja.wikipedia.org/wiki/Unicode%E3%81%AE%E7%AD%89%E4%BE%A1%E6%80%A7#%E6%AD%A3%E6%BA%96%E7%AD%89%E4%BE%A1 ↩
-
@torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。 ↩