# URL-Friendly Titles

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:

stewies-sequence


# 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.

# Rules

• 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  • What about leading -s? Will they have to be removed? For example in asdf-, will the last - have to be removed? – user41805 Dec 10 '16 at 13:26 • Can we use a built-in function to check if the char is alphanumeric like this if(isalphanum(ch))... – Mukul Kumar Dec 10 '16 at 13:27 • @KritixiLithos Minify groups of adjacent dashes (a---b -> a-b), remove any that are leading/trailing. I guess this should make you clear. – Mukul Kumar Dec 10 '16 at 13:47 • And what about _ underscores? My code works except when there are underscores. – user41805 Dec 10 '16 at 13:48 • @L3viathan Doesn't matter now, I changed my code so that even underscores will be removed – user41805 Dec 10 '16 at 14:07 ## 19 Answers # Retina, 33 31 bytes TLl [^a-z ,-9]+ \W+ - ^-|-$


(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!

## Explanation

TLl


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 ,./-.

\W+
-


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

• I think this will fail for inputs like a = b. – Martin Ender Dec 14 '16 at 15:44
• I really want to accept this, but as martin said, it doesn't minify adjacent dashes when you input a = b :( – FlipTack Dec 18 '16 at 18:01
• @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 – Downgoat Dec 19 '16 at 2:56

## JavaScript (ES6), 908279 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 • For ,a^a,, this code gives -aa- (there are leading/trailing hyphens) – user41805 Dec 10 '16 at 15:49 • @KritixiLithos Oh, thanks for pointing this out. I didn't pay attention to that rule. That should be fixed. – Arnauld Dec 10 '16 at 16:09 # V, 41, 40, 37, 36 bytes VuÍ[ .,\/]/- Í0-9a-z­] Í-«/- Í^-ü-$


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. # Explanation 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 :%s/[^0-9a-z\-]//g :%s/-\+/- :%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,'')


Test

F=
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'], ['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'], ['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'],
['0123', '0123'],
['a a1', 'a-a1'],
['2-1=1', '2-11']]
.forEach(t=>{
var i=t[0],k=t[1],r=F(i)
console.log(r==k?'OK':'KO',i+' -> '+r,r==k?'':k)
})

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

# Python 3, 10310096 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)

• @Flp.Tkc Indeed.. – L3viathan Dec 10 '16 at 15:20
• Oops, I accidentally downvoted this. I can't reverse my vote until you edit this post – user41805 Dec 11 '16 at 16:50
• @KritixiLithos Done – L3viathan Dec 11 '16 at 16:51

# Retina, 34 bytes

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

\W+
-
^-|-$ Try it online! Note the trailing newline. Essentially the OP's implementation. • The TIO link points to a slightly different code – user41805 Dec 10 '16 at 16:02 • Yeah I just fixed it. – Mama Fun Roll Dec 10 '16 at 16:03 • You can use TLl to go to lowercase with less bytes – Downgoat Dec 10 '16 at 18:18 • Fails on a.. snd similar – Downgoat Dec 11 '16 at 17:32 • [\W] is just \W – Martin Ender Dec 14 '16 at 15:39 # MATL, 38 bytes '-'jyvk45y' .,/'m(t8Y245hm)'-*'45YX6L)  ### Explanation '-'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, 61606164 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)

• 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. – Value Ink Dec 11 '16 at 7:09
• @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. – Synoli Dec 11 '16 at 14:54
• 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. – Value Ink Dec 12 '16 at 4:16 • -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! – Value Ink Dec 12 '16 at 10:59 • Since nobody said it, welcome to code-golf! – FlipTack Dec 12 '16 at 21:10 ## JavaScript (ES6), 74 69 bytes f= 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. • I think you have to include the HTML code into the bytecount since it is being used to solve the challenge – user41805 Dec 11 '16 at 7:44 • @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. – Neil Dec 11 '16 at 10:00 • [a-z\d] could be [^\W_] ? – edc65 Dec 15 '16 at 11:47 • @edc65 Nice, but I then realised that it could be even simpler! – Neil Dec 15 '16 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

• mightn't $input save you 2 bytes? – briantist Dec 11 '16 at 22:23 # JavaScript, 909894939190 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("---"));//TEST console.log(f("!@#$%ˆ&*()_+"));//TEST

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

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

console.log(f("C.U.S.R.S"));
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("0123")); console.log(f("a a1")); console.log(f("2-1=1")); <input oninput=a.textContent=f(this.value)><pre id=a> • Doesn't remove leading dashes: "-1" becomes "-1", when it should become "1". – L3viathan Dec 10 '16 at 14:08 • @L3viathan Should work now – user41805 Dec 10 '16 at 14:11 • 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 – edc65 Dec 10 '16 at 14:18 • Hei! I'm not that old! (65 not 64) – edc65 Dec 10 '16 at 15:00 • 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. – Ismael Miguel Dec 10 '16 at 15:09 # Lua, 91 bytes a=a:lower():gsub( '[ .,/]', '-' ):gsub( '[^%w-]', '' ):gsub( '%-+', '-' ):match'%-?(.*)%-?'  Where a is the URL string. Explanation: • 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 i,j;f(char*s,char*d){if(*s>47&*s<58|*s>96&*s<123)d[i++]=*s;if(*s>64&*s<91)d[i++]=*s+32;if(i-j&&*s>43&*s<48|*s==32&&*(s+1)&&*(s+1)>47|(*(s+1)<44&&*(s+1)^32)){d[i++]=45;j=i;}*++s?f(s,d):(d[i]=0);}  Call with: int main() { char *in="Loading... Forever"; char out[128]; f(in,out); puts(out); }  ## 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"[ -]+"\-


### Explanation

    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/.*/\L\0/
s@[ .,/]@-@g
s/[^-a-z0-9]//g
s/-\+/-/g
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.