[Question]
  [
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 [code-golf](/questions/tagged/code-golf "show questions tagged 'code-golf'"), 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
```
  
[Answer]
## 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
```
[Answer]
# [V](http://github.com/DJMcMayhem/V), ~~41, 40, 37~~, 36 bytes
```
VuÍ[ .,\/]/-
Í0-9a-z­]
Í-«/-
Í^-ü-$
```
[Try it online!](http://v.tryitonline.net/#code=VnXDjVsgLixcL10vLQrDjcKEMC05YS16wq1dCsONLcKrLy0Kw41eLcO8LSQ&input=IkhlbGxvLCBXb3JsZCEi) or [Check all test cases at once!](http://v.tryitonline.net/#code=Vkd1w41bIC4sXC9dLy0Kw43ChDAtOWEtesKtXQrDjS3Cqy8tCsONXi3DvC0k&input=IkhlbGxvLCBXb3JsZCEiCkxvYWRpbmcuLi4gRm9yZXZlcgpOKGUocyh0KSkpIGEgc3RyaW5nCiJIZWxsbywgV29ybGQhIgpVUkwtRnJpZW5kbHkgdGl0bGVzCkMuVS5TLlIuUwoxKzIrMys0Ky4uLituID0gLTEvMTI_CkhvdyBjYW4gSSB1c2UgY21wKGEsYik)
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](https://codegolf.stackexchange.com/a/90098/31716). 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](https://github.com/DJMcMayhem/V/wiki/Regexes), 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
```
[Answer]
# 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'],
['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']]
.forEach(t=>{
  var i=t[0],k=t[1],r=F(i)
  console.log(r==k?'OK':'KO',i+' -> '+r,r==k?'':k)
})
```
[Answer]
# Retina, ~~33~~ 31 bytes
```
T`L`l
[^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](http://vihan.org/p/tio/?l=retina&p=C0nwScjhitbR1Y/V5tLl0k6I063RVamJjkvUrYpJ0Y3l@m@obaRtrG2iraenp52nYKuga6hvaGSvkAhkVinYAgA)
[Try it online!](http://vihan.org/p/tio/?l=retina&p=C0nwScjhio5L1K1S0NG1jNXm4ooJ1@bS5YrTrdFV4fpvqG2kbaxtoq2np6edp2CroGuob2hkr5AIZFYp2AIA)
## Explanation
```
T`L`l
```
This line is simple, it converts to lowercase by **T**ransliterating `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
[Answer]
# 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)
```
[Answer]
# Retina, 34 bytes
```
T`L`l
[^a-z\d .,/-]+
\W+
-
^-|-$
```
[Try it online!](http://retina.tryitonline.net/#code=VGBMYGwKW15hLXpcZCAuLC8tXSsKClxXKwotCl4tfC0kCg&input=MSsyKzMrNCsuLi4rbiA9IC0xLzEyPw)
Note the trailing newline. Essentially the OP's implementation.
[Answer]
# [MATL](https://github.com/lmendo/MATL), 38 bytes
```
'-'jyvk45y' .,/'m(t8Y245hm)'-*'45YX6L)
```
[Try it online!](https://tio.run/nexus/matl#@6@uq55VWZZtYlqprqCno6@eq1FiEWlkYpqRq6muq6VuYhoZYeaj@f9/cElqeWaqerFCcWphaWpecqqVgraCloKugj6MBgA "MATL – TIO Nexus") Or [verify all test cases](https://tio.run/nexus/matl#FVDJTsMwEL37K6YVUrbaxWmKEFJVdaH0ECHoAvSCMMo0MTh25bgR@friXmb0tnnSfF0CGvx07W827gJgg2FQh@7@kGbjqo4CGgfZ@PBxl0eX5e6SG1FIXTLGYGUstmjJc4hhE7ooikBA46yXSX@NSpkBvBuril6f7Dc5XVmJulAdOOkUNmTB9mzLNmxLeJImoyRL/NVEwwQoH/J0Sh7/TsY62FUINy@z3RrehJXiW@EAcqmRzjt63WRpQIPQBejPEVSiRXA@0ojaD3RgjlDIUrpmSl7P3g8zLUor6qYH4cKcmsA3WBRFRK5NT0Yd0cKsaFG7s0WgsKjEyXmOk7kxTvkPeFB63wPMrZD6GMcxueXpiAgQnKSUT/g/).
### 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
```
[Answer]
# [Ruby](https://www.ruby-lang.org/), ~~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!](https://tio.run/nexus/ruby#U1bULy0u0k/KzNNPzStTKCpNqlTQLfivEm@rEq9XUqSh5KgbpaCjq6@ko5SoW6WgpKmXXlyapKEfHRdTrhCrr1ldq1dckJNZomWv@/8/AA "Ruby – TIO Nexus")
> 
> **`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](https://ruby-doc.org/core-2.3.0/doc/syntax/literals_rdoc.html), 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](https://codegolf.stackexchange.com/questions/102812/url-friendly-titles#comment250085_102888).
**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)**
[Answer]
## 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.
[Answer]
# 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>`
[Answer]
# 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.
[Answer]
## 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
[Answer]
# 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("---"));//TEST
console.log(f("!@#$%ˆ&*()_+"));//TEST
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("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>
```
[Answer]
# 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.
[Answer]
# 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);
}
```
[Answer]
## 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')));
```
[Answer]
## 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
```
[Answer]
## Perl 6, 75
```
{lc .subst(/<[\ .,/]>/,"-"):g.subst(/<[\W]-[\-]>/,""):g.subst(/\-+/,"-"):g}
```
[Answer]
## 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`.
]