[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
この記事では、特定の文字・文字列が文字列中のどこにあるのかを特定する方法を解説します。
文字や文字列を「indexOf
」メソッドで検索する
特定の文字や文字列が文字列中のどの位置にあるのかを調べるには、indexOf
メソッドを使用します。
indexOf
メソッドを使用すると、対象となる文字や文字列の位置を示すインデックスを取得することができます。
インデックスは0から始まる正の整数です。
人が普通に数えるときの番号から1を引いたものです。
- 1番目ならインデックスは0
- 2番目ならインデックスは1
- ...
- n番目ならインデックスはn-1
となります。
indexOf
メソッドは以下のように定義されています。
引数には文字列、あるいはUnicodeのコードポイントを表す数字を渡します。
Javapublic int indexOf(int ch) public int indexOf(String str)
Scala// Scala風に読み替えるとこうなります def indexOf(ch: Int): Int def indexOf(str: String): Int
java.lang.String#indexOf(int)
java.lang.String#indexOf(java.lang.String)
文字列中の1文字を検索する
まずは特定の1文字を指定して調べてみましょう。
indexOf
メソッドの引数にChar
型の値を渡します。
indexOf
メソッドが引数に取るのはInt
(Javaのint
)かString
ですが、実はChar
型の値を渡すと、Int
型へと暗黙的に変換されます。
これはJavaもScalaも共通です。
この挙動のおかげでInt
を取る引数にChar
型を渡すことができる、というわけです。
val Title = "Scala逆引き解説|Scalapedia"
println(Title.indexOf('a'))
出力は以下のようになります。
3番目にあるのでインデックスは2となります。
2
文字列の中の文字列を検索する場合も「indexOf
」
今度は文字列を指定して検索してみましょう。
ここでは、試しに2文字以上の文字列を指定してみます。
使い方は、引数にさきほどのChar
の代わりに文字列(String
)を与えてあげればOKです。
戻り値は文字列の先頭の文字のインデックスです。
"ap"と指定したら"a"の部分のインデックスが返ってきます。
val Title = "Scala逆引き解説|Scalapedia"
println(Title.indexOf("ap"))
出力は以下のようになります。
"ap"の"a"は16番目に位置しているのでインデックスは15となります。
15
「lastIndexOf
」で後ろから順に文字を検索する
indexOf
メソッドは前から検索していました。
これに対して、文字や文字列を後ろから検索するにはlastIndexOf
メソッドを使います。
lastIndexOf
メソッドは以下のように定義されています。
Javapublic int lastIndexOf(int ch) public int lastIndexOf(String str)
Scala// Scala風に読み替えるとこうなります def lastIndexOf(ch: Int): Int def lastIndexOf(str: String): Int
java.lang.String#lastIndexOf(int)
java.lang.String#lastIndexOf(java.lang.String)
先程と同様、特定の1文字を指定して調べてみましょう。
lastIndexOf
メソッドの引数にChar
型の値を渡します。
val Title = "Scala逆引き解説|Scalapedia"
println(Title.lastIndexOf('c'))
出力は以下のようになります。
2つある"c"のうち、後ろの"c"は13番目にあるので、インデックスは12となります。
12
indexOfSlice
で一連の文字を検索する
ScalaのString
にはindexOfSlice
メソッドが生えています。
このメソッドを使うと、文字列に含まれる連続する文字を検索することができます。
実はこのindexOfSlice
メソッドは、String
ではなくSeqOps
が持っているメソッドです。
Seq
におけるindexOf(str: String)
に相当するメソッドとして実装されています。
String
の場合にはindexOf
メソッドで同じことができるので、実際のところ使う必要性があまりないのですが、
「Seq[Char]
ならある」という場合には便利に使うことができます。
例えばString
ではないがArray[Char]
ならあるよ、という場合にはArray#toSeq
メソッドで変換して使うことができます。
val Title = "Scala逆引き解説|Scalapedia"
val arr = Array('l', 'a') println(Title.indexOfSlice(arr.toSeq))
3
ScalaのString
にindexOfSlice
メソッドがあるのはなぜ?
さて、ここでひとつ脱線しますね。
ScalaのString
クラス自体はjava.lang.String
なので、Javaと共通です。
しかし、JavaのString
にはindexOfSlice
メソッドはありません。
なぜJavaのString
に無いはずのメソッドがScalaにおいて生えているのでしょうか?
それは、String
がScalaにおいてWrappedString
に暗黙的に変換されるからです。
このWrappedString
型が継承しているSeqOps
クラスにindexOfSlice
メソッドが定義されているため、あたかもStringにメソッドが生えているかのように見えるのです。
WrappedString#indexOfSlice[B >: Char](Seq[B]): Int
lastIndexOfSlice
で一連の文字を後ろから検索する
lastIndexOfSlice
というメソッドもあります。
その名の通り、後ろから検索する以外はindexOfSlice
と同じ機能を持つメソッドです。
検索対象となる文字はSeq
で指定します。
こちらは逆順になりません。
val Title = "Scala逆引き解説|Scalapedia"
println(Title.lastIndexOfSlice(Seq('l', 'a')))
出力は以下のようになります。
後ろの"la"は15文字目から始まるので、インデックスは14となります。
14
WrappedString#lastIndexOfSlice[B >: Char](Seq[B]): Int
indexWhere
で条件を満たす文字の位置を調べる
Scalaでは、indexWhere
メソッドを使うと、特定の1文字だけでなく特定の条件を満たすようないずれかの文字を検索することもできます。
文字が満たすべき条件を指定して検索してみましょう。
ここではアルファベットの小文字を検索してみます。
val Title = "Scala逆引き解説|Scalapedia"
println(Title.indexWhere(_.isLower))
結果は以下のようになります。 先頭から数えて最初の小文字である"c"がヒットし、そのインデックスである1が出力されます。
1
WrappedString#indexWhere((Char) => Boolean): Int
indexWhere
で正規表現にマッチする文字列の位置を調べる
また、indexWhere
メソッドは正規表現パターンと組み合わせて使うことができます。
これにより、文字が満たすべき条件を正規表現で指定することができます。
こんどは文字列に含まれる数字を検索してみましょう。
val Title = "Scala逆引き解説|Scalapedia"
@annotation.nowarn val result = Title.indexWhere(c => "\\d".r().matches(c.toString) ) println(result)
結果は以下のようになります。
元の文字列には数字が含まれていなかったので-1
が出力されます。
-1
lastIndexWhere
で条件を満たす文字の位置を後ろから調べる
indexWhere
にも後ろから検索するメソッドが用意されており、それがlastIndexWhere
メソッドです。
こんどは最後の大文字の位置を取得してみましょう。
val Title = "Scala逆引き解説|Scalapedia"
println(Title.lastIndexWhere(_.isUpper))
うしろの"S"は12番目なので、インデックスは11となります。
11
WrappedString#lastIndexWhere(A=>Boolean):Int
指定した位置以降に出てくる特定の文字列の位置を知りたい場合
これにて、先頭や末尾の文字や文字列の位置を調べることができるようになりました。
それでは、それぞれ2番目以降に出現する文字列の位置を取得するにはどうしたらよいでしょうか?
これまで紹介してきたメソッドには、それぞれ、第2引数に検索を開始する位置を与えられるメソッドが定義されています。
指定した位置以降に出てくる特定の文字列の位置を取得したい場合は、これらのメソッドを使うことができます。
Javapublic int indexOf(int ch, int fromIndex)
Scala// Scala風に読み替えるとこうなります def indexOf(ch: Int, fromIndex: Int): Int
indexOf
メソッドを使って、指定した位置以降にある特定の文字を検索してみましょう。
インデックス5(つまり6番目)以降にある文字"a"を検索します。
val Title = "Scala逆引き解説|Scalapedia"
println(Title.indexOf('a', 5))
結果は以下のようになります。
インデックス5以降で最初の"a"のインデックスである13が出力されます。
13
java.lang.String#indexOf(int, int)
java.lang.String#indexOf(java.lang.String, int)
java.lang.String#lastIndexOf(int, int)
java.lang.String#lastIndexOf(java.lang.String, int)
WrappedString#indexOfSlice(Seq[B], Int): Int
WrappedString#lastIndexOfSlice(Seq[B], Int): Int
WrappedString#indexWhere((Char) => from: Int): Int
WrappedString#lastIndexWhere(A=>Boolean, Int):Int
n回目に出てくる特定の文字・文字列の位置を知りたい場合
1文字の場合
さて、それでは特定の文字がn回目に出てくる位置を調べてみましょう。
新たにindexOf
メソッドを定義します。
indexOf
メソッドには元の文字列、検索対象の文字、出現回数を渡します。
末尾再帰の最適化をするアノテーションscala.annotation.tailrec
は忘れずに付けましょう。
scala.annotation.tailrec
def indexOf(target: String, elem: Char, count: Int): Int = @annotation.tailrec def go(target: String, elem: Int, count: Int, fromIndex: Int): Int = if (count <= 0) -1 else if (count == 1) target.indexOf(elem, fromIndex) else val nextCount = count - 1 val nextIndex = target.indexOf(elem, fromIndex) + 1 go(target, elem, nextCount, nextIndex) go(target, elem.toInt, count, 0)
val Title = "Scala逆引き解説|Scalapedia"
println(indexOf(Title, 'a', -1)) println(indexOf(Title, 'a', 0)) println(indexOf(Title, 'a', 1)) println(indexOf(Title, 'a', 2)) println(indexOf(Title, 'a', 3))
結果は以下のようになります。
-1 -1 2 4 13
文字列の場合
さて、こんどは特定の文字列がn回目に出てくる位置を調べてみましょう。
重ならないように数える場合の方法です。
def indexOf(target: String, elem: String, count: Int): Int = @annotation.tailrec def go(target: String, elem: String, count: Int, fromIndex: Int): Int = if (count <= 0) -1 else if (count == 1) target.indexOf(elem, fromIndex) else val nextCount = count - 1 val nextIndex = target.indexOf(elem, fromIndex) + elem.length go(target, elem, nextCount, nextIndex) go(target, elem, count, 0)
val Title = "Scala逆引き解説|Scalapedia"
println(indexOf(Title, "la", -1)) println(indexOf(Title, "la", 0)) println(indexOf(Title, "la", 1)) println(indexOf(Title, "la", 2))
-1 -1 3 14
可変長文字列(StringBuilder
)の場合
可変長文字列(StringBuilder
)の場合も、Stringと同様にindexOf
、lastIndexOf
、indexOfSlice
、indexWhere
メソッドを使用することができます。
indexOf
でStringBuilder内の文字を検索する
val Title = "Scala逆引き解説|Scalapedia"
println(TitleBuilder.indexOf('p'))
16
StringBuilder#indexOf[B >: Char](B):Int
StringBuilder#indexOf[B >: Char](B, Int):Int
indexOf
でStringBuilder内の文字列を検索する
val Title = "Scala逆引き解説|Scalapedia"
println(Title.indexOf("ap"))
15
StringBuilder#indexOf(String):Int
StringBuilder#indexOf(String, Int):Int
lastIndexOf
でStringBuilder内の文字を後ろから検索する
val Title = "Scala逆引き解説|Scalapedia"
println(TitleBuilder.lastIndexOf('l'))
14
StringBuilder#lastIndexOf(String):Int
StringBuilder#lastIndexOf(String, Int):Int
StringBuilder#lastIndexOf[B >: Char](B, Int):Int
indexOfSlice
でStringBuilder内の文字列を検索する
val Title = "Scala逆引き解説|Scalapedia"
println(Title.indexOfSlice(Seq('l', 'a')))
3
WrappedString#indexOfSlice(that: Seq[B]): Int
WrappedString#indexOfSlice(that: Seq[B], from: Int): Int
lastIndexOfSlice
でStringBuilder内の文字列を後ろから検索する
val Title = "Scala逆引き解説|Scalapedia"
println(Title.lastIndexOfSlice(Seq('l', 'a')))
14
StringBuilder#lastIndexOfSlice[B>:A](Seq[B]):Int
StringBuilder#lastIndexOfSlice[B>:A](Seq[B], Int):Int
indexWhere
でStringBuilder内の条件を満たす文字の位置を調べる
val Title = "Scala逆引き解説|Scalapedia"
println(Title.indexWhere(_.isLower))
1
WrappedString#indexWhere(p: (Char) => Boolean): Int
WrappedString#indexWhere(p: (Char) => from: Int): Int
indexWhere
でStringBuilder内の正規表現にマッチする文字の位置を調べる
val Title = "Scala逆引き解説|Scalapedia"
@annotation.nowarn val result = TitleBuilder.indexWhere(c => "\\d".r().matches(c.toString) ) println(result)
-1
lastIndexWhere
でStringBuilder内条件を満たす文字の位置を後ろから調べる
val Title = "Scala逆引き解説|Scalapedia"
println(Title.lastIndexWhere(_.isUpper))
11
StringBuilder#lastIndexWhere(A=>Boolean):Int
StringBuilder#lastIndexWhere(A=>Boolean, Int):Int