34
\$\begingroup\$

The Challenge

I was reading Google's Java Style Guide the other day and stumbled over their algorithm to convert any arbitrary string into camelCase notation. In this challenge you have to implement this algorithm since you don't want to do all this stuff in your head when you are writing your super competitive Java submissions to code-golf challenges.

Note: I made some small adjustments to the algorithm. You need to use the one specified below.

The algorithm

You start with an arbitrary input string and apply the following operations to it:

  1. Remove all apostrophes `'
  2. Split the result into words by splitting at
  • characters that are not alphanumerical and not a digit [^a-zA-Z0-9]
  • Uppercase letters which are surrounded by lowercase letters on both sides. abcDefGhI jk for example yields abc Def Ghi jk
  1. Lowercase every word.
  2. Uppercase the first character of every but the first word.
  3. Join all words back together.

Additional notes

  • The input will only contain printable ASCII.
  • If a digit is the first letter in a word, leave it as it is and don't capitalize something else in this word.
  • The input will always have at least one character.

Rules

Test cases

"Programming Puzzles & Code Golf" -> "programmingPuzzlesCodeGolf"
"XML HTTP request" -> "xmlHttpRequest"
"supports IPv6 on iOS?" -> "supportsIpv6OnIos"
"SomeThing w1th, apo'strophe's and' punc]tuation" -> "someThingW1thApostrophesAndPuncTuation"
"nothing special" -> "nothingSpecial"
"5pecial ca5e" -> "5pecialCa5e"
"1337" -> "1337"
"1337-spEAk" -> "1337Speak"
"whatA mess" -> "whataMess"
"abcD" -> "abcd"
"a" -> "a"
"B" -> "b"

Happy Coding!

\$\endgroup\$
9
  • 3
    \$\begingroup\$ Interesting, I never knew this was called "camelCase". Name is fitting I suppose... \$\endgroup\$ Commented Mar 3, 2016 at 4:53
  • 4
    \$\begingroup\$ There are more: snake_case & PascalCase \$\endgroup\$
    – Martijn
    Commented Mar 3, 2016 at 8:50
  • 14
    \$\begingroup\$ @Martijn snake_case because of Python, of course. FORTH also has FORTHCASE and APL has unreadable in any case \$\endgroup\$
    – cat
    Commented Mar 3, 2016 at 14:40
  • \$\begingroup\$ Test case 4 should have ApostropheS in the output. \$\endgroup\$
    – Titus
    Commented Nov 10, 2016 at 16:45
  • \$\begingroup\$ @Titus No, it is correct. Apostrophes are removed before the input is splitted. \$\endgroup\$
    – Denker
    Commented Nov 10, 2016 at 21:42

14 Answers 14

13
\$\begingroup\$

Retina, 56 bytes

Byte count assumes ISO 8859-1 encoding.

T`'\`
S_`\W|_|(?<=[a-z])(?=[A-Z][a-z])
T`L`l
T`l`L`¶.
¶

Try it online!

Explanation

This implements the specification quite literally:

T`'\`

Remove apostrophes and backticks.

S_`\W|_|(?<=[a-z])(?=[A-Z][a-z])

Split the string around non-word characters (in regex this also excludes digits and underscores), or underscores or positions that have a lower case letter on the left and and upper case, lower case on the right. This would create some empty segments when there are two non-letter, non-digit characters in a row, or more important at the beginning of the string. We get rid of those with the _ option. Here, "splitting" means put each remaining part on its own line.

T`L`l

Convert everything to lower case.

T`l`L`¶.

Convert each character that occurs after the linefeed to upper case. This will conveniently skip the first word because there's no linefeed in front of it.

Get rid of the linefeeds to join everything back together.

\$\endgroup\$
6
  • \$\begingroup\$ You beat me to it. Nice one! \$\endgroup\$
    – mbomb007
    Commented Mar 2, 2016 at 20:01
  • \$\begingroup\$ This question may be a bit weird, but...should I post my answer if it's shorter than yours and also in Retina? I was working on it before your answer appeared, but then it did and now I don't know if I should post it. \$\endgroup\$
    – daavko
    Commented Mar 2, 2016 at 20:11
  • 5
    \$\begingroup\$ @daavko Sure, post it (I usually decide based on how different the approach is to the existing answer... if it's the exact same thing with a byte shaved off somewhere I normally just comment on that answer, but if it's a lot shorter of a different approach, then I'd just post a separate answer). \$\endgroup\$ Commented Mar 2, 2016 at 20:12
  • 2
    \$\begingroup\$ @daavko The lookaround is necessary though. Note that your answer doesn't retain the capitalisation of Thing although it should. \$\endgroup\$ Commented Mar 2, 2016 at 20:19
  • 1
    \$\begingroup\$ @MartinBüttner Oh...I didn't notice that. Oh well, I'll successfully answer some other challenge, then. \$\endgroup\$
    – daavko
    Commented Mar 2, 2016 at 20:22
10
\$\begingroup\$

Java, 198 190 bytes

+3 bytes because I forgot that \W+ == [^a-zA-Z0-9_]+ and I need to match [^a-zA-Z0-9]+

-11 bytes thanks to user20093 - ?: instead of if/else

Because, Java.

s->{String[]a=s.replaceAll("`|'","").split("[\\W_]+|(?<=[a-z])(?=[A-Z][a-z])");s="";for(String w:a){String t=w.toLowerCase();s+=a[0]==w?t:t.toUpperCase().charAt(0)+t.substring(1);}return s;}

This is a lambda. Call like so:

UnaryOperator<String> op = s->{String[]a=s.replaceAll("`|'","").split("[\\W_]+|(?<=[a-z])(?=[A-Z][a-z])");s="";for(String w:a){String t=w.toLowerCase();s+=a[0]==w?t:t.toUpperCase().charAt(0)+t.substring(1);}return s;};
System.out.println(op.apply("Programming Puzzles & Code Golf"));

Readable version:

public static String toCamelCase(String s) {
    String[] tokens = s
            .replaceAll("`|'", "") // 1. Remove all apostrophes
            .split("[\\W_]+|(?<=[a-z])(?=[A-Z][a-z])"); // 2. Split on [\W_]+ or between [a-z] and [A-Z][a-z]
    s = ""; // Reusing s for building output is cheap
    for (String token : tokens) {
        String lowercaseToken = token.toLowerCase(); // 3. Lowercase every word
        s += tokens[0].equals(token)?lowercaseToken:lowercaseToken.toUpperCase().charAt(0) + lowercaseToken.substring(1); // 4. Uppercase first char of all but first word
        // ^ 5. Join all words back together
    }
    return s;
}
\$\endgroup\$
5
  • 1
    \$\begingroup\$ It's not Swift... \$\endgroup\$ Commented Mar 3, 2016 at 4:17
  • 2
    \$\begingroup\$ Welcome to Programming Puzzles & Code Golf! This is a nice first answer! \$\endgroup\$
    – Alex A.
    Commented Mar 3, 2016 at 4:28
  • 1
    \$\begingroup\$ @CatsAreFluffy What? \$\endgroup\$
    – cat
    Commented Mar 3, 2016 at 14:49
  • \$\begingroup\$ if you replace conditional statement(if/else) with conditional expression (?:) you could save around 9 bytes \$\endgroup\$
    – user902383
    Commented Mar 4, 2016 at 11:55
  • \$\begingroup\$ Don't know how I missed that @user902383 - added for -11 bytes. Unfortunately I had to add 3 as well to match _ as a token delimiter. \$\endgroup\$
    – CAD97
    Commented Mar 4, 2016 at 19:12
9
\$\begingroup\$

JavaScript (ES6), 156 154 152 148 145 141 140 bytes

Thanks @Neil (6 bytes), @ETHproductions (3 bytes), and @edc65 (7 bytes)

a=>a[r='replace'](/`|'/g,a='')[r](/[a-z](?=[A-Z][a-z])/g,'$& ')[r](/[^\W_]+/g,b=>a+=(a?b[0].toUpperCase():'')+b.slice(!!a).toLowerCase())&&a

Removes apostrophes, then does a replace to split on special characters/before surrounded capitals, then combines with proper casing. Unfortunately, toLowerCase() and toUpperCase() are annoyingly long and hard to avoid here...

\$\endgroup\$
14
  • 1
    \$\begingroup\$ I was working on a different approach which your b.slice(i>0) approach blows out of the water, but in the mean time my match regex of /[A-Z]?([a-z0-9]|[0-9A-Z]{2,})+([A-Z](?![a-z]))?/g does appear to save 2 bytes over your otherwise ingenious replace approach. \$\endgroup\$
    – Neil
    Commented Mar 2, 2016 at 20:28
  • 1
    \$\begingroup\$ Or I could just save 2 bytes on your replace directly: replace(/[a-z](?=[A-Z][a-z])/g,'$& ') \$\endgroup\$
    – Neil
    Commented Mar 2, 2016 at 20:30
  • 1
    \$\begingroup\$ Usually match...map can be replaced with replace \$\endgroup\$
    – edc65
    Commented Mar 2, 2016 at 21:45
  • 1
    \$\begingroup\$ @edc65 I get a minimum of 160 bytes with that approach: a=>a.replace(/`|'/g,'').replace(/[a-z](?=[A-Z][a-z])/g,'$& ').replace(/[\W_]*([a-z0-9]+)[\W_]*/gi,(_,b,i)=>(i?b[0].toUpperCase():'')+b.slice(i>0).toLowerCase()) \$\endgroup\$ Commented Mar 2, 2016 at 21:50
  • 2
    \$\begingroup\$ On the other hand, I would like to offer b=>a+=(a?b[0].toUpperCase():'')+b.slice(!!a).toLowerCase() which I believe saves you another 4 bytes. \$\endgroup\$
    – Neil
    Commented Mar 3, 2016 at 0:28
6
\$\begingroup\$

vim, 69 68 66

:s/[`']//g<cr>:s/[a-z]\zs\ze[A-Z][a-z]\|\W\|_/\r/g<cr>o<esc>guggj<C-v>GgU:%s/\n<cr>

vim shorter than Perl?! What is this madness?

:s/[`']//g<cr>           remove ` and '
:s/                      match...
 [a-z]\zs\ze[A-Z][a-z]   right before a lowercase-surrounded uppercase letter
 \|\W\|_                 or a non-word char or underscore
 /\r/g<cr>               insert newlines between parts
o<esc>                   add an extra line at the end, necessary later...
gugg                     lowercasify everything
j                        go to line 2 (this is why we added the extra line)
<C-v>G                   visual select the first char of all-but-first line
gU                       uppercase
:%s/\n<cr>               join all lines into one

Thanks to Neil for spotting a useless keystroke!

\$\endgroup\$
6
  • \$\begingroup\$ I can see why the last :s has a % but why the inconsistency in the first two? \$\endgroup\$
    – Neil
    Commented Mar 3, 2016 at 0:30
  • \$\begingroup\$ @Neil Bah, muscle memory. Thanks! \$\endgroup\$
    – Doorknob
    Commented Mar 3, 2016 at 0:53
  • 5
    \$\begingroup\$ Manages to be less readable than Perl, too +1 \$\endgroup\$
    – cat
    Commented Mar 3, 2016 at 14:41
  • \$\begingroup\$ I'm totally adding this to my .vimrc \$\endgroup\$
    – moopet
    Commented Mar 4, 2016 at 10:58
  • 1
    \$\begingroup\$ @fruglemonkey 1. :%j<cr> is equivalent and shorter. 2. That adds spaces between lines. \$\endgroup\$
    – Doorknob
    Commented Mar 5, 2016 at 5:36
5
\$\begingroup\$

Mathematica 10.1, 101 bytes

""<>(ToCamelCase@{##2}~Prepend~ToLowerCase@#&@@StringCases[StringDelete[#,"`"|"'"],WordCharacter..])&

Uses the undocumented ToCamelCase, which works similarly to Capitalize but sets other characters to lowercase.

\$\endgroup\$
5
  • \$\begingroup\$ Not in 10.3.0.. \$\endgroup\$
    – A Simmons
    Commented Mar 3, 2016 at 14:04
  • \$\begingroup\$ Is ToCamelCase[n_,m_]:=n<>Capitalize/@m correct? Seems like it. And why use Prepend when #~ToCamelCase~{##2} works? \$\endgroup\$ Commented Mar 3, 2016 at 17:29
  • \$\begingroup\$ @CatsAreFluffy That gives me ToCamelCase::argx: ToCamelCase called with 2 arguments; 1 argument is expected. \$\endgroup\$ Commented Mar 3, 2016 at 22:23
  • \$\begingroup\$ Well, how does CamelCase work? Just ToCamelCase[n_]:=""<>Capitalize/@n? \$\endgroup\$ Commented Mar 4, 2016 at 16:22
  • \$\begingroup\$ @CatsAreFluffy, see this. \$\endgroup\$ Commented Mar 5, 2016 at 1:02
4
\$\begingroup\$

Julia, 98 89 bytes

s->lcfirst(join(map(ucfirst,split(replace(s,r"['`]",""),r"[a-z]\K(?=[A-Z][a-z])|\W|_"))))

This is an anonymous function that accepts a string and returns a string. To call it, assign it to a variable.

The approach here is the same as in Doorknob's Perl answer: replace apostrophes and backticks with the empty string, split into an array on a regular expression that matches the necessary cases, map the ucfirst function over the array to uppercase the first letter of each element, join the array back into a string, and lcfirst the result to convert the first character to lowercase.

\$\endgroup\$
1
  • \$\begingroup\$ I've always liked Julia as a more functional, more interesting Python but I hate the end syntax. Maybe I'll just use anonymous functions for everything, then I never have to type end :D \$\endgroup\$
    – cat
    Commented Mar 3, 2016 at 14:51
3
\$\begingroup\$

Perl 67 + 1 = 68 bytes

y/'`//d;s/([a-z](?=[A-Z][a-z]))|\W|_/$1 /g;$_=lc;s/^ +| +(.)/\u$1/g

Requires the -p flag, and -l for multi line:

$ perl -pl camelCase.pl input.txt
programmingPuzzlesCodeGolf
xmlHttpRequest
supportsIpv6OnIos:
someThingW1thApostrophesAndPuncTuation
nothingSpecial
5pecialCa5e
1337
1337Speak
abcd

How it works:

y/'`//d;                            # Remove ' and `
s/([a-z](?=[A-Z][a-z]))|\W|_/$1 /g; # Replace according to '2. Split...' this will create
                                    #   a space separated string.
$_=lc;                              # lower case string
s/^ +| +(.)/\u$1/g                  # CamelCase the space separated string and remove any
                                    #   potential leading spaces.
\$\endgroup\$
2
\$\begingroup\$

Lua, 127 Bytes

t=''l=t.lower z=io.read()for x in z:gmatch('%w+')do t=t..(t==''and l(x:sub(1,1))or x:sub(1,1):upper())..l(x:sub(2))end return t

Accepts a string from stdin and returns camelized results.

Probably still gonna look for a better solution as storing everything in a variable feels inefficient.

But anyhow, pretty simple in general:

 z:gmatch('%w+')

This is the beauty that saved me a bit of bytes. gmatch will split the string based on the pattern: %w+ which grabs only alphanumerics.

After that it's simple string operations. string.upper, string.lower and done.

\$\endgroup\$
2
\$\begingroup\$

PHP, 145 122 133 bytes

<?=join(split(" ",lcfirst(ucwords(strtolower(preg_replace(["#`|'#","#\W|_#","#([a-z])([A-Z][a-z])#"],[""," ","$1 $2"],$argv[1]))))));

Save to file, call from CLI.
Takes input from a single command line argument; escape quotes and whitespace where necessary.

breakdown

<?=                 // 9. print result
join(split(" ",     // 8. remove spaces
    lcfirst(        // 7. lowercase first character
    ucwords(        // 6. uppercase first character in every word
    strtolower(     // 5. lowercase everything
    preg_replace(
        ["#`|'#",   "#\W|_#",   "#([a-z])([A-Z][a-z])#"],
        ["",        " ",        "$1 $2"],
        // 2. replace apostrophes with empty string (remove them)
                    // 3. replace non-word characters with space
                                // 4. insert space before solitude uppercase
        $argv[1]    // 1. take input from command line
    ))))
));

lcfirst allowed to reduce this to a single command, saving 23 bytes.
Fixing the apostrophes cost 11 bytes for the additional replace case.

\$\endgroup\$
1
\$\begingroup\$

Perl, 87 80 78 bytes

y/'`//d;$_=join'',map{ucfirst lc}split/[a-z]\K(?=[A-Z][a-z])|\W|_/,$_;lcfirst

Byte added for the -p flag.

First, we use the y/// transliteration operator to delete all '` characters in the input:

y/'`//d;

Then comes the meat of the code:

                         split/[a-z]\K(?=[A-Z][a-z])|\W|_/,$_;

(split the input string $_ in the appropriate locations, using the fancy \K in the match string to exclude the portion preceding it from the actual match)

          map{ucfirst lc}

(map over each split portion of the string and make the entire string lowercase, then make the first character of the modified string uppercase)

$_=join'',

(join on empty string and re-assign to magic underscore $_, which gets printed at the end)

Finally, we lowercase the first letter by regex-matching it and using \l in the replacement string with a builtin, saving 2 bytes over the previous method:

lcfirst

Thanks to @MartinBüttner for 7 bytes ([^a-zA-Z\d] -> \W|_)!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ How I envy that \K... ;) \$\endgroup\$ Commented Mar 2, 2016 at 20:25
1
\$\begingroup\$

Kotlin, 160 Bytes

fun a(s: String)=s.replace(Regex("['`]"),"").split(Regex("[\\W_]+|(?<=[a-z])(?=[A-Z][a-z])")).map{it.toLowerCase().capitalize()}.joinToString("").decapitalize()

My goal was to be Scala, the other "alternative Java", so I'm somewhat happy with my results. I stole the regex from the Java answer.

Test it with:

fun main(args: Array<String>) {
    val testCases = arrayOf(
            "Programming Puzzles & Code Golf",
            "XML HTTP request",
            "supports IPv6 on iOS?",
            "SomeThing w1th, apo'strophe's and' punc]tuation",
            "nothing special",
            "5pecial ca5e",
            "1337",
            "1337-spEAk",
            "abcD",
            "a",
            "B")
    testCases.forEach { println(a(it)) }

}
\$\endgroup\$
2
  • \$\begingroup\$ At this point I think everyone is "borrowing" the optimized regex \W|_|(?<=[a-z])(?=[A-Z][a-z]) or slightly modifying it eg. [\W_]+ \$\endgroup\$
    – CAD97
    Commented Mar 4, 2016 at 20:54
  • \$\begingroup\$ you can save some on map and extension function fun String.a()=replace(Regex("['`]"),"").split(Regex("[\\W_]+|(?<=[a-z])(?=[A-Z][a-z])")).joinToString(""){it.toLowerCase().capitalize()}.decapitalize() \$\endgroup\$
    – poss
    Commented Jan 18, 2019 at 11:46
1
\$\begingroup\$

Scala, 181 170 144

def f(s:String)={val l=s.replaceAll("'|`","")split("[\\W_]+|(?<=[a-z])(?=[A-Z][a-z])")map(_.toLowerCase);l(0)+l.tail.map(_.capitalize).mkString}

Tester:

val testCases = List(
  "Programming Puzzles & Code Golf" -> "programmingPuzzlesCodeGolf",
  "XML HTTP request" -> "xmlHttpRequest"
  // etc
)
println(testCases.map(t=>if(t._2!=f(t._1))s"FAIL:${f(t._1)}"else"PASS").mkString("\n"))

Props to CAD97 and apologies to Nathan Merrill :)

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can save 6 bytes by replacing [^a-zA-Z0-9]+ with [\\W_]+. \$\endgroup\$
    – CAD97
    Commented Mar 4, 2016 at 20:53
0
\$\begingroup\$

C 272 characters

C program pass string to camelCase in quotes as argument 1. There are lot's of gotchas in this problem statement...

#define S strlen(t)
#define A isalnum(t[i])
j=0;main(i,v)char**v;{char*p=v[1],*t;char o[99]={0};while(t=strtok(p," [{(~!@#$%^*-+=)}]")){i=0;p+=S+1;while((!A)&&i<S)i++;if(i!=S){o[j]=((j++==0)?tolower(t[i++]):toupper(t[i++]));while(i<S){if(A)o[j++]=t[i];i++;}}}puts(o);}
\$\endgroup\$
5
  • \$\begingroup\$ You need to #include<string.h> for strlen, strtok, and toupper, and #include<ctype.h> for isalnum. \$\endgroup\$
    – user45941
    Commented Mar 4, 2016 at 22:03
  • \$\begingroup\$ I didn't need it using gcc 3.4.4 in cygwin. They must be automatically linked in, assuming extern int. \$\endgroup\$
    – cleblanc
    Commented Mar 4, 2016 at 22:05
  • \$\begingroup\$ With ./camel "Programming Puzzles & Code Golf" on cygwin (compiled with gcc 3.4.4), I get programmingPuzzlesCodeEGolf. Same output with 5.3.0. \$\endgroup\$
    – user45941
    Commented Mar 4, 2016 at 22:08
  • \$\begingroup\$ Crap. me too. I must've created a bug while golfing it. I'm looking at it now... \$\endgroup\$
    – cleblanc
    Commented Mar 4, 2016 at 22:12
  • \$\begingroup\$ The problem was I added the other tokenizer strings after golfing and didn't test it well enough. If you remove the '&' from the strtok call it works on that input. \$\endgroup\$
    – cleblanc
    Commented Mar 4, 2016 at 22:26
0
\$\begingroup\$

JavaScript, 123 bytes

v=>v[r="replace"](/[`']/g,"")[r](/^.|.$|[A-Z][^a-z]+/g,x=>x.toLowerCase())[r](/[^a-z0-9]+./ig,x=>x.slice(-1).toUpperCase())

Readable version

v=>
  v.replace(/[`']/g,"")
  .replace(/^.|.$|[A-Z][^a-z]+/g,x=>x.toLowerCase())
  .replace(/[^a-z0-9]+./ig,x=>x.slice(-1).toUpperCase())

Remove the apostrophes, make the first character lower case, the last character lowercase, and any grouping of multiple uppercase characters, match any group of 1 or more non-alphanumeric chars + 1 other character, replace with that last character capitalized.

[r="replace"] trick from Mrw247's solution.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.