Saying stuff about stuff.

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)

JS Golf #4

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

Use destructuring assignment.

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