26
\$\begingroup\$

In that Coding Golf, you should convert one coding convention with TitleCase to lower_case_with_underscores. And... vice versa!

Specification

Change the casing in a following way:

  • If underscore character is a delimiter, change the casing to Title Case without any of delimiter.
  • If there are multiple words with no delimiter, change the casing to lower case and add an underscore character as a delimiter.
  • In case of only one word (or one character): change the casing to Title Case if the word starts with lower case; change the casing to lower case if the word starts with the upper case.

Allowed characters:

  • A to Z
  • a to z
  • underscore (_).

Input with mixed cased words are disallowed. Examples of disallowed cases:

  • Coding_Convention_Conversion
  • a_BC

Example Cases

Input                        | Expected Output
===========================================================
CodingConventionConversion   | coding_convention_conversion
coding_convention_conversion | CodingConventionConversion
abc                          | Abc
Abc                          | abc
ABC                          | a_b_c
a_b_c                        | ABC
a                            | A
A                            | a

Rules

  • It is allowed to use ToUpper, ToLower and ToTitleCase functions.
  • Using regular expressions is allowed.
  • : shortest code in bytes wins!
\$\endgroup\$
4
  • \$\begingroup\$ Is using a ToTitleCase function okay? You didn't specify, so I'd assume it is okay. \$\endgroup\$
    – Justin
    Commented Jan 26, 2016 at 6:55
  • \$\begingroup\$ @Justin: Good question indeed. Let's make it more fun and disallow the ToTitleCase function :) \$\endgroup\$ Commented Jan 26, 2016 at 7:04
  • \$\begingroup\$ Darn... my solution relies on it \$\endgroup\$
    – Justin
    Commented Jan 26, 2016 at 7:06
  • 1
    \$\begingroup\$ @Justin: Okay - I didn't specify it in the beginning, so in that case - let's allow it anyway. \$\endgroup\$ Commented Jan 26, 2016 at 7:08

19 Answers 19

8
\$\begingroup\$

Jolf, 35 bytes

Saves 1 byte thanks to @Cᴏɴᴏʀ O'Bʀɪᴇɴ. This is encoded in ISO 8859-7.

? hI'_ΜGI'_dpyH0pxRGIL0"(?=[A-Z])'_

Woohoo my first Jolf program!

Explanation

   // I = input
? hI'_                              // If input contains _
       GI'_                          // Split on _
      Μ    d                         // Loop, then join
            pyH0                     // Make the first character uppercase
                                    // ELSE...
                  RGIL0"(?=[A-Z])    // Split *after* all uppercase chars
                                 '_  // join with _ 
                px                   //Make lowercase

Try it online

\$\endgroup\$
2
  • \$\begingroup\$ You can use string separation at the end, so it becomes "(?=[A-Z])'_. The string is closed automatically. \$\endgroup\$ Commented Jan 26, 2016 at 0:54
  • \$\begingroup\$ @CᴏɴᴏʀO'Bʀɪᴇɴ oh, cool, thanks! \$\endgroup\$
    – Downgoat
    Commented Jan 26, 2016 at 0:55
7
\$\begingroup\$

Retina, 37

Thanks to @ MartinBüttner for saving 4 bytes!

^|[A-Z]
_$0
T`Ll`lL`_.
^_|_(?=[A-Z])

(Note the trailing newline.)

Try it online. Note this includes extra m` to configure a couple of lines to treat each input line separately so all testcases may be run in one go. This is not a requirement of the question, so these are not counted in the score.

  • Lines 1 and 2 insert _ either at the beginning of input or before uppercase letters. All words are now _-separated, regardless of case.
  • Line 3 swaps case of the first letter in each word.
  • Lines 4 and 5 remove _ either at the start of input, or when followed by an uppercase letter.
\$\endgroup\$
3
  • \$\begingroup\$ This saves four bytes: retina.tryitonline.net/… \$\endgroup\$ Commented Jan 26, 2016 at 7:43
  • \$\begingroup\$ Also you can avoid the trailing empty line by omitting the last ?= and replacing that stage with $1 (doesn't affect the byte count though). \$\endgroup\$ Commented Jan 26, 2016 at 7:44
  • \$\begingroup\$ @Martin Very good - thanks! \$\endgroup\$ Commented Jan 26, 2016 at 16:11
5
\$\begingroup\$

GNU Sed, 46

Thanks to @TobySpeight for saving 2 bytes!

Score includes +1 for -E (or -r) option to sed.

s/(^|_)([a-z])/\u\2/g
t
s/[A-Z]/_\l&/g
s/^_//

Try it online.

Fairly straightforward sed:

  • Line 1 substitutes beginning of line or _, followed by a lowercase letter with the upper case of that letter. The g flag to s performs this substitution for each instance found
  • t jumps to the : unnamed label if there were any matches for the above substitution. This label is implicitly at the end.
  • Otherwise all uppercase letters are substituted with _ the lower case of that letter
  • This leaves a leading _ before the first letter. s/^_// removes that.
\$\endgroup\$
3
  • 1
    \$\begingroup\$ @Toby Thanks. -E works in my GNU sed 4.2.2 (Ubuntu 14.04.3), though its not in the man page. I read somewhere [citation needed] that -E is newer Posix option that will officially be added to GNU Sed in a newer release, but is already there unofficially. Regardless, -r does the right thing if -E doesn't work for you. \$\endgroup\$ Commented Jan 26, 2016 at 16:19
  • \$\begingroup\$ @Toby lines 280-282 of sed/sed.c are /* Undocumented, for compatibility with BSD sed. */ case 'E': case 'r':. \$\endgroup\$ Commented Jan 26, 2016 at 17:00
  • \$\begingroup\$ @Digital - I was mistaken; my sed does accept -E as a synonym for -r. I wasn't correctly passing a minimal program e.g. sed -E -e Q \$\endgroup\$ Commented Jan 26, 2016 at 18:03
4
\$\begingroup\$

Pyth, 25 bytes 29 33 35 40

Saved 2 bytes thanks to @Dennis

Saved 4 bytes thanks to @FryAmTheEggman

?rIz0smrd4cz\_tsXzrG1*\_G

Try it online

\$\endgroup\$
4
  • \$\begingroup\$ Your link needs to be updated. \$\endgroup\$
    – isaacg
    Commented Jan 26, 2016 at 6:36
  • \$\begingroup\$ When I try put "abc" as an input, it yields "bc" as an output. Bug? :) \$\endgroup\$ Commented Jan 26, 2016 at 9:39
  • \$\begingroup\$ To fix what @DariuszWoźniak noticed, you can change your condition from /z\_ to rIz0. I also believe I found a same-length alternative to the adding underscores program: tsXzrG1_Mcj\_G2, maybe someone can golf it more... \$\endgroup\$ Commented Jan 26, 2016 at 18:16
  • \$\begingroup\$ Ah, found it: tsXzrG1*\_G \$\endgroup\$ Commented Jan 26, 2016 at 18:29
4
\$\begingroup\$

JavaScript (ES6), 87 bytes

s=>s.replace(/[A-Z]|(^|_)(.)/g,(c,_,l,i)=>l?l.toUpperCase():(i?"_":"")+c.toLowerCase())

Explanation

Depending on which part of the regex matched, it replaces the match with the opposite case.

s.replace(
  /[A-Z]|(^|_)(.)/g,
  (c,_,l,i)=>
    l?
      (i?"_":"")+c.toLowerCase()
    :l.toUpperCase()
)

Test

var solution = s=>s.replace(/[A-Z]|(^|_)(.)/g,(c,_,l,i)=>l?l.toUpperCase():(i?"_":"")+c.toLowerCase())
<input type="text" id="input" value="coding_convention_conversion" />
<button onclick="result.textContent=solution(input.value)">Go</button>
<pre id="result"></pre>

\$\endgroup\$
3
\$\begingroup\$

PHP 160 bytes

not the shortest but for completeness here my solution in PHP, $s holds the string to convert:

trim(preg_replace_callback('/((^[a-z]|_[a-z])|([A-Z]))/',function($m){return empty($m[2])?'_'.strtolower($m[3]):strtoupper(str_replace('_','',$m[2]));},$s),'_')
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Welcome to Programming Puzzles and Code Golf Stack Exchange. Well done for posting something in a language that you knew wasn't going to win. code-golf challenges are mostly within languages, so using a non-golfing language is good. +1 d:-D \$\endgroup\$
    – wizzwizz4
    Commented Jan 28, 2016 at 17:04
2
\$\begingroup\$

Ruby, 101 87 75 bytes

->s{s.gsub(/^.|[A-Z]/,'_\0').gsub(/_./,&:swapcase).gsub(/_(?=[A-Z])|^_/,'')}

Unfortunately, this does exactly the same thing as the Retina solution, as that method ended up being shorter than anything else I came up with.

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

Python 3, 130 bytes

Quick and dirty attempt using regex to split at the caps. Pretty brute force: if anyone can come up with a different approach I'm sure this can be beaten.

import re
lambda s:('_'.join(re.findall('[A-Z][a-z]*',s)).lower(),''.join([a[0].upper()+a[1:]for a in s.split('_')]))[s.islower()]
\$\endgroup\$
0
2
\$\begingroup\$

𝔼𝕊𝕄𝕚𝕟 3, 15 chars / 32 bytes (noncompetitive)

⟮ѨDZï⟯≠ï?Ⅰ:ѨȎѨƎï

Try it here (Firefox only).

v3 was released after this challenge, with a bunch of bugfixes and library updates.

Explanation

This is just a mashup of builtins.

⟮ѨDZï⟯≠ï?Ⅰ:ѨȎѨƎï // implicit: ï=input
⟮ѨDZï⟯≠ï?        // check if ï is NOT in snake_case
       Ⅰ       // if so, then convert to snake_case
        :ѨȎѨƎï // otherwise, convert to camelCase and make the first letter UPPERCASE
\$\endgroup\$
2
\$\begingroup\$

Jelly, 24 bytes

e€ØAḊkŒlj”_$ʋ⁾_ yŒtḲƲ}Ẹ?

Try it online!

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

Python 3, 86 bytes

lambda s,u='_':''.join([u[i>u:]+i.lower()for i in(s<u)*s]or[u]+s.title().split(u))[1:]

Try it online!

Also works in Python 2.

Making use of the convenient fact that the ascii value for _ (95) is right in between those of the uppercase (65-90) and lowercase (97-122) letters, which allows for easy string comparisons.

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

Perl 6,  73 72 71  68 bytes

{.comb(/<:Lu><:Ll>*|<:Ll>+/).map({/<:Lu>/??.lc!!.tc}).join('_'x?/<:Lu>/)} # 73
{.comb(/<:Lu><:Ll>*|<:L>+/).map({/<:Lu>/??.lc!!.tc}).join('_'x?/<:Lu>/)}  # 72
{/<:Lu>/??S:g/(^)?(<:Lu>)/{$0||'_'}$1.lc()/!!S:g/[^|_](<:Ll>)/$0.tc()/}   # 71
{.comb(/<:Lu><:Ll>*|<:L>+/).map({/<:Lu>/??.lc!!.tc}).join('_'x!/_/)}      # 68

Usage:

# give it a lexical name
my &code = {...}

for <CodingConventionConversion coding_convention_conversion abc Abc ABC a_b_c a A>
{ say .&code }
coding_convention_conversion
CodingConventionConversion
Abc
abc
a_b_c
ABC
A
a

Explanation:

{
  .comb( / <:Lu><:Ll>* | <:L>+ / ) # grab the "words" only
  .map({
      /<:Lu>/ # if the word has uppercase
    ??
      .lc     # lowercase the whole word
    !!
      .tc     # otherwise titlecase the word
   })
  .join(  # join the words
    '_'   # with '_'
    x     # repeated
    !/_/  # zero times if it had a _, otherwise once
  )
}

You may be wondering why I used the Unicode properties (<:Lu>, <:Ll>) instead of just a character class. In Perl 6 they are no longer spelled [a-z] they are spelled <[a..z]> which is 1.6 times as big. The brackets [ … ] are used for non-capturing grouping instead which was spelled as (?: … ) in Perl 5.

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

Japt, 40 bytes

UfV="%A" ?UrV@'_s!Y +Xv} :Ur"^.|_."_sJ u

Test it online!

How it works

           // Implicit: U = input string
UfV="%A"   // Set variable V to the string "\\A", and get all matches in U.
?          // If the list is not null:
UrV@     } //  Replace each match X and its index Y with this function:
'_s!Y +Xv  //   Return "_".slice(!Y) (1 for Y=0, 0 for anything else) + X.toLowerCase().
:          // Otherwise:
Ur"^.|_."  //  Replace the char at the beginning and each char following an underscore with:
_sJ u      //   The last char of the match (the letter) .toUpperCase().
\$\endgroup\$
2
  • \$\begingroup\$ Why is the U not automatically inserted? \$\endgroup\$ Commented May 28, 2021 at 4:41
  • \$\begingroup\$ @SamuelWaller, because this solution pre-dates that feature being added. \$\endgroup\$
    – Shaggy
    Commented May 31, 2021 at 15:28
1
\$\begingroup\$

Perl 5, 42 bytes

40 bytes plus 2 for -p (thanks, dev-null)

s/[A-Z]/_\l$&/g||s/(^|_)(.)/\u$2/g;s/_//
\$\endgroup\$
2
  • \$\begingroup\$ On Windows, using perl and MINGW32, I get no output, what am I missing? \$\endgroup\$
    – ChatterOne
    Commented Jan 27, 2016 at 21:46
  • \$\begingroup\$ @ChatterOne I don't know what MINGW32 is, but it worked fine for me on Strawberry Perl. Use -E instead of -e. \$\endgroup\$
    – msh210
    Commented Jan 27, 2016 at 21:49
1
\$\begingroup\$

Forth (gforth), 129 bytes

: f bounds dup c@ 32 xor emit 1+ ?do i c@ '_ < if ." _"i c@ 32 + emit then i c@ '_ > if i 1- c@ '_ = 32 * i c@ + emit then loop ;

Try it online!

Code Explanation

: f              \ start a new word definition
  bounds         \ convert string address and length to beginning and ending address
  dup c@         \ get the first character
  32 xor emit    \ convert to the opposite case and output
  1+             \ add 1 to beginning of string (skip starting char)
  ?do            \ begin counted loop over string character addresses
    i c@ '_ <    \ check if char is uppercase 
    if           \ if it is:
      ." _"      \ output underscore
      i c@       \ get current char
      32 + emit  \ convert to lowercase and output
    then         \ end if block
    i c@ '_ >    \ check if lowercase (not '_')
    if           \ if it is:
      i 1- c@    \ get the previous character
      '_ = 32 *  \ if it's an underscore, multiply by 32 (true = -1 in forth)
      i c@ +     \ add result to current char (make uppercase if previous was '_')
      emit       \ output resulting char
    then         \ end if block
  loop           \ end loop
;                \ end word definition
      
\$\endgroup\$
1
\$\begingroup\$

Pip, 37 bytes

'_Na?UC@_.@>_Ma^'_(LC_MRXU.XL.'+a)J'_

Try it online!

Lot of underscores in this one.

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

K (ngn/k), 43 bytes

{("_"/(&x<_x)__x;,/`c$t-32*~<'t:"_"\x)x~_x}

Try it online!

  • x~_x check if the input x is all lowercase
  • (...;...) index into this two-item list depending on that value (i.e., return the first item in the list if x contains an uppercase letter, and the second item if it does not)
  • When x contains an uppercase letter:
    • (&x<_x) identify indices of uppercase letters
    • (...)__x split the lowercased input on those indices
    • "_"/ join the resulting chunks with underscores
  • When x is all lowercase:
    • t:"_"\x split input on underscores, storing in variable t
    • t-32*~<'t capitalize the first letter in each chunk (this converts to integers)
    • ,/`c$ convert back to characters, flattening the list
\$\endgroup\$
1
\$\begingroup\$

Japt, 37 bytes

a'_ Ä?Uq'_ ®hZu g} q:Uò@Yc <#a} q'_ v

Try it online!

Explanation:

There are some unicode chars that a shortcuts for common groups of chars,
like Ä is +1. This is the expanded version.

     // U = input string
a'_  // get index of "_" in string, -1 if not found
+1   // add 1 to make -1 falsy
?    // if truthy, ie. if there is an underscore in the input text
Uq'_ // split on underscores
m_   // map
h    // replace character (first char by default)
Zu g // the first letter of the string converted to uppercase (Zug)
} q  // close function and join
:    // else, ie. the string doesn't have any underscores
Uò   // runs func on every pair of chars, splits them if it returns true
@Yc <#a } // returns true if the second char code is less than "a", ie. uppercase
q'_  // join with underscores
v    // make lowercase

implicit print

First Japt program, I beat the creator, and Zu g!

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

Japt, 27 26 bytes

I fear I may have gone in the wrong direction with this; stuck on 27 and can't seem to do any better.

r"^.|_.|%A"ÈÌc^H i'_pY©XÊu

Try it

r"^.|_.|%A"ÈÌc^H i'_pY©XÊu     :Implicit input of string
r                              :Replace
 "^.|_.|%A"                    :RegEx /^.|_.|[A-Z]/g
           È                   :Pass each match X at 0-based index Y through the following function
            Ì                  :  Last character of X
             c                 :  Charcode
              ^H               :  Bitwise XOR with 32
                 i             :  Prepend
                  '_p          :    "_" repeated
                     Y©        :      Logical AND of Y and
                       XÊ      :      Length of X
                         u     :      Mod 2
\$\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.