-
-
Save sidharthkuruvila/3154845 to your computer and use it in GitHub Desktop.
/** | |
* Takes a camel cased identifier name and returns an underscore separated | |
* name | |
* | |
* Example: | |
* camelToUnderscores("thisIsA1Test") == "this_is_a_1_test" | |
*/ | |
def camelToUnderscores(name: String) = "[A-Z\\d]".r.replaceAllIn(name, {m => | |
"_" + m.group(0).toLowerCase() | |
}) | |
/* | |
* Takes an underscore separated identifier name and returns a camel cased one | |
* | |
* Example: | |
* underscoreToCamel("this_is_a_1_test") == "thisIsA1Test" | |
*/ | |
def underscoreToCamel(name: String) = "_([a-z\\d])".r.replaceAllIn(name, {m => | |
m.group(1).toUpperCase() | |
}) |
your algorithm doesn't correctly map _FOO_BAR
to _FooBar
and vice versa; here's my attempt at it:
def underscores2camel(name: String) = {
assert(!(name endsWith "_"), "names ending in _ not supported by this algorithm")
"[A-Za-z\\d]+_?|_".r.replaceAllIn(name, { x =>
val x0 = x.group(0)
if (x0 == "_") x0
else x0.stripSuffix("_").toLowerCase.capitalize
})
}
def camel2underscores(x: String) = {
"_?[A-Z][a-z\\d]+".r.findAllMatchIn(x).map(_.group(0).toLowerCase).mkString("_")
}
scala> camel2underscores("CamelCaseF")
res37: String = camel_case
@eallik, your algorithm returns "" for a non-camelcased string
scala> camel2underscores("foobar")
res1: String = ""
To avoid the leading _
:
def camelToUnderscores(name: String) = "[A-Z\\d]".r.replaceAllIn(name, {m =>
if(m.end(0) == 1){
m.group(0).toLowerCase()
}else {
"_" + m.group(0).toLowerCase()
}
})
Maybe a little less concise but I prefer this;
def camel2Underscore(text: String) = text.drop(1).foldLeft(text.headOption.map(_.toLower + "") getOrElse "") {
case (acc, c) if c.isUpper => acc + "_" + c.toLower
case (acc, c) => acc + c
}
I had to support some cases where words were all upper case:
/**
* Converts from camelCase to snake_case
* e.g.: camelCase => camel_case
*
* @param name the camelCase name to convert
* @return snake_case version of the string passed
*/
def camelToSnake(name: String): String = {
@tailrec
def go(accDone: List[Char], acc: List[Char]): List[Char] = acc match {
case Nil => accDone
case a::b::c::tail if a.isUpper && b.isUpper && c.isLower => go(accDone ++ List(a, '_', b, c), tail)
case a::b::tail if a.isLower && b.isUpper => go(accDone ++ List(a, '_', b), tail)
case a::tail => go(accDone :+ a, tail)
}
go(Nil, name.toList).mkString.toLowerCase
}
"camelToSnake" should "process right camel case + all upper case + mixed" in {
camelToSnake("COLUMN") shouldBe "column"
camelToSnake("someColumnNameRespectingCamel") shouldBe "some_column_name_respecting_camel"
camelToSnake("columnWITHSomeALLUppercaseWORDS") shouldBe "column_with_some_all_uppercase_words"
}
@dmateusp 👍 exactly what I needed! Would be great if worked with numeric text:
actual: DirectlyEmployedOr1099Resources => directly_employed_or1099resources
expected: DirectlyEmployedOr1099Resources => directly_employed_or_1099_resources
@dmateusp FYI, that causes a problem with things like camelToSnake("IsAString") => is_astring, when it should be is_a_string.
I prefer not to use regex, this approach runs in n time, it's tail recursive and does not convert consecutive upper chars, e.g. ThisIsCAmel -> this_is_camel:
def camel2Underscore(s: String): String = {
@tailrec def camel2Underscore(s: String, output: String, lastUppercase: Boolean): String =
if (s.isEmpty) output
else {
val c = if (s.head.isUpper && !lastUppercase) "_" + s.head.toLower else s.head.toLower
camel2Underscore(s.tail, output + c, s.head.isUpper && !lastUppercase)
}
camel2Underscore(s, "", true)
}
@ruloweb This is nice but won't work if the two consecutive uppercase chars are at the start (ie. THisIsCamel) or if there are greater than two consecutive uppercase characters elsewhere (ie. ThisIsCAMel). Consequently, this will fail for converting all caps as well.
I adjusted the code to support the all caps case:
def camel2Snake(str: String): String = {
@tailrec
def camel2SnakeRec(s: String, output: String, lastUppercase: Boolean): String =
if (s.isEmpty) output
else {
val c = if (s.head.isUpper && !lastUppercase) "_" + s.head.toLower else s.head.toLower
camel2SnakeRec(s.tail, output + c, s.head.isUpper && !lastUppercase)
}
if (str.forall(_.isUpper)) str.map(_.toLower)
else {
camel2SnakeRec(str, "", true)
}
}
@amackillop, it doesnt't work for "THISIsADog" (it gives "t_hi_sis_adog")
i suggest the following code :
def camel2Snake(str: String): String = {
val headInUpperCase = str.takeWhile(c => c.isUpper || c.isDigit)
val tailAfterHeadInUppercase = str.dropWhile(c => c.isUpper || c.isDigit)
if (tailAfterHeadInUppercase.isEmpty) headInUpperCase.toLowerCase else {
val firstWord = if (!headInUpperCase.dropRight(1).isEmpty) {
headInUpperCase.last match {
case c: Any if (c.isDigit) => headInUpperCase
case _ => headInUpperCase.dropRight(1).toLowerCase
}
} else {
headInUpperCase.toLowerCase + tailAfterHeadInUppercase.takeWhile(c => c.isLower)
}
if (firstWord == str.toLowerCase) {
firstWord
} else {
s"${firstWord}_${camel2Snake(str.drop(firstWord.length))}"
}
}
}
@0dilon you version stack-overflows if the camelCase already contains an underscore.
e.g. camel2Snake("foo_BarBaz")
Late to the party, but iterate w/ Char
s seems fine too if you're ok w/ no handling -
s in the String
s
val camelToKebab: String => String = _.foldLeft("") {
case (acc, chr) if chr.isUpper => acc :+ '-' :+ chr.toLower
case (acc, chr) => acc :+ chr
}
val camelToSnake : String => String = _.foldLeft( "" ){ (acc, c) =>
( c.isUpper, acc.isEmpty, acc.takeRight(1) == "_" ) match {
case (true, false, false) => acc + "_" + c.toLower
case (true, _, _) => acc + c.toLower
case (false, _, _) => acc + c
}
}
You shouldn't include digits when converting between camel and underscores, by definition there is no such thing as an upper case digit, and therefore it doesn't make sense to consider a digit a capitalised separate word.