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' }
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
}
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
}
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
}
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
}
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
}
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
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
which we can finally put this into one line:
f = a => o = {} && a.map(s => o[s] = s) && o
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
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
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!)
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)
Top comments (2)
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)
Impressing! So it's 21 characters at the lowest :-)