People on this site really love to embellish their post titles...

Stewie's sequence: + * - / + * - /

However, when this title needs to be included in the page URL, it is simplified:


The Challenge

Your task is to create a program or function which, given a string representing a post title, outputs/returns its "URL-Friendly" conversion.

The algorithm is:

  • Convert to lowercase (where applicable)
  • Replace every space (), period (.), comma(,) or slash (/) with a dash (-)
  • Remove any non-alphanumeric characters, except dashes.
  • Minify groups of adjacent dashes (a---b -> a-b), remove any that are leading/trailing.

Please note that this algorithm is a simplification, and may not always produce the same results as the site's real method.


  • You can assume that input:
    • Will not be empty.
    • Will contain at least one alphanumeric character.
    • Will only contain characters in the ASCII range 32-126 (printable)
  • Full programs or functions are permitted.
  • A builtin which does the exact task specification is not permitted.
  • This is , so the shortest solution (in bytes) wins!

Test Cases

Most posts on this site will serve as tests, but here's a handy list:

Loading... Forever       -> loading-forever
N(e(s(t))) a string      -> nest-a-string
"Hello, World!"          -> hello-world
URL-Friendly titles      -> url-friendly-titles

C.U.S.R.S                -> c-u-s-r-s
1+2+3+4+...+n = -1/12?   -> 1234-n-1-12
How can I use cmp(a,b)   -> how-can-i-use-cmpa-b

Some longer ones...

Export The $PATH Variable, Line-By-Line   -> export-the-path-variable-line-by-line
Do n and n^3 have the same set of digits? -> do-n-and-n3-have-the-same-set-of-digits
Quine Anagrams! (Cops' Thread)            -> quine-anagrams-cops-thread
The Golfer Adventure - Chapter 1          -> the-golfer-adventure-chapter-1
Bootloader golf: Brainf***                -> bootloader-golf-brainf

And some edge-case checking samples (feel free to suggest more):

0123   ->   0123
a a1   ->   a-a1
2-1=1  ->   2-11
  • \$\begingroup\$ What about leading -s? Will they have to be removed? For example in asdf-, will the last - have to be removed? \$\endgroup\$
    – user41805
    Dec 10, 2016 at 13:26
  • \$\begingroup\$ Can we use a built-in function to check if the char is alphanumeric like this if(isalphanum(ch))... \$\endgroup\$ Dec 10, 2016 at 13:27
  • 1
    \$\begingroup\$ @KritixiLithos Minify groups of adjacent dashes (a---b -> a-b), remove any that are leading/trailing. I guess this should make you clear. \$\endgroup\$ Dec 10, 2016 at 13:47
  • \$\begingroup\$ And what about _ underscores? My code works except when there are underscores. \$\endgroup\$
    – user41805
    Dec 10, 2016 at 13:48
  • \$\begingroup\$ @L3viathan Doesn't matter now, I changed my code so that even underscores will be removed \$\endgroup\$
    – user41805
    Dec 10, 2016 at 14:07

19 Answers 19


JavaScript (ES6), 90 82 79 75 bytes

This is an attempt to do the job with a single replace(). This code extracts only the characters that we're interested in and ignores everything else. There's some additional logic to process the hyphens.

s=>(s.toLowerCase().replace(/[ a-z,-9]/g,c=>S=c<'0'?s+'-':s=s?S+c:c,s=0),s)

Test cases

let f =

s=>(s.toLowerCase().replace(/[ a-z,-9]/g,c=>S=c<'0'?s+'-':s=s?S+c:c,s=0),s)

console.log(f(`Loading... Forever`));                         // -> loading-forever
console.log(f(`N(e(s(t))) a string`));                        // -> nest-a-string
console.log(f(`"Hello, World!"`));                            // -> hello-world
console.log(f(`URL-Friendly titles`));                        // -> url-friendly-titles
console.log(f(`C.U.S.R.S`));                                  // -> c-u-s-r-s
console.log(f(`1+2+3+4+...+n = -1/12?`));                     // -> 1234-n-1-12
console.log(f(`How can I use cmp(a,b)`));                     // -> how-can-i-use-cmpa-b
console.log(f(`Export The $PATH Variable, Line-By-Line`));    // -> export-the-path-variable-line-by-line
console.log(f(`Do n and n^3 have the same set of digits?`));  // -> do-n-and-n3-have-the-same-set-of-digits
console.log(f(`Quine Anagrams! (Cops' Thread)`));             // -> quine-anagrams-cops-thread
console.log(f(`The Golfer Adventure - Chapter 1`));           // -> the-golfer-adventure-chapter-1
console.log(f(`Bootloader golf: Brainf***`));                 // -> bootloader-golf-brainf
console.log(f(`0123`));                                       // -> 0123
console.log(f(`a a1`));                                       // -> a-a1
console.log(f(`2-1=1`));                                      // -> 2-11
console.log(f(',a^a,'));                                      // -> aa

  • 1
    \$\begingroup\$ For ,a^a,, this code gives -aa- (there are leading/trailing hyphens) \$\endgroup\$
    – user41805
    Dec 10, 2016 at 15:49
  • \$\begingroup\$ @KritixiLithos Oh, thanks for pointing this out. I didn't pay attention to that rule. That should be fixed. \$\endgroup\$
    – Arnauld
    Dec 10, 2016 at 16:09

V, 41, 40, 37, 36 bytes

VuÍ[ .,\/]/-

Try it online! or Check all test cases at once!

As usual, here this contains a bunch of unprintable and non-ASCII characters, so here is a hexdump:

0000000: 5675 cd5b 202e 2c5c 2f5d 2f2d 0acd 8430  Vu.[ .,\/]/-...0
0000010: 2d39 612d 7aad 5d0a cd2d ab2f 2d0a cd5e  -9a-z.]..-./-..^
0000020: 2dfc 2d24                                -.-$

It's challenges like these where V's "Compressed regex" system come in handy.


First things first, we will convert everything to lowercase. Fortunately there is a really convenient way to do this in two bytes. I wrote a tip about that here. So we do

V           " Visually select this whole line
 u          " Convert this whole line to lowercase

After that we do a bunch of compressed substitute commands. A nice overview of how V's compressed regex works can be foud here, but the basic idea is we can set the high-bit to avoid having to escape certain characters. Another convenience is that ranges (like :%) and flags (like /g) are automagically filled in. But in the end, it all translates to vim substitute commands. In fact, we could even directly translate the rest of the program to vim. That would give us this:

:%s/[ .,/]/-/g

If you speak vim-regex, it should be more clear what the rest of the program does now. So here is the rest of the program:

Í               " Substitute:
 [ .,\/]        "   a space, period, comma or forward slash. (Due to a strange bug, this needs to be escaped)
        /-      "   with a dash
Í               " Remove:
 [^0-9a-z­]     "   Any character that is not a dash or alpha-numeric
Í               " Substitute:
 -«             "   One or more dashes
   /-           "   with one dash
Í               " Remove:
 ^-             "   A dash at the beginning of a line
   ü            "   OR
    -$          "   a dash at the end of a line

JavaScript (ES6) 91 96

1 bytes saved thx @ETHproductions

s=>s.toLowerCase().replace(/([ .,/-])|\W|_/g,(c,d)=>d?'-':'').replace(/^-*|-*$|-(?=-)/g,'')


s=>s.toLowerCase().replace(/([ .,/-])|\W|_/g,(c,d)=>d?'-':'').replace(/^-*|-*$|-(?=-)/g,'')

;[['Loading... Forever.....', 'loading-forever'],
['N(e(s(t))) a string', 'nest-a-string'],
['"Hello, World!"', 'hello-world'],
['URL-Friendly titles', 'url-friendly-titles'],
['1+2+3+4+...+n = -1/12?', '1234-n-1-12'],
['How can I use cmp(a,b)', 'how-can-i-use-cmpa-b'],
['Export The $PATH Variable, Line-By-Line', 'export-the-path-variable-line-by-line'],
['Do n and n^3 have the same set of digits?', 'do-n-and-n3-have-the-same-set-of-digits'],
['Quine Anagrams! (Cops\' Thread)', 'quine-anagrams-cops-thread'],
['The Golfer Adventure - Chapter 1', 'the-golfer-adventure-chapter-1'],
['Bootloader golf: Brainf***', 'bootloader-golf-brainf'],
['0123', '0123'],
['a a1', 'a-a1'],
['2-1=1', '2-11']]
  var i=t[0],k=t[1],r=F(i)
  console.log(r==k?'OK':'KO',i+' -> '+r,r==k?'':k)

  • \$\begingroup\$ This has exactly the same bytecount as my answer if it is converted to a named function \$\endgroup\$
    – user41805
    Dec 10, 2016 at 14:12
  • \$\begingroup\$ Don't think you need the last * in the last regex, though I may be wrong \$\endgroup\$ Dec 10, 2016 at 14:27
  • \$\begingroup\$ I may be mistaken, but are you sure the lookahead is necessary? \$\endgroup\$
    – user41805
    Dec 10, 2016 at 14:40
  • \$\begingroup\$ @KritixiLithos the lookahead is necessary to keep at least 1 - inside the string, while removing all at start and end \$\endgroup\$
    – edc65
    Dec 10, 2016 at 14:54
  • \$\begingroup\$ @ETHproductions right, thanks \$\endgroup\$
    – edc65
    Dec 10, 2016 at 14:55

Retina, 33 31 bytes

[^a-z ,-9]+


(The program has a trailing newline)

I'm not sure I can squeeze more out of this. This should cover everything. Came similar to Mama Fun Roll's. Another 33 bytes version using recursive regexes

Try it online!



This line is simple, it converts to lowercase by Transliterating A-Z (L) to a-z (l, lowercase).

This stage is simple, it essentially gets rid of all unneeded characters to save ourselves a lot of trouble later on

[^a-z ,-9]+

[^a-z ,-9] Matches any character that is NOT:

  • a-z: lowercase alphabet (remember entire string is lowercase because of previous item)
  • : space chacacter
  • ,-9 this is char code range of , to 9 which happens to be ,-./0123456789, exactly the characters we need

Next we convert all non alphanumeric chars to dashes (which is now just and ,./-.


This won't (not) match _ which is included in \w (negation of \W) because it was removed in the previous stage

  • \$\begingroup\$ I think this will fail for inputs like a = b. \$\endgroup\$ Dec 14, 2016 at 15:44
  • \$\begingroup\$ I really want to accept this, but as martin said, it doesn't minify adjacent dashes when you input a = b :( \$\endgroup\$
    – FlipTack
    Dec 18, 2016 at 18:01
  • \$\begingroup\$ @Flp.Tkc sorry for late response (Finals week right now). I've managed to squeeze out two more bytes and fix it. I believe this correctly handles such cases now \$\endgroup\$
    – Downgoat
    Dec 19, 2016 at 2:56

Python 3, 103 100 96 95 bytes

5 bytes saved thanks to Flp.Tkc

import re
lambda s,y=re.sub,d='-':y('-+',d,y('[^0-9a-z-]','',y('[ .,/]',d,s.lower()))).strip(d)
  • \$\begingroup\$ @Flp.Tkc Indeed.. \$\endgroup\$
    – L3viathan
    Dec 10, 2016 at 15:20
  • \$\begingroup\$ Oops, I accidentally downvoted this. I can't reverse my vote until you edit this post \$\endgroup\$
    – user41805
    Dec 11, 2016 at 16:50
  • \$\begingroup\$ @KritixiLithos Done \$\endgroup\$
    – L3viathan
    Dec 11, 2016 at 16:51

Retina, 34 bytes

[^a-z\d .,/-]+


Try it online!

Note the trailing newline. Essentially the OP's implementation.

  • \$\begingroup\$ The TIO link points to a slightly different code \$\endgroup\$
    – user41805
    Dec 10, 2016 at 16:02
  • \$\begingroup\$ Yeah I just fixed it. \$\endgroup\$ Dec 10, 2016 at 16:03
  • 1
    \$\begingroup\$ You can use T`L`l to go to lowercase with less bytes \$\endgroup\$
    – Downgoat
    Dec 10, 2016 at 18:18
  • \$\begingroup\$ Fails on a.. snd similar \$\endgroup\$
    – Downgoat
    Dec 11, 2016 at 17:32
  • 2
    \$\begingroup\$ [\W] is just \W \$\endgroup\$ Dec 14, 2016 at 15:39

MATL, 38 bytes

'-'jyvk45y' .,/'m(t8Y245hm)'-*'45YX6L)

Try it online! Or verify all test cases.


'-'jyv       % Take input line. Append and prepend a dash. Gives a char column vector
k            % Convert to lowercase
45y' .,/'m(  % Replace any of ' .,/' by a dash, using assignment indexing
t8Y245hm)    % Keep only alphanumeric chars or dashes, using reference indexing
'-*'45YX     % Replace each run of dashes by a single dash, using a regular expression
6L)          % Remove first and last chars, which are always dashes. Implicitly display

Ruby, 61 60 61 64 53 bytes

(52 bytes of code plus one byte for the -p)

$_=$_.tr("A-Z ,-/","a-z ").gsub(/[^\w ]/){}.split*?-

Try it online!

tr() – convert upper case characters, space, comma, period, and slash. Temporarily substitute the - with whitespace so I can use strip later on.
Note the - character in the "A-Z ,-/" expression is actually a range operator, which also makes the . character subject to transformation. This maneuver won’t actually shave off bytes but it’s fancy so it gets to stay.

gsub(/[^\w ]/){} – remove all characters not in the allowed set.

split – technically, we don’t exactly need that array but split eliminates leading and trailing whitespace (which is actually - characters in disguise). As a bonus, this squeezes together runs of multiple spaces.

*?- – Shorthand for .join("-"); this reverses both the preceding split operation and the whitespace transformation at the same time. One more byte is saved by using the abbreviated notation for character literals, which makes the program require Ruby 1.9 or newer.

Update 1: Using gets instead of Ruby’s stream editing mode saves one byte.
Reverted as per ValueInk’s suggestion.

Update 2: (+3 bytes overall)

  • Fixed edge case ..--hi, $/ (→ hi) (+10 bytes) – once again courtesy of user ValueInk
  • Took malus for -p (+1 byte)
  • Got rid of squeeze and used gsub instead (+2 bytes), which allowed me to:
  • Use strip to handle the leading and trailing dashes (-10 bytes).

Update 3: Hattrick by ValueInk. We save 11 bytes by leveraging String#split’s habit of auto-squeezing runs of the same separator, which enables us to ditch the whole final strip/gsub chain and replace it by a split/join combo. (-11 bytes)

  • \$\begingroup\$ This only returns the string in a REPL environment and fails if run as a proper Ruby program, and that's no good. Full programs or functions/lambdas only. In fact, your old version would've worked with the -p flag, but this definitely won't. \$\endgroup\$
    – Value Ink
    Dec 11, 2016 at 7:09
  • \$\begingroup\$ @ValueInk You are of course correct. I’ve changed my solution accordingly. Thanks for your comment; it is exactly the kind of guidance I appreciate a lot as this is my first attempt at golfing. \$\endgroup\$
    – Synoli
    Dec 11, 2016 at 14:54
  • 1
    \$\begingroup\$ Thank you for making the fix; I have removed my downvote. A thing to note is that using the -p flag implicitly adds 1 byte to your code (because it changes your code execution from ruby -e 'your code' to ruby -pe 'your code'). I have also found one edge case where it gives -hi- for input like ..--hi, $/ when you should be removing all leading/trailing dashes and thus be returning hi. \$\endgroup\$
    – Value Ink
    Dec 12, 2016 at 4:16
  • 2
    \$\begingroup\$ -2 bytes by changing gsub(/[^\w ]/){} to tr('^a-z ',''), and then end with .split*?- instead of .strip.gsub... since it automatically handles duplicates and the ends of the string, all in one go! \$\endgroup\$
    – Value Ink
    Dec 12, 2016 at 10:59
  • 1
    \$\begingroup\$ Since nobody said it, welcome to code-golf! \$\endgroup\$
    – FlipTack
    Dec 12, 2016 at 21:10

JavaScript (ES6), 74 69 bytes

s=>s.toLowerCase().replace(/[^-/,. a-z\d]/g,``).match(/\w+/g).join`-`
<input oninput=o.textContent=/[a-z\d]/i.test(this.value)?f(this.value):``><pre id=o>

Edit: Saved 5 bytes by realising that I had already deleted all characters except -/,. 0-9a-z so I can use \w to match the remaining words.

  • \$\begingroup\$ I think you have to include the HTML code into the bytecount since it is being used to solve the challenge \$\endgroup\$
    – user41805
    Dec 11, 2016 at 7:44
  • 1
    \$\begingroup\$ @KritixiLithos No, it's only there for demonstration purposes. The question says that my code can assume at least one alphanumeric character, and the HTML code simply tests this before calling the function. \$\endgroup\$
    – Neil
    Dec 11, 2016 at 10:00
  • \$\begingroup\$ [a-z\d] could be [^\W_] ? \$\endgroup\$
    – edc65
    Dec 15, 2016 at 11:47
  • \$\begingroup\$ @edc65 Nice, but I then realised that it could be even simpler! \$\endgroup\$
    – Neil
    Dec 15, 2016 at 19:50

PHP, 87 bytes

The idea of the regular expressions come from existing answers.

<?=trim(preg_replace(['@[^ a-z,-9]@','@[ ,-/]+@'],['','-'],strtolower($_GET[T])),'-');

It requires you to have a server running PHP, and access over HTTP.

The title must be on the key T, and the result will be printed on the screen.

Example: http://localhost/title.php?T=<my shiny title>


bash/Unix tools, 56 bytes

tr A-Z\ .,/ a-z-|tr -cds a-z0-9- -|sed s/^-//|sed s/-$//

Replace upper-case with lower-case letters, and the required special characters with dashes.

Delete (-d option to tr) characters other than letters, digits, and dashes, and then squeeze (-s option to tr) multiple dashes in a row into a single dash.

Delete dashes at the beginning, and then at the end.


Powershell, 85 Bytes

($args[0].ToLower()-replace'[ .,/]','-'-replace'[^a-z,-9]'-replace'-+','-').Trim('-')

make it lowercase, then 3 regex replaces in a row, and trim any trailing -'s

  • \$\begingroup\$ mightn't $input save you 2 bytes? \$\endgroup\$
    – briantist
    Dec 11, 2016 at 22:23

JavaScript, 90 98 94 93 91 90 91 bytes

1 byte saved thanks to @edc65!

1 byte saved thanks to @IsmaelMiguel for spotting a leading semi-colon!

1 byte gained after failing for ,a-^-a,

f=s=>s.toLowerCase().replace(/[^ a-z,-9]/g,"").replace(/[ ,-/]+/g,"-").replace(/^-|-$/g,"")

The thing I like most about this particular submission are the ranges. In the first replace, we remove anything that is not alphanumeric and not a ,,-,.,/ and not a space. We use a-z for detecting the letters, and we use ,-9 to detect those special characters and numbers since the character codes of these ASCII literals all line up!

, = 44
- = 45
. = 46
/ = 47
0 = 48
9 = 57

f=s=>s.toLowerCase().replace(/[^ a-z,-9]/g,"").replace(/[ ,-/]+/g,"-").replace(/^-|-$/g,"")


console.log(f("Stewie's sequence: + * - / + * - /-"));

console.log(f("Loading... Forever"));
console.log(f("N(e(s(t))) a string"));
console.log(f("\"Hello, World!\""));
console.log(f("URL-Friendly titles"));

console.log(f("1+2+3+4+...+n = -1/12?"));
console.log(f("How can I use cmp(a,b)"));

console.log(f("Export The $PATH Variable, Line-By-Line"));
console.log(f("Do n and n^3 have the same set of digits?"));
console.log(f("Quine Anagrams! (Cops' Thread)"));
console.log(f("The Golfer Adventure - Chapter 1"));
console.log(f("Bootloader golf: Brainf***--"));

console.log(f("a a1"));
<input oninput=a.textContent=f(this.value)><pre id=a>

  • \$\begingroup\$ Doesn't remove leading dashes: "-1" becomes "-1", when it should become "1". \$\endgroup\$
    – L3viathan
    Dec 10, 2016 at 14:08
  • \$\begingroup\$ @L3viathan Should work now \$\endgroup\$
    – user41805
    Dec 10, 2016 at 14:11
  • \$\begingroup\$ No need to count f= so your byte count is 96 right now. And no need of the \ inside a range in the regexp, so it could be 95. But ... still not working: try ...title \$\endgroup\$
    – edc65
    Dec 10, 2016 at 14:18
  • 1
    \$\begingroup\$ Hei! I'm not that old! (65 not 64) \$\endgroup\$
    – edc65
    Dec 10, 2016 at 15:00
  • 1
    \$\begingroup\$ I believe you don't need the f= and the ; at the end. Just specify that this is an anonymous function. With this, your answer should be 90 bytes long. \$\endgroup\$ Dec 10, 2016 at 15:09

Lua, 91 bytes

a=a:lower():gsub( '[ .,/]', '-' ):gsub( '[^%w-]', '' ):gsub( '%-+', '-' ):match'%-?(.*)%-?'

Where a is the URL string.


  • Most of it's pretty straight forward. a:lower() returns the lower-case function
  • :gsub finds the match of the pattern and replaces it with the string.
  • '[ .,/]': Brackets mean "or", so this matches space, period, comma, and slash. No need to be greedy because :gsub does all occurrences.
  • '[^%w-]': ^ means "not" when inside brackets, %w means anything alphanumeric. So '[^%w-] matches anything that's not alphanumeric or a dash.
  • '%-+': Match as many dashes as you can and replace them with just one dash.
  • match'%-?(.*)%-?': In Lua, if a string is the function's only argument, no parenthesis are needed. Only needs to check for one dash at the start and end because dashes have been minimized already. No need for anchor characters because .* is match everything, greedy.

C, 194 bytes


Call with:

int main()
    char *in="Loading... Forever";
    char out[128];

SAS, 108

One of the less competitive answers here due to the verbose syntax of SAS - the 9 character penalty per regex really hurts - but it was a good regex learning exercise:

t=prxchange('s/^-|-$//',-1,prxchange('s/-+/-/',-1,compress(translate(lowcase(t),'----',' .,/'),'-','adk')));

Pyth, 35 bytes

:r::rQ0"[-.,/]"d"[^\w ]"k6"[ -]+"\-


    rQ0                              Convert letters to lower case
   :   "[-.,/]"d                     Replace all -.,/ with spaces
  :             "[^\w ]"k            Remove all remaining symbols
 r                       6           Remove leading and trailing spaces
:                         "[ -]+"\-  Turn runs of spaces and dashes to one dash

Perl 6, 75

{lc .subst(/<[\ .,/]>/,"-"):g.subst(/<[\W]-[\-]>/,""):g.subst(/\-+/,"-"):g}

GNU Sed, 65 bytes

s@[ .,/]@-@g

A series of regex substitutions. Uses non-portable \L from GNU sed to lowercase the input. Run from a file using sed -f.


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.