3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

目的

String.splitの使いどころを理解する。

実行環境

Windows 11 + WSL2 + Ubuntu 22.04
Elixir v1.17.0
Erlang v27.0

前提

Elixirの文字列はUTF-8エンコードされたバイナリです。

iex
string = "hello"
is_binary(string)
true
iex
byte_size(string)
5

String.split/1

String.split(binary)は、

  • binaryの先頭と末尾の空白を無視した上で、残りの文字をUnicodeの空白文字を区切りとして部分文字列に変換します
  • 空白文字がいくつ連続しても1つの区切り文字として扱われます

例1

iex
String.split("foo bar")
["foo", "bar"]

例2

iex
String.split("foo" <> <<194, 133>> <> "bar")
["foo", "bar"]

例2の補足

<<194, 133>>は印刷不可能な文字列

"foo" <> <<194, 133>> <> "bar"は印刷不可能な文字列を含む結合文字列です。
<<194, 133>>がバイナリのまま表示されていることに違和感がある1のですが、Elixirの文字列はUTF-8でエンコードされたバイナリであることを念頭に置けば混乱せずに済みます。

iex
 i <<194, 133>>
Term
  <<194, 133>>
Data type
  BitString
Byte size
  2
Description
  This is a string: a UTF-8 encoded binary. It's printed with the `<<>>`
  syntax (as opposed to double quotes) because it contains non-printable
  UTF-8 encoded code points (the first non-printable code point being
  `<<194, 133>>`).
Reference modules
  String, :binary
Implemented protocols
  Collectable, IEx.Info, Inspect, List.Chars, String.Chars
iex(2)>

上記を要約すると「<194, 133>>は文字列ではあるが、印刷不可能な文字が含まれている」とのことです。
印刷不可能な文字列はバイナリとしてそのまま表示されます。
ゆえに"foo" <> <<194, 133>> <> "bar"は印刷不可能な文字列を含む結合文字列だといえます。

<<194, 133>>はUnicodeの空白文字

実行結果より、<<194, 133>>が区切り文字として扱われていることが分かります。
String.split/1の定義より、区切り文字はUnicodeの空白文字です。
ゆえに<<194, 133>>はUnicodeの空白であるといえます。

例3

fooとbarの間には半角スペースと全角スペースがそれぞれ1つずつ入っていますが、空白文字がいくつ連続しても1つの区切り文字として扱われます。

iex
String.split(" foo  bar ")
["foo", "bar"]

例4

iex
String.split("no\u00a0break")
["no break"]

例4の補足

\u00a0は、Unicodeの「U+00A0」を示すもので、これはno-break space(=ノンブレークスペース)という制御文字。
これもUnicodeの空白文字に含まれているため、String.split/1の区切り文字として機能します。

String.split/3

String.split(string, pattern, options \\ [])は、

  • stringpatternに沿って区切り、部分文字列に変換します
  • optionにはRegex.split/3のオプション全てが使用できます

例1

option無し。カンマを区切り文字として部分文字列に分割

iex
String.split("a,b,c", ",")
["a", "b", "c"]
iex
String.split("a,bc", ",")
["a", "bc"]

例2

optionあり。正の整数で部分文字列の要素数を指定。デフォルトは:infinity

iex
String.split("a,b,c", ",", parts: 2)
["a", "b,c"]
iex
String.split("a,b,c,d,e", ",", parts: 3)
["a", "b", "c,d,e"]

例3

option無し。半角スペースおよびカンマを区切り文字として部分文字列に分割

iex
String.split("1,2 3,4", [" ", ","])
["1", "2", "3", "4"]

String.split/3にRegex.split/3のoptionを組み合わせる

Regex.split/3のオプションを使ってみます

例1

正規表現でカンマを区切り文字として部分文字列に分割

iex
String.split("a,b,c", ~r{,}) 
["a", "b", "c"]

例2

正規表現でカンマを区切り文字として部分文字列に分割、かつoptionの正の整数で部分文字列の要素数を指定

iex
String.split("a,b,c", ~r{,}, parts: 2)
["a", "b,c"]

例3

trimオプションはtrueのとき、出力結果のうち前後の空白文字列をトリミングする。デフォルトはfalse
正規表現の\sは1文字の区切り文字を指すメタ文字2

iex
String.split(" a b c ", ~r{\s}, trim: true)
["a", "b", "c"]

例4

正規表現で"b"を区切り文字として部分文字列に分割

iex
String.split("abc", ~r{b})
["a", "c"]

例5

正規表現で"b"を区切り文字として部分文字列に分割
include_capturesオプションはtrueのとき正規表現でマッチした部分(今回は"b")も返す。デフォルトはfalse

iex
String.split("abc", ~r{b}, include_captures: true)
["a", "b", "c"]

例6

正規表現で"b"を区切り文字として部分文字列に分割
include_capturesオプションはtrueのとき正規表現でマッチした部分(今回は"b")も返す。デフォルトはfalse
include_capturesオプションとpartsオプションを併用した場合、マッチした部分(今回は"b")については最大要素数にカウントされない。なのでこの出力結果について目検では要素数が3つあるように見える

iex
String.split("abc", ~r{b}, include_captures: true, parts: 2)
["a", "b", "c"]

例7

正規表現で"b"および"d"を区切り文字として部分文字列に分割

iex
String.split("abcde", ~r{[b,d]})
["a", "c", "e"]

例8

正規表現で"b"および"d"を区切り文字として部分文字列に分割
include_capturesオプションはtrueのとき正規表現でマッチした部分(今回は"b"と"d)も返す。デフォルトはfalse

iex
String.split("abcde", ~r{[b,d]}, include_captures: true)
["a", "b", "c", "d", "e"]

例9

正規表現で"b"および"d"を区切り文字として部分文字列に分割
include_capturesオプションはtrueのとき正規表現でマッチした部分(今回は"b"と"d)も返す。デフォルトはfalse
include_capturesオプションとpartsオプションを併用した場合、マッチした部分(今回は"b")については最大要素数にカウントされない。なのでこの出力結果について目検では要素数が3つあるように見える

iex
String.split("abcde", ~r{[b,d]}, include_captures: true, parts: 2)
["a", "b", "cde"]

例10

正規表現で"b"および"d"を区切り文字として部分文字列に分割
include_capturesオプションはtrueのとき正規表現でマッチした部分(今回は"b"と"d)も返す。デフォルトはfalse
include_capturesオプションとpartsオプションを併用した場合、マッチした部分(今回は"b"と"d)は最大要素数にカウントされない。なのでこの出力結果について目検では要素数5つあるように見える

iex
String.split("abcde", ~r{[b,d]}, include_captures: true, parts: 3)
["a", "b", "c", "d", "e"]

例11

区切り文字を変数化

iex
pattern = :binary.compile_pattern([" ", ","])
String.split("1,2 3,4", pattern)
["1", "2", "3", "4"]

例12

区切り文字に空文字を指定した場合書記素ごとに分解される

iex
String.split("abc", "")
["", "a", "b", "c", ""]

String.splitと書記素の関係

String.splitは書記素の境界を越えることがあるので注意が必要です

結合文字列ありの場合

string1は U+0065 と U+0301 の結合文字列。
したがって"e"を区切り文字として部分文字列に分割される。
また、この時書記素の境界を越える

iex
string1 = "é"
String.split(String.normalize(string1, :nfd), "e")
["", "́"]

結合文字列なしの場合

string2はU+00E9であり結合文字列ではない。
したがって"e"を区切り文字として部分文字列に分割されない。

iex
string2 = "é"
String.split(String.normalize(string2, :nfc), "e")
["é"]

備考

Elixirにおける書記素については過去記事をご参照ください

~Elixirの国のご案内~

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

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

We Are The Alchemists, my friends!:bouquet:3
Elixirコミュニティは本当に優しくて温かい人たちばかり!
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。4

  1. 初見で"foo" <> <<194, 133>> <> "bar"について「文字列とバイナリが結合してる?何これ?」と混乱したため補足としてまとめました

  2. 参考:正規表現 https://www.tohoho-web.com/js/regexp.htm

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

  4. @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中!

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?