I Re-Wrote These 10+ Single Lines of JavaScript Code from Medium
This post is in response to, but by no means a dig on the blog post written by Zard-x on Medium called ‘I Wrote These 10+ Single Lines of JavaScript Code; the Team Lead Praised the Code for Being Elegant’.
One-liners are really cool challenges and very fun to make. I used to spend a lot of my time on codewars.com and codingame.com, which are two websites that facilitates little coding challenges which challenge your ability to write short code, or write code fast.
While code golf can serve as a fun engineering exercise, their lessons should rarely be used in the real world—and when they are—by no means should engineering managers be promoting developers to use this type of code in a codebase. When you’re in a collaborative environment, writing good code should be a priority. That means code that is readable, maintainable, and runs reliably.
Code golf is a type of recreational computer programming competition in which participants strive to achieve the shortest possible source code that solves a certain problem. — Wikipedia, Code golf
Array Deduplication
Original Source Code
const uniqueArr = (arr) => [...new Set(arr)];
I’m not necessarily opposed to the common trick of removing duplicates from arrays by instantiating, and spreading a set, but due to the amount of times you’re indexing the array, it can be quite poor performance-wise.
Rewritten Source Code
const removeDuplicateStrings = (array) => {
const uniqueValues = [];
const seenMap = {};
for (const item of array) {
if (seenMap[item]) continue;
seenMap[item] = true;
uniqueValues.push(item);
}
return uniqueValues;
};
On an array of 1,000,000 randomly generated numbers between 1 - 100, the original source code only performs at 12% of the speed that the rewritten version does. Since we use a map in the second function, we only have to iterate once over the array, and made it so that checking to see if we’ve already seen an element takes O(1) time.
The code eliminates “tricks,” and instead implores a well-known concept like hash maps, which may reduce the occurrence of inquiries by other developers on your code. The code also has well-named variables, with the function name removeDuplicateStrings defining the action that the function is doing, rather than an ambiguous name like uniqueArr, it also does not use excessively abbreviated words, making the code easier for non-native English speakers to understand.
Notice that the function is not documented. Excessive documentation can sometimes be a bad thing. If your variable names are well named, you may find your comments redundant, and that’s a good thing! If that happens, happily drop ‘em, and ship it!
If we’re absolutely dead-set on using spread operators and sets, or we want to remove duplicates of values that are not exclusively strings without writing bloated functionality, all we need to do is document our code, and ensure our variables are well-named.
const removeDuplicates = (array) => {
// Turn our array into a Set, which can only contain
// unique values, and then make an array from that set.
return [...new Set(array)];
};
Get Parameters from a URL and Convert Them to Object
Original Source Code
const getParameters = URL => JSON.parse(`{"${decodeURI(URL.split("?")[1]).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"')}"}`
If there was a bug with this code, how excited would you be when you figured out this is the function you’d be working on? Odds are, not so much. Not to mention that re-building JSON objects with strings is something you want to avoid as much as possible, in this case that’d be quite easy.
Rewritten Source Code
/**
* Returns the provided URLs search parameters
* as a set of key-value pairs.
*/
const getURLParameters = (url) => {
const { searchParams } = new URL(url);
return Object.fromEntries(searchParams);
};
In the rewritten code for this function, we use the convenient native JavaScript URL API which automatically invokes an instance of the URLSearchParams object for us, then we simply use the Object#fromEntries function on the resulting object! This prevents us from having to manually construct a JSON object by mingling and mangling strings.
Notice that we’ve provided a comment for this function! Despite the fact that I’ve included a specification for which type of parameters the function handles in the name of the function getURLParameters, it’s not necessarily clear what format the resulting object would be in.
Check Whether the Object is Empty
Original Source Code
const isEmpty = obj => Reflect.ownKeys(obj).length === 0 && obj.constructor === Object;
Using ownKeys or Object.keys are both not very performant when checking if an object is empty.
Rewritten Source Code
const isObjectEmpty = (object) => {
// Iterates over the keys of an object, if
// any exist, return false.
if (object.constructor !== Object) return false;
for (_ in object) return false;
return true;
};
This rewritten code is an old school method, and man has it passed the test of time! The re-written source code performs 20 times faster than the original method, and that’s only with two properties on an object. The performance boost comes from the fact that you no longer need to create arrays, copy over values, and index keys. You see one key and you’ve escaped the function!
It might not immediately be obvious why we’re iterating over the keys of the object, so we’ve left a short comment in there to ensure that any other developers know what’s going on.
Reverse the String
Original Source Code
const reverse = str => str.split('').reverse().join('');
In this case, we’re not off to a bad start! We could use a for-loop to get an 80% performance boost on short strings, and we might consider implementing that method if we’re reversing some chunky strings often, but in the case of the odd reversal on lightweight strings, we might just be better off with this functionality.
Rewritten Source Code
const reverseString = (string) => {
return string
.split("")
.reverse()
.join("");
};
Splitting the chained methods across multiple lines is a personal preference of mine, as it allows me to scan the function paths rather than jumping through them, however if someone asked me to change it I wouldn’t make a fuss.
For fun, here is a more performant version.
const reverseString = (string) => {
let reversedString = "";
for (let i = string.length - 1; i >= 0; i--)
reversedString += string[i];
return reversedString;
};
Generate Random Hex
Original Source Code
const randomHexColor = () => `#${Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0")}`
Once again, not off to a bad start! I particularly like that we’re directly referencing the maximum hexadecimal value supported for non-alpha colours! Some minor cleanup, and we’ll be in business!
Rewritten Source Code
const getRandomHexColor = () => {
const randomValue = Math.floor(Math.random() * 0xFFFFFF);
const hexValue = randomValue.toString(16);
return hexValue.padStart(6, "0");
};
The only major change I made in this example is that I changed padEnd to padStart. The way this function works is by generating a random 24-bit integer, and then representing it in its hexadecimal format.
The minimum value we can get is 0 0x000000, with the highest value being 16,777,215 0xFFFFFF.
Let’s say our randomValue ended up being 255. Our hexValue would then be ff, and if we padded the end, our return value would be #ff0000, versus if we padded the beginning it would be #0000ff. If we then translate both these values back into integers, #ff0000 would actually represent the value 16,711,680, while #0000ff would represent 255. This means that if we pad the end rather than the beginning, we’d effectively get the wrong value.
In addition to this fact, with the first method, by padding the end rather than the beginning, #0000ff would actually be impossible to generate. Meaning there are plenty of missing values that could not possibly be generated. Specifically, every value between 0x000001 and 0x100000. That is 1,048,576 missing values from our function, but would also tip the odds in favour of all values that end with a 0, some more than others. For example, #100000 would be the result from 0x1, 0x10, 0x100, 0x1000, 0x10000, and of course 0x100000.
Conclusion
That took longer than expected, so I’ll be posting a second part to this blog post at a later date. I had a ton of fun refactoring this code. Would you have done something differently? Leave a comment on Medium or dev.to! I think this is a great learning opportunity for myself and others that might be in their code-golfing phase.
Want to read more of my stuff? You can check out my personal website. If you’d like to start a dialogue, feel free to tweet at me!