16
\$\begingroup\$

Write a function to convert CamelCased text to snake_case: FunctionForHTMLManipulation becomes function_for_html_manipulation

The input text will be a single suitable identifier in many languages. It must start with an English letter, then be followed by any number of English letters or digits. No other characters (spaces, symbols, etc.) are allowed.

Each "word" within the CamelCased text will start with a capital letter unless at the beginning of the text or immediately after a digit, and be followed by zero or more letters, all of the same case. Groups of digits will be considered as separate words but pass through unchanged.

In other words, a lowercase letter followed by an uppercase letter indicates a word break. Any letter and digit next to each other indicates a word break. An uppercase letter followed by another uppercase letter and a lowercase letter indicates a word break.

...lU... => ...l_u...
...l9... => ...l_9...
...U9... => ...u_9...
...9l... => ...9_l...
...9U... => ...9_u...
...UUl... => ...u_ul...

Both Buy24Beers and buy24beers become buy_24_beers.
MacDonaldAndObrian becomes mac_donald_and_obrian.
MACDonaldAndOBrian becomes mac_donald_and_o_brian.

\$\endgroup\$
7
  • 6
    \$\begingroup\$ "MACDonaldAndOBrian becomes mac_donald_and_o_brian" - why? \$\endgroup\$
    – Qwertiy
    Apr 22, 2016 at 8:20
  • 2
    \$\begingroup\$ @Qwertiy Because I thought those names would be fun. Unless you're asking about the rule, which is covered by ...UUl... => ...u_ul.... \$\endgroup\$
    – CJ Dennis
    Apr 22, 2016 at 8:23
  • \$\begingroup\$ Very closely related \$\endgroup\$ Apr 22, 2016 at 20:04
  • \$\begingroup\$ @DigitalTrauma Amazingly close to my original question but without the complaints about being two questions in one and no downvotes! The biggest difference is in the treatment of ALLCAPS strings. I searched to see if the question had been asked before but I didn't find it. \$\endgroup\$
    – CJ Dennis
    Apr 23, 2016 at 4:59
  • 1
    \$\begingroup\$ @ggorlen the ... indicates it's in the middle of a string. \$\endgroup\$
    – CJ Dennis
    Dec 22, 2018 at 21:29

10 Answers 10

9
\$\begingroup\$

Retina, 61 37 bytes

r1>`[A-Z]?[a-z]+|[A-Z]+|\d+
_$&
T`L`l

Try it online! (Slightly modified to run a full test suite.)

Explanation

Instead of finding word boundaries to insert underscores, we simply match each word and prepend a _. Matching words from the left is a bit annoying because of the UUl rule, but using .NET's right-to-left matching we can easily match words greedily. To avoid a leading _, we make use of Retina's limits.

r1>`[A-Z]?[a-z]+|[A-Z]+|\d+
_$&

The r activates right-to-left mode, the 1> tells Retina to process everything except the first match (counting from left to right). Then there's four types of "words": Ulll, lll, UUU, ddd. These are easily matched with the given pattern. The substitution just writes a _ followed by the word itself.

T`L`l

This simply turns upper case into lower case to complete the transformation.

\$\endgroup\$
6
\$\begingroup\$

JavaScript (ES6), 79 bytes

s=>s.match(/[A-Z]+(?=[A-Z][a-z]|\d|$)|[A-Z]?[a-z]+|\d+/g).join`_`.toLowerCase()
\$\endgroup\$
0
3
\$\begingroup\$

JavaScript (ES6), 89 bytes

s=>s.replace(/\d(?=\D)|\D(?=\d)|[a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z])/g,"$&_").toL‌​owerCase()
\$\endgroup\$
0
2
\$\begingroup\$

Powershell, 77 bytes

Based on Neil's answer.

$args-creplace'\d(?=\D)|\D(?=\d)|[a-z](?=[A-Z])|.(?=[A-Z][a-z])','$&_'|% *wer

Less golfed test script:

$f = {

$args-creplace'\d(?=\D)|\D(?=\d)|[a-z](?=[A-Z])|.(?=[A-Z][a-z])','$&_'|% toLower

}

@(
    ,("Buy24Beers", "buy_24_beers")
    ,("buy24beers", "buy_24_beers")
    ,("MacDonaldAndObrian", "mac_donald_and_obrian")
    ,("MACDonaldAndOBrian", "mac_donald_and_o_brian")
    ,("BigD", "big_d")
) | % {
    $s,$expected = $_
    $result = &$f $s
    "$($result-ceq$expected): $result"
}

Output:

True: buy_24_beers
True: buy_24_beers
True: mac_donald_and_obrian
True: mac_donald_and_o_brian
True: big_d
\$\endgroup\$
1
\$\begingroup\$

Retina, 62 bytes

Shamelessly translated from the JavaScript solution.

\d(?=\D)|\D(?=\d)|[a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z])
$&_
T`L`l

Try it online!

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

Lua, 135 bytes

function snake(s)return s:gsub('%f[^%l]%u','_%1'):gsub('%f[^%a]%d','_%1'):gsub('%f[^%d]%a','_%1'):gsub('(%u)(%u%l)','%1_%2'):lower()end

Try it online!

This solution benefits from Lua's shorthand notation for C's character classes (lowercase %l, uppercase %u, alphabetic %a, digit %d), frontier notation (%f[]), and from the whole match being added as the implicit first capture in the absence of any other captures.

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

Python 2, 82 bytes

lambda x:re.sub('(\d+|.([A-Z]*|[a-z]*)(?![a-z]))(?!$)','\\1_',x).lower()
import re

Try it online!

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

PowerShell, 68 92 bytes

Briefly deleted, +24 bytes for using the wrong RegEx.

($args-creplace'\d(?=\D)|\D(?=\d)|[a-z](?=[A-Z])|.(?=[A-Z][a-z])','$&_').Trim('_').ToLower()

Try it online!

Basically the same as the JavaScript solutions.

\$\endgroup\$
2
  • \$\begingroup\$ It does not work with buy24beers and MACDonaldAndOBrian. Sorry. \$\endgroup\$
    – mazzy
    Dec 23, 2018 at 6:40
  • 1
    \$\begingroup\$ @mazzy fixed, thanks. \$\endgroup\$
    – GMills
    Jan 26, 2019 at 14:58
0
\$\begingroup\$

Factor, 140 bytes

[ R/ [a-z][A-Z][a-z]/ [ dup from>> swap dup to>> swap seq>> subseq R/ [A-Z][a-z]/ [ "_" prepend ] re-replace-with ] re-replace-with >lower ]

Ungolfed:

: slice>subseq ( slice -- subseq )
dup from>> swap dup to>> swap seq>> subseq ;

: camel-case>snake-case ( string -- string' )
    R/ [a-z][A-Z][a-z]/ [
        slice>subseq R/ [A-Z][a-z]/
        [ "_" prepend ] re-replace-with
    ] re-replace-with >lower ;
\$\endgroup\$
0
\$\begingroup\$

Raku, 61 bytes

{S:g:P5/\d+|[A-Z]+(?=[A-Z]|\d)|[A-Z][a-z]*/{$/.lc~"_"}/.chop}

Try it online!

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