20
\$\begingroup\$

As many of you will know, almost every programming language has a standard casing system; unfortunately, we have not been able to agree on a singular system to use and now must frequently switch between camelCase, snake_case, PascalCase and kebab-case.

Now I know what you're thinking... wouldn't it be nice if we had a program that could convert from one casing to another?
Well - soon we're going to have plenty!!! (This is where you come in)

Challenge

You're job is to write a program/function that will take an input string, and a casing system. It will then print/return the converted string.

Inputs:

You're program will receive two inputs, an alphabetic string that is to be converted and a string that will always be one of kebab camel pascal or snake.

Outputs:

You're program should output a string that conforms to the new casing. You can assume the input string is valid and will only conform to one of the casings (i.e. it won't be mixed_Casing).
As most programming languages prefer lowercase variable names, you should convert all letters to lowercase unless it is needed to be upper case for the casing requirements.

  • Snake Case: All letters are lower case, with underscores separating words.
  • Kebab Case: All letters are lower case, with hyphens separating words.
  • Camel Case: The first letter of every word after the first is capitalized, with no spaces.
  • Pascal Case: The first letter of every word is capitalized, with no spaces.

Test Cases:

"aJavaVariable", "snake" = "a_java_variable"  
"a_python_variable", "kebab" = "a-python-variable" 
"golf", "camel" = "golf"
"camelToPascal", "pascal" = "CamelToPascal"  
"PascalToCamel", "camel" = "pascalToCamel"  
"", "snake" = "" 
"doHTMLRequest", "kebab" = "do-h-t-m-l-request"  

<!-- Run the snippet to see the leaderboard. Report any bugs to @ozewski on Github. -->    <iframe src="https://ozewski.github.io/ppcg-leaderboard/?id=216396" width="100%" height="100%" style="border:none;">Oops, your browser is too old to view this content! Please upgrade to a newer version of your browser that supports HTML5.</iframe><style>html,body{margin:0;padding:0;height:100%;overflow:hidden}</style>

\$\endgroup\$
8
  • 6
    \$\begingroup\$ This is very similar/a superset of this challenge \$\endgroup\$ – caird coinheringaahing Dec 15 '20 at 21:38
  • 2
    \$\begingroup\$ Also I would recommend using the Sandbox for future challenges. \$\endgroup\$ – Giuseppe Dec 15 '20 at 21:40
  • 4
    \$\begingroup\$ @Guiseppe it was in the sandbox, but only for a day. The real issue is that it should have been left longer \$\endgroup\$ – lyxal Dec 15 '20 at 21:42
  • 3
    \$\begingroup\$ Apologies for not leaving it in the sandbox for longer; noted for future reference. As I also pointed out in the sandbox - the premise of the question is very similar to the other one, however the logic required to golf it will be very different, and in my opinion that's the point of the code golf \$\endgroup\$ – Scott Dec 15 '20 at 22:42
  • 3
    \$\begingroup\$ I recommend adding a PascalToCamel test to make sure the first letter gets lowercased \$\endgroup\$ – Asone Tuhid Dec 16 '20 at 11:23
6
\$\begingroup\$

JavaScript (Node.js),  127 126  120 bytes

Expects (string)(type).

s=>t=>s.split(/_|-|(?=[A-Z])/).map((s,i)=>(k=parseInt(t,29)%5,(b=Buffer(s))[0]=b[0]&95|(k+!i>1)<<5,b)).join(['-_'[k-3]])

Try it online!

How?

The type string \$t\$ is converted to an ID \$k \in [0..4]\$ by parsing it as a base-29 value and applying a modulo \$5\$:

 type     | base 29 -> dec. | mod 5
----------+-----------------+-------
 "pascal" |    520544830    |   0
 "camel"  |      8750191    |   1
 "kebab"  |     14496618    |   3
 "snake"  |     20373819    |   4

This formula was chosen because it puts pascal next to camel and kebab next to snake, allowing the following expressions:

  • Put the leading character in uppercase if 1) the type is pascal (\$k=0\$), or 2) the type is camel (\$k=1\$) and this is not the first word (\$i\neq 0\$):

    (b = Buffer(s))[0] = b[0] & 95 | (k + !i > 1) << 5
    
  • Join with "-" for kebab (\$k=3\$), "_" for snake (\$k=4\$) or an empty string otherwise:

    .join(['-_'[k - 3]])
    
\$\endgroup\$
4
\$\begingroup\$

Perl 5 -lF'_|-|(?=[A-Z])', 69 bytes

$,=('_','-')[$c=3-ord(<>)%14%7];say map$c>1&&$i++||$c>2?ucfirst:lc,@F

Try it online!

\$\endgroup\$
4
\$\begingroup\$

Retina 0.8.2, 81 bytes

T`l\_-`L_`^.|_.|-.
\B(?=[A-Z].*¶s)
_
\B(?=[A-Z].*¶k)
-
T`L`l`^.(?!.*¶p)|_.|-.
1G`

Try it online! Takes input on separate lines but link includes test suite that splits the input on commas. Explanation:

T`l\_-`L_`^.|_.|-.

Uppercase the first letter and any letters after _ or -, which get deleted. This gives you Pascal case.

\B(?=[A-Z].*¶s)
_
\B(?=[A-Z].*¶k)
-

Prefix interior capital letters with _ (for snake case) or - (for kebab case).

T`L`l`^.(?!.*¶p)|_.|-.

Except for Pascal case, lowercase the first letter, plus (since we just marked them) all the letters for snake or kebab case.

1G`

Delete the second input (the case) leaving the result.

\$\endgroup\$
2
  • \$\begingroup\$ This fails for "PascalToCamel","camel". it just deletes the first letter for some reason \$\endgroup\$ – Asone Tuhid Dec 16 '20 at 11:29
  • \$\begingroup\$ @AsoneTuhid Ugh, it was a last-minute golf that I didn't test correctly... fixed now. \$\endgroup\$ – Neil Dec 16 '20 at 11:46
3
\$\begingroup\$

05AB1E, 50 48 47 bytes

.ªÐáмÙ¡¸˜Dgi`.œ.ΔD™Q]l²ÇO5%©2‹i„_-®èýë™J®4Qićlì

Try it online or verify all test cases.

Explanation:

.ª           # Sentence capitalize the first (implicit) input-string, which titlecases
             # without removing existing capital letters
  Ð          # Triplicate that string
   á         # Only leave its letters
    м        # Remove all those letters (so only "-"/"_" are left, if any)
     Ù       # Uniquify the potential multiple "-"/"_" to a single character
      ¡      # Split the string on this character
       ¸˜    # Wrap it into a list, and flatten
Dg           # Get the length of a copy of this list
  i          # If this length is 1 (thus no "-"/"_" were present):
   `         #  Push the string to the stack to remove the wrapped list
    .œ       #  Get all partitions of this string
      .Δ     #  Find the first partition which is truthy for:
        D    #   Duplicate the list of substrings
         ™   #   Titlecase each string
          Q  #   Check that the two lists are equal
  ]          # Close both the find_first and if-statement
   l         # Lowercase each string in the list
    ²        # Push the second input-string
     Ç       # Convert it to a list of codepoint integers
      O      # Sum those
       5%    # And take modulo-5 on it (snake=0; kebab=1; camel=4; pascal=3)
         ©   # Store this in variable `®` (without popping)
2‹i          # If it's smaller than 2 (thus snake or kebab):
   „_-       #  Push string "_-"
      ®è     #  (0-based) index `®` into this string
        ý    #  Join the list by this character as delimiter
  ë          # Else (thus camel or pascal):
   ™         #  Titlecase each string in the list
    J        #  Join them together
     ®4Qi    #  If `®` equals 4 (thus camel):
         ć   #   Extract head; pop and push remainder-string and first character
             #   separated to the stack
          l  #   Lowercase this first character
           ì #   And then prepend it back in front of the remainder-string
             # (after which the result is output implicitly)
\$\endgroup\$
3
  • 1
    \$\begingroup\$ This fails for "PascalToCamel", "camel" \$\endgroup\$ – Asone Tuhid Dec 16 '20 at 11:28
  • 1
    \$\begingroup\$ @AsoneTuhid Thanks for noticing! Should be fixed now, and the fix actually saved a byte as well. :) \$\endgroup\$ – Kevin Cruijssen Dec 16 '20 at 13:26
  • \$\begingroup\$ haha, nice, that's when you know a language is right for the job \$\endgroup\$ – Asone Tuhid Dec 17 '20 at 10:19
1
\$\begingroup\$

Ruby, 112 103 bytes

->s,c{x=s.split(/(?=[A-Z])|-|_/).map{x=c[5]||x&&c<?d?_1.capitalize: _1.downcase}*(c>?s??_:c[?k]??-:'')}

Try it online! (+1 byte because TIO doesn't support ruby 2.7's _1)

Multiple bytes saved thanks to @Dingus

  • x tracks whether it's not the first iteration
  • c[5] asks whether it's "pascal" (the only one with 6 letters)
  • c>?s asks if c is greater than 's', this is only true for "snake", etc.
  • the multiplication at the end is equivalent to join
\$\endgroup\$
0
0
\$\begingroup\$

Laravel/PHP, 264 bytes

It accepts a GET request to /{string}/{kebab|camel|pascal|snake} as input, and output is done via dumping. Bytes are counted for the one file that I wrote.

web.php:

<? use Illuminate\Support\Str;Illuminate\Support\Facades\Route::get('/{a}/{b}',function(){$a=str_replace("_","-",request()->a);switch(request()->b[0]){case'p':dd(Str::studly($a));case'c':dd(Str::camel($a));case'k':dd(Str::kebab($a));default:dd(Str::snake($a));}});

I wrote/ran this on PHP Sandbox and verified it with PHPUnit tests like so (must be run one at a time!):

$this->get('/aJavaVariable/snake');
$this->get('/a_python_variable/kebab');
$this->get('/golf/camel');
$this->get('/camelToPascal/pascal');
$this->get('/PascalToCamel/camel');
$this->get('//snake'); // 404 (no output)
$this->get('/doHTMLRequest/kebab');

Could probably save bytes by using Laravel < 6 and the helpers str_*, but didn't feel like verifying it would run. Does anyone see any other shortcuts?

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