Trying my hand at (code) golf
I’ve never attempted code golf before but encountered the following tweet that looked achievable.
JS Golf #4: play(“hello=goodbye&answer=42&x=y”) === {hello:”goodbye”,answer:”42”,x:”y”} (parse url encoded parameter strings into objects)
I thought I’d describe the journey to my shortest solution (lower score is better) which ended up touching on a few of JavaScript’s newer language features.
An unoptimised solution
Here’s my starting “normal” code that does the job.
function play(querystring) {
return querystring.split('&').reduce(function(object, param) {
const parts = param.split('=')
object[parts[0]] = parts[1]
return object
}, {})
}
Score: 104
Apply some manual minification.
function play(q){return q.split('&').reduce(function(o,p){let k=p.split('=');o[k[0]]=k[1];return o},{})}
Score: 81
Arrow functions are shorter, don’t require parentheses when there’s a single argument, and implicitly return when used without braces.
play=q=>q.split('&').reduce((o,p)=>{let a=p.split('=');o[a[0]]=a[1];return o},{})
Score: 79
play=q=>q.split('&').reduce((o,p)=>{let [k,v]=p.split('=');o[k]=v;return o},{})
Score: 75
Don’t worry about setting global variables 😱
play=q=>q.split('&').reduce((o,p)=>{[k,v]=p.split('=');o[k]=v;return o},{})
Score: 71
Template literals also call a function so there’s no need for parentheses.
play=q=>q.split`&`.reduce((o,p)=>{[k,v]=p.split`=`;o[k]=v;return o},{})
Score: 65
Arrow function implicit returns again. I couldn’t use a semicolon to turn the function body into a single line but a comma works.
play=q=>q.split`&`.reduce((o,p)=>([k,v]=p.split`=`,o[k]=v)&&o,{})
Score: 64
Using another comma instead of &&
is possible as the comma operator returns the last expression.
play=q=>q.split`&`.reduce((o,p)=>([k,v]=p.split`=`,o[k]=v,o),{})
Score: 60
I started looking at whether I could use something other than reduce
as its multiple arguments mean parenthesis can’t be removed. In the good old days before ES5 introduced reduce
and friends I would have created the data object manually by looping through the array adding properties to it like so:
function play(querystring) {
var object = {}
var params = querystring.split('&')
for (var i = 0; i < params.length; i++) {
var keyvalue = params[i].split('=')
object[keyvalue[0]] = keyvalue[1]
}
return object
}
It turns out that this technique actually shortens the code by a further 4 characters, particularly as nowadays we don’t need to use a verbose for
loop. So play
now loops over the querystring’s parameters setting key/value pairs on an accumulator object which it then returns — using map
instead of forEach
for its shorter name.
o={},play=q=>q.split`&`.map(p=>([k,v]=p.split`=`,o[k]=v))&&o
The winner? Yes!
This solution currently shares the top score of 60 characters — someone got there first with an essentially identical version.
It’s been an interesting exercise and taking a deeper look into some of JavaScript’s features has been useful — despite being focussed on hacky ways to shorten code. Re-adding whitespace and proper variable names gives us the shape of the code which looks remarkably Lisp-ish and very un-JavaScript.
object = {},
play = querystring =>
querystring.split `&`
.map(
param => (
[key, value] = param.split `=`,
object[key] = value
)
) && object
Bonus round: a different approach. Score: 63
I wondered if something could be done about the two uses of split
so tried a different approach, this time creating an array of key/values with a single split
. Combined with the lessons learned so far it’s a good effort but falls just short at 63 characters.
o={},play=q=>q.split(/[&=]/).map((p,i,a)=>i%2?o[a[i-1]]=p:p)&&o
Update: Thanks to @Benjie for removing another valuable character:
o={},play=q=>q.split(/&|=/).map((p,i,a)=>i%2?o[a[i-1]]=p:p)&&o
For good measure here it is deminified:
object = {},
play = querystring =>
querystring.split(/&|=/)
.map(
(param, index, array) =>
index % 2
? object[array[index - 1]] = param
: 1
) && object
Also on medium.com.