文字列の操作

Takami Torao Java 9 Scala 2.12 Julia 0.6 #java #scala #julialang
  • このエントリーをはてなブックマークに追加

文字列の長さを取得

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

Matcherfind() メソッドは文字列中で最初に一致した部分を取得する。複数の一致部分を取得したい場合は find() を繰り返し呼ぶことができる。find() は文字列内にそれ以上一致部分が存在しなくなった時に false を返す。reset() を実行すると文字列を再び先頭から評価する。

jshell> m2.reset().lookingAt()
$54 ==> false

jshell> p.matcher("DEF").lookingAt()
$89 ==> true
MatcherlookingAt() メソッドは前方一致を判定する。

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\numnum も可)。

正規表現を使った高度な置換

Scala

scala> val x = "&#x41;BC"
x: String = &#x41;BC

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 = "&#x41;BC"
"&#x41;BC"

julia> replace(x, r"&#x(\d+);", function(x)
         Char(parse(Int16, x[4:end-1], 16))
       end)
"ABC"

replace() 関数の第三引数に関数を指定した場合、一致した文字列を加工した結果をその位置に置き換える文字として使用することができる。上記は XML の数値エンティティで &#x41;'A' に置き換えている。

大文字または小文字の統一

Java, Scala

jshell>  "abcdEFG".toLowerCase()
$76 ==> "abcdefg"

jshell> "abcdEFG".toUpperCase()
$77 ==> "ABCDEFG"

String クラスの toUpperCase(), toLowerCase() メソッドを使用する。

Julia

julia> uppercase("abcdEFG")
"ABCDEFG"

julia> lowercase("abcdEFG")
"abcdefg"

uppercase(), lowercase() 関数を使用する。

数値と文字列の変換

数値と文字列の変換参照。

文字列の分割と連結、削除

文字列の分割

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() を使用する。

Julia

julia> join(["A", "B", "C"], ", ")
"A, B, C"

join() 関数を使用する。

文字列の連結

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"

intchar はキャストなしで変換が可能。

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 文字セットから実行環境によって利用できる文字セットに大きな差がある点に注意。

参考文献

  1. String (Java API Reference)
  2. StringLike (Scala API Reference)
  3. String (Julia Document)
  4. Introducing Julia/Strings and characters (wikibooks)
  5. 文字列の補間 (Scala Document)