Stringモジュールで遊んでみたシリーズ⑤ -String.next_grapheme_size ~ String.printable? の紹介

こんにちは！
プログラミング未経験文系出身、Elixirの国に迷い込んだ？！見習いアルケミストのaliceと申します。
今回はStringモジュールについて学んだことをまとめます。

# 目次
1.[Stringモジュールで遊んでみたシリーズ① -String.at ~ String.chunk の紹介](https://qiita.com/Alicesky2127/items/47fffc38711d7369beee)
2.[Stringモジュールで遊んでみたシリーズ② -String.codepoints ~ String.ends_with? の紹介](https://qiita.com/Alicesky2127/items/5db34c69993d35b8f3a7)
3.[Stringモジュールで遊んでみたシリーズ③ -String.equivalent? ~ String.last の紹介](https://qiita.com/Alicesky2127/items/37aa8e62e8e1d5fef3b6)
4.[Stringモジュールで遊んでみたシリーズ④ -String.length ~ String.next_grapheme の紹介](https://qiita.com/Alicesky2127/items/e3fa2092a67cb9f6ec9c)
5.Stringモジュールで遊んでみたシリーズ⑤ -String.next_grapheme_size ~ String.printable? の紹介(本記事)
6.[Stringモジュールで遊んでみたシリーズ⑥ -String.replace ~ String.replace_suffix の紹介](https://qiita.com/Alicesky2127/items/94a77afe270587ed21a9)
7.[Stringモジュールで遊んでみたシリーズ⑦ -String.replace_trailing ~ String.split の紹介](https://qiita.com/Alicesky2127/items/1a8e9c92fa758d0b1337)
8.[Stringモジュールで遊んでみたシリーズ⑧ -String.split_at ~ String.to_charlist の紹介](https://qiita.com/Alicesky2127/items/40e74cd622653796df5c)
9.[Stringモジュールで遊んでみたシリーズ⑨ -String.to_existing_atom ~ String.trim の紹介](https://qiita.com/Alicesky2127/items/9bf2b1effc3b68819ac2)
10.[Stringモジュールで遊んでみたシリーズ10 -String.trim_leading ~ String.valid? の紹介](https://qiita.com/Alicesky2127/items/b8310a3f5fc5b040ca78)

# 目的 
Stringモジュールに含まれる関数を触って機能を理解したい

https://hexdocs.pm/elixir/String.html

## 実行環境
Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.17.3
Erlang v27.0



# String.next_grapheme_sizeとは
`String.next_codepoint(string)`は`string`内の次の書記素のサイズ（バイト単位）と、残りの文字列のタプルを返します。
## 例
```diff_elixir:iex
String.next_grapheme_size("olá")
```
```
{1, "lá"}
```

残りの文字列が存在しない場合
```diff_elixir:iex
String.next_grapheme_size("o")
```
```
{1, ""}
```

空文字の場合
```diff_elixir:iex
String.next_grapheme_size("")
```
```
nil
```


### `string`がUTF-8でエンコードされた文字列ではないバイトシーケンスで始まる場合
string型と同様に、次の書記素のサイズ（バイト単位）と、残りの文字列のタプル（バイナリ型）で返されます。

※下記の`\x80`はUTF-8でエンコードされた文字列ではない
```diff_elixir:iex
string = "\x80\x80hogehoge"
String.next_grapheme_size(string)
```
```
{1, <<128, 104, 111, 103, 101, 104, 111, 103, 101>>}
```

### 類似の関数との比較
https://qiita.com/Alicesky2127/items/e3fa2092a67cb9f6ec9c#stringnext_codepoint%E3%81%A8%E3%81%AF



# String.normalizeとは
`String.normalize(string, form)`は`string`を`form`形式でUnicode正規化[^1]します。
無効な Unicode コードポイントはスキップされ、文字列の残りの部分が変換されます。

## 例
### :nfd
`form = :nfd`のときは文字を可能な限り細かく分解。[^2]アクセントや修飾記号が独立した部品になります。

下記は目検ではインプットとアウトプットの文字列に違いがあるようには見えません。
```diff_elixir:iex
converted_string = String.normalize("yêṩ", :nfd)
```
```
"yêṩ"
```

しかし、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
```diff_elixir:iex
"yêṩ" == converted_string
```
```
false
```

インプットの文字列は5つのコードポイントによって構成されていました。
```diff_elixir:iex
String.codepoints("yêṩ") 
```
```
["y", "ê", "s", "̇", "̣"]
```

アウトプットの文字列はアクセントや修飾記号が独立した部品になっています。
今回の場合は`^`(サーカムフレックス記号)が独立し、6つのコードポイントによって構成されています。
したがって、インプットとアウトプットは同じでないことが分かります。
```diff_elixir:iex
String.codepoints(converted_string)
```
```
["y", "e", "̂ ", "s", "̣", "̇"]　
```

### :nfc
`form = :nfc`のときは文字を可能な限り細かく分解した後、再度合成します。

下記も目検ではインプットとアウトプットの文字列に違いがあるようには見えません。
```diff_elixir:iex
converted_string = String.normalize("leña", :nfc)
```
```
"leña"
```

しかし、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
```diff_elixir:iex
"leña" == converted_string
```
```
false
```

インプットの文字列は5つのコードポイントによって構成されていました。
```diff_elixir:iex
String.codepoints("leña") 
```
```
["l", "e", "n", "̃ ", "a"]
```

アウトプットの文字列はアクセントや修飾記号が合成された文字列になっています。
今回の場合は`̃ `(チルダ記号)が合成され、4つのコードポイントによって構成されています。
したがって、インプットとアウトプットは同じでないことが分かります。
```diff_elixir:iex
String.codepoints(converted_string)
```
```
["l", "e", "ñ", "a"]
```


### :nfkd
`form = :nfkd`のときは文字を可能な限り細かく分解。アクセントや修飾記号が独立した部品になります。
但し「互換性」を考慮して文字を変換します。[^3]
つまり、装飾や特別な書式情報を取り除き、プレーンな文字にします。

下記は目検で合字（リガチャ）が分解されたように見えています。
```diff_elixir:iex
converted_string = String.normalize("ﬁ", :nfkd)
```
```
"fi"
```

さらに、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
```diff_elixir:iex
"ﬁ" == converted_string
```
```
false
```

インプットの文字列は1つのコードポイントによって構成されていました。
```diff_elixir:iex
String.codepoints("ﬁ") 
```
```
["ﬁ"]
```
アウトプットの文字列はアクセントや修飾記号が独立した部品になっています。
今回の場合は合字が分解され、2つのコードポイントによって構成されています。
したがって、インプットとアウトプットは同じでないことが分かります。
```diff_elixir:iex
String.codepoints(converted_string)
```
```
["f", "i"]
```

### :nfkc
`form = :nfkc`のときは文字を可能な限り細かく分解。アクセントや修飾記号が独立した部品になります。
但し「互換性」を考慮して文字を変換します。[^3]
つまり、装飾や特別な書式情報を取り除き、プレーンな文字にします。
さらに、分解後、再度合成します。

下記は目検で合字（リガチャ）が分解されたように見えています。
```diff_elixir:iex
converted_string = String.normalize("ﬁ", :nfkc)
```
```
"fi"
```

さらに、マッチ演算子がfalseを返すので、インプットとアウトプットは同じでないということです。
インプットとアウトプットの差分について詳細を見てみましょう。
```diff_elixir:iex
"ﬁ" == converted_string
```
```
false
```

インプットの文字列は1つのコードポイントによって構成されていました。
```diff_elixir:iex
String.codepoints("ﬁ") 
```
```
["ﬁ"]
```
アウトプットの文字列はアクセントや修飾記号が独立した部品になっています。
今回の場合は合字が分解され、2つのコードポイントによって構成されています。
合字はプレーンな文字列ではないため、再度合成しても元の合字には戻りません。
したがって、インプットとアウトプットは同じでないことが分かります。
```diff_elixir:iex
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`で満たします。

## 例
```diff_elixir:iex
String.pad_leading("abc", 5)
```
```
"  abc"
```

不足分に対し`padding`の一部を使用するだけで満たされる場合。
`padding`の先頭から不足分にあたる文字数だけが使われる
```diff_elixir:iex
String.pad_leading("abc", 4, "12")
```
```
"1abc"
```

不足分に対し`padding`を全て使用してもまだ不足がある場合。
`padding`が繰り返し使われる
```diff_elixir:iex
String.pad_leading("abc", 6, "12")
```
```
"121abc"
```

`string`の文字数が`count`よりも多い場合
```diff_elixir:iex
String.pad_leading("abcdef", 5)
```
```
"abcdef"
```

`padding`はリストでもOK
```diff_elixir:iex
String.pad_leading("abc", 10, ["1", "23", "45"])
```
```
"abcdef"
```



# String.pad_trailingとは
`String.pad_trailing(string, count, padding \\ [" "])`は`string`の文字数を数えた上で、`count`に満たない場合、`string`の末尾から不足分を`padding`で満たします。
## 例
```diff_elixir:iex
String.pad_trailing("abc", 5)
```
```
"abc  "
```

不足分に対し`padding`の一部を使用するだけで満たされる場合。
`padding`の先頭から不足分にあたる文字数だけが使われる
```diff_elixir:iex
String.pad_trailing("abc", 4, "12")
```
```
"abc1"
```

不足分に対し`padding`を全て使用してもまだ不足がある場合。
`padding`が繰り返し使われる
```diff_elixir:iex
String.pad_trailing("abc", 6, "12")
```
```
"abc121"
```

`string`の文字数が`count`よりも多い場合
```diff_elixir:iex
String.pad_trailing("abcdef", 5)
```
```
"abcdef"
```

`padding`はリストでもOK
```diff_elixir:iex
String.pad_trailing("abc", 10, ["1", "23", "45"])
```
```
"abcdef"
```

# String.printable?とは
`String.printable?(string, character_limit \\ :infinity)`は`string`に対して`character_limit`までの文字数が印刷可能であるか否かをチェックします。

## 例
```diff_elixir:iex
String.printable?("abc")
```
```
true
```

バイナリ型は印刷不能なのでデフォルトでは`character_limit = :infinity`のためfalseになります。
```diff_elixir:iex
String.printable?("abc" <> <<0>>)
```
```
false
```

バイナリ型は印刷不能だが`character_limit`で文字数制限を付ければtrueにもなりえます。
```diff_elixir:iex
String.printable?("abc" <> <<0>>, 2)
```
```
true
```





# ～Elixirの国のご案内～
↓Elixirって何ぞや？と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ！私もエンジニア未経験から学習中です。

https://elixir-lang.info/topics/entry_column


↓[We Are The Alchemists, my friends!:bouquet:](https://qiita.com/torifukukaiou/items/6bcd5bbb80df9e594e9e "Awesome-san's Awesome article")[^10]
Elixirコミュニティは本当に優しくて温かい人たちばかり！
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。[^11]


https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

[^1]:https://ja.wikipedia.org/wiki/Unicode%E6%AD%A3%E8%A6%8F%E5%8C%96

[^2]:文字を可能な限り細かく分解することを「正準等価」または「正規等価」というそうです。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

[^3]:互換性を考慮した上で文字を可能な限り細かく分解することを「互換等価」というそうです。

[^10]: @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。


[^11]: @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中！