DEV Community

Jan Küster
Jan Küster

Posted on

Let's solve a one-liner code puzzle

From time to time I commit to the joy of solving programming puzzles / coding challenges. Some of them are mostly generic and can be solved by choosing one's favorite programming language. They rather focus on conceptual problem solving. Others are language specific and exhaust the (combination of) paradigms that are inherent to a certain language.

An exotic form of the more specific ones is the one-liner puzzle, or simply one-liner. This kind always pushed me beyond my limits and in the end it felt so rewarding to master them while having learned many new things in the process.

This is why I decided to share my one-liner-experiences with you here. It will include you and me solving a simple one-liner together and I hope you're so hooked afterwards that you are finally eager to solve the ⭐ bonus challenge ⭐.

Before we start, I'd like to give you a short introduction and if you haven't solved any programming puzzle yet, you may pick a challenge at one of the various coding challenge platforms. (I am not affiliated with any of them)

What's so special about one-liner puzzles?

Programming puzzles may have long and complex instructions. If they do, they often involve a lot of theory and reasoning before getting started with actual programming. One-liners in contrast represent instant action, because their use case is mostly simple and the introduction is short and clear.

Examples of classic use cases are among others

  • reverse a string / array
  • find a certain pattern in string / array
  • transpose / transform values in array

For our example I created a similar instruction. It starts with the following:

Consider a function f which receives an Array of Strings and returns an Object that contains the Strings as key values:

f: [String] => {}

...

Sounds easy peasy, right? Well, besides being just one line of code, the result also needs to fulfill some other criteria.

Ruthless mindtwisters

One-liners usually come with a maximum limit of characters for the single line of code. This limit is often so short, that classical approaches will fail miserably. You can forget your Swiss army knife of functions, they won't help you that much.

Advanced one-liners will come with even harder constraints, such as forbidden methods and properties (if these are not already unfeasible, due to the length restriction), disabled language features like import / require (so no lodash or underscore) or performance constraints due to very large inputs.

Platforms may enforce these rules using static code analysis (server-side) and reject any form of violation or cheating.

Get rid of good practices

In order to solve one-liners you will use a bunch of features that are considered 🚫 "bad practices" 🚫. Therefore, I will always add some info, if a bad practice is involved.

Note, that a friendly gesture of the coding challenge platforms is that they often have strict mode disabled. This opens the door for some weird but useful options.

Let's take at this point some of the above mentioned constraints and add them to the instruction for our example:

Consider a function f which receives an Array of Strings and returns an Object that contains the Strings as key values:

f: [String] => {}

Your solution should be in one line and use less than 32 characters. Features like import / require are disabled.

You can assume that the input array is defined and contains only String values.

Have fun!

You can try to solve it on your own now and continue to read later. I'd be very interested in alternative solutions.

A step by step solution

In the first part we create a reference implementation that satisfies the unit tests. By doing so we can ensure validity while the code changes. Fortunately, most coding challenge platforms provide a convenient TDD setup as part of their challenges with a minimal set of tests.

Here are some simple (pseudocode) assertions, that should satisfy as simple unit tests for now:

[] => {}
['a', 'b', 'c'] => { a: 'a', b: 'b', c: 'c' }
['0', '1', '2'] => { 0: '0', 1: '1', 2: '2' }
['text key'] => { 'text key': 'text key' }
Enter fullscreen mode Exit fullscreen mode

Now, we create a potential reference implementation using core features and readable code that would also pass a standard linter:

function f (arr) {
  const obj = {}
  arr.forEach(function(str) {
    obj[str] = str
  })
  return obj
}
Enter fullscreen mode Exit fullscreen mode

It starts with 104 chars and 7 lines of code. Very far from 1 line and 31 characters. Anyway, the tests all pass and from here we can now start to narrow it down to a one-liner.

Single character variables

The very first and most obvious step is to reduce all variable names to a single character. It is a bad practice, because it takes all contextual information off the variable names. However, it is required for our solution as it reduces the length from 104 to 88 characters:

function f (a) {
  const o = {}
  a.forEach(function(s) {
    o[s] = s
  })
  return o
}
Enter fullscreen mode Exit fullscreen mode

In the next step we will start the foundation of one-liner code by using ES6 arrow functions for f and for the map callback. The new code will be 81 chars in 7 lines:

const f = a => {
  const o = {}
  a.forEach(s => {
    o[s] = s
  })
  return o
}
Enter fullscreen mode Exit fullscreen mode

We can easily get rid of forEach as it occupies 7 characters of space. An immediate substitute for forEach is map. It saves us 4 characters but results in the same behavior for our use case:

const f = a => {
  const o = {}
  a.map(s => {
    o[s] = s
  })
  return o
}
Enter fullscreen mode Exit fullscreen mode

Declare variables in global scope

Now let's get rid of the variable declaration const. This is possible, because f is declared in global scope and we assume not to be in strict mode. Note, that this is under normal circumstances a very bad practice. There is much to read about "why global scope should be avoided" and if you have not read about it you should do it asap! You can start with this gist to get a first impression what could go wrong.

Back to our code. Let's leave out const and also let's make the map function one line. Thereby, we reduce the new code to 55 characters in 5 lines:

f = a => {
  o = {}
  a.map(s => o[s] = s)
  return o
}
Enter fullscreen mode Exit fullscreen mode

Use type coercion to return values from arrow functions

Next, we want to get rid of the return statement. Instead, the function should resolve to directly to the o value.

To make this possible, we use of how Javascript's type coercion behavior works: The logical and (&&) does not cast values to boolean but continues evaluation to the last truthy or falsy value. This value is also used as the expression's assignment:

const x = { prop: 42 }
const y = x && x.prop
y // 42
Enter fullscreen mode Exit fullscreen mode

In this example y is assigned neither true nor false but the value of x.prop if, and only if, x exists (is truthy).

Using this behavior, we could also "chain" statements in one line, because

  • o is an object (truthy)
  • a.map returns an array (truthy) after execution
  • o will be added as most right part of the logical expression, which will act as the last value of the expression, thus as our arrow function's return value.

Applying these points to our f function it may look like this:

f = a => 
  o = {} && 
  a.map(s => o[s] = s) && 
  o
Enter fullscreen mode Exit fullscreen mode

which we can finally put this into one line:

f = a => o = {} && a.map(s => o[s] = s) && o
Enter fullscreen mode Exit fullscreen mode

I would not consider this a 100% bad practice but you should be aware, that type coercion can defy control structures. For example, if you need to check for a value being defined, a branch like if (x) will result in a false-negative for values like 0 or "".

By the way, the length is at 44 characters now.

Use parameter defaults

This one-liner code above unfortunately breaks all the unit tests. There will be an error due to the missing declaration of o, even with disabled strict mode. So how can we create the o object implicitly at each call?

We can move it to the arguments by using default parameter values. This ensures that o is always truthy:

f = (a, o = {}) => a.map(x => o[x] = x) && o
Enter fullscreen mode Exit fullscreen mode

Note that we indirectly introduce a bad practice here. While default parameters are not bad at all, the f function manipulates the o parameter's properties. If we would now call f with an explicit second parameter (like f([], {}) we are actually mutating en externally declared variable (read more about function parameters to know why and when this happens). The function would not be guaranteed to be free of side-effects anymore.

Remove white space

Finally, we remove all white space between the characters. This is obviously a bad practice (allthough I haven't seen it in real source code yet), because it further reduces readability to a minimum and should only be used in the step of minification.

However, we finally have a potential valid solution to our puzzle with 31 characters in one line 🎉🎉🎉:

f=(a,o={})=>a.map(x=>o[x]=x)&&o
Enter fullscreen mode Exit fullscreen mode

Bonus challenge

If you got hooked and want to challenge yourself immediately, I have a little task for you. I have created it on my own and it's very basic. Thus, chances are high it might appear on some other platform as well. Here is the task:

Write a function, which receives to arrays as parameters and returns their total length as a single integer value:

f: [][] => Integer

You are not allowed to use the length property. Maximum allowed characters are 26. Assume the arrays to be defined and non-null.

Have fun!

What I have learned in the process

As you may already have realized, this one-liner required a multitude of basic skills and knowledge:

  • You need to know core APIs and a great part of the language in order to know potential steps toward a solution

  • You also need to know extended functionality, for example ES6 arrow Functions

  • You often need to explore yet undiscovered parts of the language / APIs in order to get new solution paths

  • You need to think out of the box all the time, because the way you usually implement a solution for this would usually be of no use here

There are also some points to take away for coding in general:

  • Re-check your knowledge on fundamentals. Do you really know them all?

  • Keep up with new language features (ES6 up to ES) to maximize your options.

  • Test driven development is strongly encouraged when optimizing code to ensure validity.

  • Bad practices can still lead to valid (but not good) solutions. Unit tests may not reveal them. Use a linter / static code analyses for all the code you produce!

Some final words

This article began as a short notice why I love one-liners and grew into a detailed report on these little mindbusters and their implications for programming practice. I also learned some of the quirky features of Javascript and it can be creepy to see how well they can be part of working code.

I hope you got interested in the puzzles and maybe learned something new. I would appreciate a comment, if you came up with an alternative solution or found issues in the writing style. I am still in the process of writing as a non-native speaker, so any feedback will be a great help.

Reveal of the bonus challenge (spoiler alert!)

Alt Text

You can solve it by applying the second array to the first array's push method, which takes an arbitrary length of arguments and returns the new size of the expanded array:

f=(a,b)=>a.push.apply(a,b)
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
chico1992 profile image
chico1992 • Edited

I got a solution of 39 characters and after seeing yours i got to an even shorter one
f=(a,b)=>a.push(...b)
My first solution was
f=(a,b)=>[...a,...b].reduce((d)=>1+d,0)

Collapse
 
jankapunkt profile image
Jan Küster

Impressing! So it's 21 characters at the lowest :-)