文字列の操作
文字列の長さを取得
Java
jshell> String x = "ABCDEFG"
x ==> "ABCDEFG"
jshell> x.length()
$53 ==> 7
文字列の length()
を使用する。
Scala
scala> val x = "ABCDEFG"
x: String = ABCDEFG
scala> x.length
res37: Int = 7
文字列の length
を使用する。
Julia
julia> x = "ABCDEFG"
"ABCDEFG"
julia> length(x)
7
length()
関数を使用する。
文字の取得
Java, Scala
jshell> String x = "ABCDEFG"
x ==> "ABCDEFG"
jshell> x.charAt(2)
$52 ==> 'C'
文字列の charAt()
メソッドを使用する。
Scala
scala> x(2)
res39: Char = C
Scala は文字列を不変リストのようにも扱えるため (index)
としてもよい。
Julia
julia> x = "ABCDEFG"
"ABCDEFG"
julia> x[3]
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
文字列に対して [index]
で参照する。C のように文字の配列と見立てての操作だがインデックスが 1 から始まる点が異なる。
部分文字列の取得
Java, Scala
jshell> String x = "ABCDEFG"
jshell> x.substring(2, 5)
$62 ==> "CDE"
jshell> x.substring(2)
$63 ==> "CDEFG"
文字列の substring()
メソッドに開始/終了インデックスを指定する (終了インデックスは含まない)。終了インデックスを省略した場合は文字列の最後までを取り出す。
Scala
scala> "ABCDEFG".slice(2, 5)
res15: String = CDE
scala> "ABCDEFG".drop(2)
res16: String = CDEFG
不変コレクションと同じ使い方のできる Scala は slice(begin, length)
や drop()
を使用しても良い。
Julia
julia> x = "ABCDEFG"
"ABCDEFG"
julia> x[3:6]
"CDEF"
julia> endof(x)
7
julia> x[3:end]
"CDEFG"
文字列に対して [index]
で参照する。C のように文字の配列と見立てての操作だがインデックスが 1 から始まる点が異なる。
インデックス内で使えるキーワード end
は参照可能な最期のインデックスを表している (endof(str)
と等価)。
文字列の検索と判定
特定の文字や文字列の位置を検索
Java, Scala
jshell> String x = "ABCDEFG"
x ==> "ABCDEFG"
jshell> x.indexOf("EF")
$54 ==> 4
jshell> x.indexOf('D')
$55 ==> 3
jshell> x.indexOf('X')
$56 ==> -1
文字列に対して indexOf()
で位置を見つけたい文字または文字列を指定する。見つからなかった場合は負の値が返される。
Julia
julia> x = "ABCDEFG"
"ABCDEFG"
julia> search(x, "BC")
2:3
julia> search(x, 'E')
5
julia> search(x, "XY")
0:-1
julia> search(x, 'X')
0
search()
関数に位置を見つけたい文字または文字列を指定する。文字列を指定した場合は範囲が返される。見つからなかった場合は 0 が返される。
開始文字列または終了文字列の判定
Java, Scala
jshell> "http://www.foo.com/".startsWith("http://")
$70 ==> true
jshell> "ftp://www.foo.com/".startsWith("http://")
$71 ==> false
jshell> "filename.txt".endsWith(".txt")
$68 ==> true
jshell> "filename.jpg".endsWith(".txt")
$69 ==> false
文字列の startsWith()
や endsWith()
を使用する。
Julia
julia> startswith("https://julialang.org", "https://")
true
julia> endswith("https://julialang.org", ".org")
true
startswith()
もしくは endswith()
関数を使用する。
文字や文字列が含まれているかの判定
Java, Scala
jshell> String x = "ABCDEFG"
x ==> "ABCDEFG"
jshell> x.contains("CD")
$57 ==> true
jshell> x.contains("XY")
$58 ==> false
jshell> x.indexOf('F') >= 0
$59 ==> true
文字列に対して contains()
に判定したい文字列を指定する。Scala は文字の場合も contains()
が利用できるが Java は indexOf()
を使用する。
scala> x.contains('E')
res38: Boolean = true
Julia
julia> x = "ABCDEFG"
"ABCDEFG"
julia> contains(x, "BC")
true
julia> contains(x, "XY")
false
julia> 'E' ∈ x
true
julia> 'E' in x
true
contains()
関数に判定したい文字列を指定する。文字の場合は集合が要素を包含しているかの判定として in()
(∈
も同じ) を使用する。REPL で ∈
の入力は \inTab ⇆。
正規表現との一致判定と部分文字列の参照
Java, Scala
jshell> String x = "ABBCCCDDDDEEEEE"
x ==> "ABBCCCDDDDEEEEE"
jshell> x.matches("A.*E")
$78 ==> true
jshell> x.matches("E+")
$79 ==> false
Java の全体一致は文字列の matches()
メソッドを使うのが簡単。ただし同じパターンを繰り返し利用するのであれば効率の面から Pattern
を使用する。
利用できる正規表現は Pattern
を参照。
Pattern
はコンパイル済みの正規表現を表す immutable なオブジェクト。matcher()
で判定したい文字列の Matcher
を取得し matches()
(全体一致), find()
(部分一致), lookingAt()
(前方一致) で判定を行う。
jshell> import java.util.regex.Pattern
jshell> import java.util.regex.Matcher
jshell> Pattern p = Pattern.compile("&#[xX](\\p{XDigit}+);")
p ==> &#[xX](\d+);
jshell> Matcher m1 = p.matcher("D")
m1 ==> java.util.regex.Matcher[pattern=&#[xX](\d+); region=0,6 lastmatch=]
jshell> if(m1.matches()){
System.out.println((char)Integer.parseInt(m1.group(1), 16));
}
D
jshell> p.matcher("ABCDEF").matches()
$85 ==> false
マッチが成功した Matcher
からパターンと一致した部分文字列を参照することができる。group(0)
は一致した部分文字列全体を表し、それ以降は正規表現中に ()
で囲んだ部分を表している。
jshell> Matcher m2 = p.matcher("ABCDEF")
m2 ==> java.util.regex.Matcher[pattern=&#[xX](\d+); region=0,11 lastmatch=]
jshell> if(m2.find()){
System.out.println((char)Integer.parseInt(m1.group(1), 16));
}
D
Matcher
の find()
メソッドは文字列中で最初に一致した部分を取得する。複数の一致部分を取得したい場合は find()
を繰り返し呼ぶことができる。find()
は文字列内にそれ以上一致部分が存在しなくなった時に false を返す。reset()
を実行すると文字列を再び先頭から評価する。
jshell> m2.reset().lookingAt()
$54 ==> false
jshell> p.matcher("DEF").lookingAt()
$89 ==> true
Matcher
の
lookingAt()
メソッドは前方一致を判定する。
Scala
scala> val entity = "&#[xX](\\p{XDigit}+);".r
entity: scala.util.matching.Regex = &#[xX](\p{XDigit}+);
scala> val entity(hex) = "D"
hex: String = 44
scala> "D" match {
| case entity(hex) => println(hex)
| case _ => println("not match")
| }
44
scala> entity.findAllMatchIn("ABCDEF").map(_.group(1)).mkString(",")
res12: String = 44,45,46
Scala の Regex
は extractor として機能するため、一致した正規表現中のグループを変数として抽出することができる。一致が確実であれば直接代入しても良いが、確実でないなら match
を使用する。
文字列内の一致部分を全て列挙する場合は findAllMatchIn()
を使用する。その他、最初に見つかった部分のみであれば findFirstMatchIn()
、先頭一致は findPrefixMatchOf()
を使用する。
Julia
julia> ismatch(r"&#[xX]([0-9a-fA-F]+);", "D")
true
julia> m = match(entity, "ABCDEF")
RegexMatch("D", 1="44")
julia> m.match
"D"
julia> m.captures[1]
"44"
julia> for m in eachmatch(entity, "ABCDEF")
println(m.match)
end
D
E
F
julia> matchall(entity, "ABCDEF")
3-element Array{SubString{String},1}:
"D"
"E"
"F"
一致判定のみであれば ismatch()
関数、最初に見つかった部分文字列が必要であれば match()
関数、一致した全ての部分文字列やグループ部分が必要であれば eachmatch()
や matchall()
関数を使用する
文字列の置換と変換
部分文字列の置換
Java, Scala
jshell> String x = "ABCDEFG"
x ==> "ABCDEFG"
jshell> x.replace("DEF", "XYZ")
$69 ==> "ABCXYZG"
文字列の replace()
メソッドで一致する部分文字列をすべて別の文字に置換する。
Julia
julia> x = "ABCDEFG"
"ABCDEFG"
julia> replace(x, "CDE", "XYZ")
"ABXYZFG"
replace()
関数に置換対象の文字列と置換後の文字列を指定する。
正規表現を使った置換
Java, Scala
scala> val x = "ABBCCCDDDEEEE"
x: String = ABBCCCDDDEEEE
scala> x.replaceAll("B+C", "XYZ")
res48: String = AXYZCCDDDEEEE
scala> x.replaceAll("D{2}([^D])", "-$1-")
res51: String = ABBCCCD-E-EEE
文字列の replaceAll()
メソッドを使用する。正規表現版は ()
によるグループ化とそのプレースホルダー $num
で結果に一致した部分文字列を挿入することができる。
Julia
julia> x = "ABBCCCDDDDEEEE"
"ABBCCCDDDDEEEE"
julia> replace(x, r"B+C", "XYZ")
"AXYZCCDDDDEEEE"
julia> replace(x, r"D{2}([^D])", s"-\1-")
"ABBCCCDD-E-EEE"
julia> replace(x, r"D{2}(?<p>[^D])", s"-\g<p>-")
"ABBCCCDD-E-EEE"
replace()
関数の第二引数に r"..."
と正規表現を指定する。第三引数を s"..."
とした場合、一致した ()
グループをキャプチャし \num
で置き換えることができる。また名前付きグループ (?<name>...)
は \g<name>
で置き換えが可能 (name
は \num
の num
も可)。
正規表現を使った高度な置換
Scala
scala> val x = "ABC"
x: String = ABC
scala> val reg = "&#x(\\d+);".r
reg: scala.util.matching.Regex = &#x(\d+);
scala> reg.replaceAllIn(x, m => Integer.parseInt(m.group(1), 16).toChar.toString)
res58: String = ABC
正規表現の replaceAllIn()
の第二引数に Matcher
を取り文字列を返す関数を指定することで、一致部分を加工した結果をその位置に置き換える文字として使用することができる。
Julia
julia> x = "ABC"
"ABC"
julia> replace(x, r"&#x(\d+);", function(x)
Char(parse(Int16, x[4:end-1], 16))
end)
"ABC"
replace()
関数の第三引数に関数を指定した場合、一致した文字列を加工した結果をその位置に置き換える文字として使用することができる。上記は XML の数値エンティティで A
を 'A'
に置き換えている。
大文字または小文字の統一
Java, Scala
jshell> "abcdEFG".toLowerCase()
$76 ==> "abcdefg"
jshell> "abcdEFG".toUpperCase()
$77 ==> "ABCDEFG"
String
クラスの toUpperCase()
, toLowerCase()
メソッドを使用する。
数値と文字列の変換
数値と文字列の変換参照。
文字列の分割と連結、削除
文字列の分割
Java, Scala
jshell> String x = "1,2,3,,4"
x ==> "1,2,3,,4"
jshell> x.split(",")
$73 ==> String[5] { "1", "2", "3", "", "4" }
jshell> "".split(",")
$75 ==> String[1] { "" }
jshell> ",".split(",")
$76 ==> String[0] { }
文字列の split()
メソッドを使用する。引数は正規表現であることに注意。
StringTokenizer
も使用できるがあまり使用されていない。しかし分割子 (delimiter) 付きで取得したい場合は使用することもある。
Julia
julia> x = "1,2,3,,4"
"1,2,3,,4"
julia> split(x, ',')
5-element Array{SubString{String},1}:
"1"
"2"
"3"
""
"4"
julia> split(x, r",+")
4-element Array{SubString{String},1}:
"1"
"2"
"3"
"4"
split()
関数を使用する。第二引数は正規表現を指定することもできる。
文字列の結合
Java
jshell> String[] x = {"A", "B", "C"}
x ==> String[3] { "A", "B", "C" }
jshell> String.join(",", x)
$44 ==> "A,B,C"
String
クラスの join()
メソッドを使用する。
Scala
scala> Array("A", "B", "C").mkString(",")
res1: String = A,B,C
コレクションクラスの mkString()
を使用する。
文字列の連結
Java, Scala
jshell> String x = "ABC"
x ==> "ABC"
jshell> String y = x + "DEF"
y ==> "ABCDEF"
Java, Scala 共に単純な文字列連結は +
演算子を使用する (concat()
メソッドもあるが特に優位性はないので使われていない)。
StringBuilder buffer = new StringBuilder();
for(int i=0; i<1000; i++){
buffer.append(i);
}
String s = buffer.toString();
ループなどで大量の文字列連結を行う場合はパフォーマンスの理由で StringBuilder
を使用し最後に文字列化する。
Julia
julia> "ABD" * "DEF" * string(1234)
"ABDDEF1234"
julia> string("ABD", "DEF", 1234)
"ABDDEF1234"
左右オペランドが String
型の2項演算子 *
が文字列連結として定義されている (文字列はモノイドだがその積は + ではなく \(\cdot\) や \(*\) を使うため)。*
を使用する場合、数値などは明示的に文字列に変換する必要がある (標準関数の string()
を使用すれば文字列でない値のキャストは不要)。
julia> buffer = IOBuffer()
julia> for i in 1:1000000
print(buffer, randstring())
end
julia> String(take!(buffer))
"XepcRZtcGhdRNcrDKBZsGWPfx7w5WJPtYwDNHus03qx8P...
ループなどで大量の文字列連結を行う場合はパフォーマンスの理由で IOBuffer
を使用し最後に文字列化する。
前後の空白の削除
Java, Scala
jshell> String x = " \tXYZ\n "
x ==> " \tXYZ\n "
jshell> x.trim()
$65 ==> "XYZ"
jshell> x.replaceAll("^\\s+", "")
$66 ==> "XYZ\n "
jshell> x.replaceAll("\\s+$", "")
$67 ==> " \tXYZ"
文字列の trim()
メソッドで文字列前後の空白文字を削除する (タブタブや改行文字を含む)。左のみ、右のみの trim は正規表現を使った置換を行う。
Scala であれば dropWhile()
で左から空白文字を削除する (右に対応する dropRightWhile()
はない)。
scala> x.dropWhile(ch => Character.isWhitespace(ch))
res43: String =
"XYZ
"
Julia
julia> x = " \t XYZ \n "
" \t XYZ \n "
julia> strip(x)
"XYZ"
julia> lstrip(x)
"XYZ \n "
julia> rstrip(x)
" \t XYZ"
strip()
, lstrip()
(左), rstrip()
(右) で文字列前後の空白文字を削除する。
文字列内の部分文字を削除
Java, Scala
jshell> "1234-56-7890".replace("-", "")
$72 ==> "1234567890"
特定のパターンに一致した部分を削除したい場合は置換を使用する。
jshell> new StringBuilder("ABCDEFG").delete(2, 5).toString()
$75 ==> "ABFG"
範囲指定で部分文字列を削除したい場合は StringBuilder
化して delete()
した後に再び文字列へ戻す。
Scala
scala> "ABCあDEいFうえGお".filter{ ch => ch <= 0xFF }
res22: String = ABCDEFG
コレクションとして filter()
メソッドを使用し条件に一致する文字のみを残す事ができる。
Julia
julia> replace("<b>text</b>", r"</?.*?>", "")
"text"
特定のパターンに一致した部分を削除したい場合は置換を使用する。
文字列のフォーマット
文字列の補間
文字列補間 (string interpolation) は文字列リテラル中にプレースホルダを記述し、実行時に変数や式の結果へ置き換える機能。文字列のフォーマットを簡潔かつ直感的に記述することができる。
Scala
scala> val pi = math.Pi
pi: Double = 3.141592653589793
scala> s"π=$pi, e=${math.E}"
res10: String = π=3.141592653589793, e=2.718281828459045
scala> f"π=$pi%.4f, e=${math.E}%.4e"
res12: String = π=3.1416, e=2.7183e+00
scala> s"dollar:$$, quote:${'"'} or \042"
res15: String = dollar:$, quote:" or "
scala> s"""or use here-doc "instead""""
res18: String = or use here-doc "instead"
scala> raw" \ ${math.Pi} \ "
res19: String = " \ 3.141592653589793 \ "
文字列中に $variable
または ${expression}
で値を埋め込むことができる。s"..."
は単純補間。f"..."
は書式付き補間で Formatterの書式を記述可能。raw"..."
は単純補間だが \
をエスケープする必要がない。
ドル記号 $
は $$
と2つ重ねることでエスケープできる。しかしダブルクォート "
のエスケープは従来の \"
がエラーになるため ${'"'}
か \042
か、もしくはヒアドキュメント形式で記述する必要がある。
Scala の文字列補間は言語構文の機能ではなく開発者が StringContextを実装することで独自に定義することができる。
Java
Java に文字列補間の機能はない。
Julia
julia> "π=$pi, √π=$(sqrt(pi))"
"π=3.141592653589793, √π=1.7724538509055159"
julia> print("dollar: \$")
dollar: $
julia> print(raw"$string $interpolation in raw string is ignored")
$string $interpolation in raw string is ignored
文字列中に $variable
または $(expression)
で値を埋め込むことができる。ドル記号 $
は \$
でエスケープ可能。文字列補間として機能させたくない場合は raw"..."
とすることで無効化できる。
文字列の書式指定
文字列補間と違ってフォーマットは単なる文字列であるため、言語リソースとして外部ファイルに定義することができる。
Java
jshell> String.format("π=%.5f", Math.PI)
$34 ==> "π=3.14159"
jshell> String zip = "150-0015"
jshell> String country = "JP"
jshell> String state = "Tokyo"
jshell> String city = "Shibuya"
jshell> String.format("%s %s, %s, %s", zip, country, state, city)
$42 ==> "150-0015 JP, Tokyo, Shibuya"
jshell> String.format("%1$s %4$s, %3$s, %2$s", zip, country, state, city)
$41 ==> "150-0015 Shibuya, Tokyo, JP"
String
クラスの format()
メソッドを使用する。Formatter
に定義されている構文が使用できる。
Scala
scala> "π=%.4f".format(math.Pi)
res0: String = π=3.1416
Scala はフォーマット文字列に対して format()
メソッドを使用する。利用可能な書式はJava と同じ。
Julia
julia> Pkg.add("Formatting")
INFO: Cloning cache of Formatting from https://github.com/JuliaIO/Formatting.jl.git
INFO: Installing Formatting v0.3.0
INFO: Package database updated
julia> using Formatting
INFO: Precompiling module Formatting.
julia> format("{} = {:.4f}", 'π', pi)
"π = 3.1416"
julia> format("{1} {4}, {3}, {2}", "150-0015", "JP", "Tokyo", "Shibuya")
"150-0015 Shibuya, Tokyo, JP"
Formatting パッケージを使用する。
ヒアドキュメント
Scala
scala> print("""This is 1st line.
|This is 2nd line.
|No need to escape \b, \r, \n, \t,
|"quote" and ""doubled quote"".
|""".stripMargin('|'))
This is 1st line.
This is 2nd line.
No need to escape \b, \r, \n, \t,
"quote" and ""doubled quote"".
ダブルクオート 3 つで囲うことで改行、タブ、ダブルクオート、バックスラッシュを含む文字列をそのまま記述できる。
コード整形などでインデントが修正されても問題がないように通常は stripMargin()
を使って行頭記号までを削除する。
scala> s"""BUT, if you use with string interpolation,
|you MUST escape like \\.""".stripMargin
res24: String =
BUT, if you use with string interpolation,
you MUST escape like \.
s
, f
の文字列補間と併用した場合は \
付きのエスケープが有効化されてしまうため raw
を使うか \\
とエスケープしなければならない点に注意。
Java
Java にヒアドキュメントに相当する機能はない。
Julia
julia> print("""
This is 1st line.
This is 2nd line.
No need to escape "quote" and ""doubled quote"",
but \\b, \\r, \\n and \\t must escape.
Here doc with string interpolation: $pi
""")
This is 1st line.
This is 2nd line.
No need to escape "quote" and ""doubled quote"",
but \b, \r, \n and \t must escape.
Here doc with string interpolation: 3.141592653589793
ダブルクオート 3 つで囲うことで改行、タブ、ダブルクオート等を含む文字列をそのまま記述できる。ただし \
付きのエスケープシーケンスは有効なため \\
とエスケープしなければならない。
先頭の空行は無視される。最後の """
の位置はインデントレベルを意味している。
文字列の文字コード変換
Unicode 数値を文字に変換
Java
jshell> char ch = 0x3042
ch ==> 'あ'
jshell> int i = ch
i ==> 12354
jshell> "ch=" + ch + ", i=" + i
$45 ==> "ch=あ, i=12354"
int
と char
はキャストなしで変換が可能。
Scala
scala> val ch:Char = 0x3042
ch: Char = あ
scala> val i:Int = ch
i: Int = 12354
scala> "ch=" + ch + ", i=" + i
res0: String = ch=あ, i=12354
Scala もJava と同様に変換なしで代入が可能。
Julia
julia> string("[", 0x3042, "]")
"[12354]"
julia> string("[", Char(0x3042), "]")
"[あ]"
julia> Int32('あ')
12354
整数を文字コードとして Char()
に指定すれば文字として扱うことができる。
文字コード変換
Java, Scala
jshell> import java.nio.charset.StandardCharsets
jshell> String x = "あいうえお"
x ==> "あいうえお"
jshell> byte[] utf8 = x.getBytes(StandardCharsets.UTF_8)
utf8 ==> byte[15] { -29, -127, -126, -29, -127, -124, -29, ... 7, -120, -29, -127, -118 }
jshell> byte[] eucjp = x.getBytes("euc-jp")
eucjp ==> byte[10] { -92, -94, -92, -92, -92, -90, -92, -88, -92, -86 }
jshell> byte[] sjis = x.getBytes("Windows-31j")
sjis ==> byte[10] { -126, -96, -126, -94, -126, -92, -126, -90, -126, -88 }
jshell> new String(utf8, StandardCharsets.UTF_8)
$25 ==> "あいうえお"
jshell> new String(eucjp, "euc-jp")
$26 ==> "あいうえお"
jshell> new String(sjis, "Windows-31j")
$27 ==> "あいうえお"
文字列を UTF-8 や他の文字セットのバイト配列に変換する場合は getBytes()
メソッドを使用する。バイト配列を文字列に戻す場合は String()
のコンストラクタにそのバイト配列で想定している文字セットを指定する。
エンコード/デコード共に文字セットで定義されていない文字が含まれている場合は ?
(0x3F) に置き換えられる。
jshell> x.getBytes("us-ascii")
$28 ==> byte[5] { 63, 63, 63, 63, 63 }
jshell> new String(utf8, "us-ascii")
$29 ==> "???????????????"
Julia
julia> Pkg.add("StringEncodings")
INFO: Initializing package repository ...
...
julia> using StringEncodings
julia> x = "あいうえお"
"あいうえお"
julia> utf8 = encode(x, "UTF-8")
15-element Array{UInt8,1}: 0xe3 0x81 0x82 0xe3 0x81 0x84 0xe3 0x81 0x86 0xe3 0x81 0x88 0xe3 0x81 0x8a
julia> sjis = encode(x, "windows-31j")
10-element Array{UInt8,1}: 0x82 0xa0 0x82 0xa2 0x82 0xa4 0x82 0xa6 0x82 0xa8
julia> eucjp = encode(x, "euc-jp")
10-element Array{UInt8,1}: 0xa4 0xa2 0xa4 0xa4 0xa4 0xa6 0xa4 0xa8 0xa4 0xaa
julia> decode(utf8, "UTF-8")
"あいうえお"
julia> decode(sjis, "Windows-31j")
"あいうえお"
julia> decode(eucjp, "EUC-JP")
"あいうえお"
0.6.0 時点では StringEncodings パッケージが利用できる。encode()
関数で UInt8
配列に変換し decode()
関数で文字列へ戻す。文字コードの識別子は大文字と小文字を区別しない。
RELP で encodings()
と入力すれば使用可能な文字セットの一覧が表示される。Windows 環境で 398 に対して Linux 環境では 1240 文字セットから実行環境によって利用できる文字セットに大きな差がある点に注意。
参考文献
- String (Java API Reference)
- StringLike (Scala API Reference)
- String (Julia Document)
- Introducing Julia/Strings and characters (wikibooks)
- 文字列の補間 (Scala Document)