21
\$\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\$
  • \$\begingroup\$ Is using a ToTitleCase function okay? You didn't specify, so I'd assume it is okay. \$\endgroup\$ – Justin Jan 26 '16 at 6:55
  • \$\begingroup\$ @Justin: Good question indeed. Let's make it more fun and disallow the ToTitleCase function :) \$\endgroup\$ – Dariusz Woźniak Jan 26 '16 at 7:04
  • \$\begingroup\$ Darn... my solution relies on it \$\endgroup\$ – Justin Jan 26 '16 at 7:06
  • \$\begingroup\$ @Justin: Okay - I didn't specify it in the beginning, so in that case - let's allow it anyway. \$\endgroup\$ – Dariusz Woźniak Jan 26 '16 at 7:08

15 Answers 15

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\$
  • \$\begingroup\$ Your link needs to be updated. \$\endgroup\$ – isaacg Jan 26 '16 at 6:36
  • \$\begingroup\$ When I try put "abc" as an input, it yields "bc" as an output. Bug? :) \$\endgroup\$ – Dariusz Woźniak Jan 26 '16 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\$ – FryAmTheEggman Jan 26 '16 at 18:16
  • \$\begingroup\$ Ah, found it: tsXzrG1*\_G \$\endgroup\$ – FryAmTheEggman Jan 26 '16 at 18:29
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\$
  • \$\begingroup\$ You can use string separation at the end, so it becomes "(?=[A-Z])'_. The string is closed automatically. \$\endgroup\$ – Conor O'Brien Jan 26 '16 at 0:54
  • \$\begingroup\$ @CᴏɴᴏʀO'Bʀɪᴇɴ oh, cool, thanks! \$\endgroup\$ – Downgoat Jan 26 '16 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\$
  • \$\begingroup\$ This saves four bytes: retina.tryitonline.net/… \$\endgroup\$ – Martin Ender Jan 26 '16 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\$ – Martin Ender Jan 26 '16 at 7:44
  • \$\begingroup\$ @Martin Very good - thanks! \$\endgroup\$ – Digital Trauma Jan 26 '16 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\$
  • 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\$ – Digital Trauma Jan 26 '16 at 16:19
  • \$\begingroup\$ @Toby lines 280-282 of sed/sed.c are /* Undocumented, for compatibility with BSD sed. */ case 'E': case 'r':. \$\endgroup\$ – Digital Trauma Jan 26 '16 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\$ – Toby Speight Jan 26 '16 at 18:03
3
\$\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\$
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\$
2
\$\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
    \$\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 Jan 28 '16 at 17:04
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\$
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\$
  • \$\begingroup\$ On Windows, using perl and MINGW32, I get no output, what am I missing? \$\endgroup\$ – ChatterOne Jan 27 '16 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 Jan 27 '16 at 21:49
1
\$\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\$
1
\$\begingroup\$

Jelly, 24 bytes

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

Try it online!

\$\endgroup\$
1
\$\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\$

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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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