概要
スネークケース(snake case)の文字列を、キャメルケース(lower camel case)に変換する方法をいくつか試してみました。
簡単のため、元の文字列はスネークケースとして well-formed であるとします。
(_foo bar_ B_aZ などの文字列は想定しない)
試した実装方法
Iteratorを使った命令型の方法
普通にIteratorでループして、 _ の場合はもう一文字読み込んで大文字にしています。
(注:文字列の末尾が _ だと落ちます)
def byIte(str: String): String = {
val it = str.iterator
val sb = new StringBuilder
while (it.hasNext) {
val c = it.next()
sb + ( if (c == '_') it.next().toUpper else c )
}
sb.toString()
}
正規表現を使った方法
replaceAllIn を使って、 置換をかけます。
private val Reg = """_([a-z0-9])""".r
def byRegex(str: String): String = Reg.replaceAllIn(str, { m => m.group(1).toUpperCase } )
foldLeftを使った方法
こちらは、ググって見つけたいかにも関数型っぽい素敵なやり方です。
List は先頭への要素追加が高速なので、最後に reverse をかけて反転させています。
foldLeft の動きを理解していないと、よくわからないかもしれません。
タプルの1つ目はアキュムレータで、 List[Char] 型です。2つ目は個々の要素で Char 型です。
def byFold(str: String): String = {
str.toList.foldLeft(List.empty[Char]){
case ('_'::xs, c) => c.toUpper::xs
case (xs, c) => c::xs
}.reverse.mkString
}
zipを使った方法
大文字にするかどうかの判定にはひとつ前の文字が必要ということで、 List[Char] と、1文字シフトした List[Char] を zip して、ひとつ前の文字とのタプルを使って判定してみたものです。
def byZip(str: String): String = {
val chars = str.toList
val cs =
for ((c1, c2) <- chars zip (' ' :: chars) if c1 != '_' )
yield (c1, c2) match {
case (c, '_') => c.toUpper
case (c, _) => c
}
cs.mkString
}
処理イメージは以下です。(※は読み飛ばし)
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|---|
| 文字 | f | o | o | _ | b | a | r |
| 一つ前 | f | o | o | _ | b | a | |
| 結果 | f | o | o | ※ | B | a | r |
パフォーマンス
各方法について、手元の環境で計測した結果の比較です。
"check_performance_of_snake_to_camel_conversion" という文字列を10万回変換した時間です。
| 方法 | ミリ秒 |
|---|---|
| Iterator | 135 |
| Regex | 605 |
| foldLeft | 286 |
| zip | 367 |
やはり、正規表現が一番遅いですね。
Iteratorで回す方法が最速でした。
よりシンプルな実装方法や、高速な実装方法をご存知の方は是非コメントください!