2019-11-03

2388

#vanilla javascript

Glad Chinda

8903

Nov 3, 2019 â‹… 8 min read

Glad Chinda
Full-stack web developer learning new hacks one day at a time. Web technology enthusiast. Hacking stuffs @theflutterwave.

Sorting (arranging data in a particular sequence or order) is a very important operation in computer science, and as such, it is very rare to talk about computer algorithms without mentioning sorting algorithms. Practically speaking, there are so many ways in which data can be sorted, which is why so many sorting algorithms exist â€” merge sort, quicksort, insertion sort, heap sort, etc.

The efficiency of a sorting algorithm when compared with another may vary based on the initial condition of the data set â€” nearly sorted, sorted in reverse order, contains duplicates, etc. Likewise, some sorting algorithms are more efficient than others for larger data sets.

In this tutorial, however, we will consider a special kind of sorting algorithm called radix sort. We will take a look at how it works and how we can implement it with JavaScript.

Most of the popular sorting algorithms perform their sort by comparing items (what item is larger than the other) in the data set, which is likely the most logical approach when it comes to arranging items in sequence. Consider this list of numbers:

75, 48, 137, 61, 206, 43, 8, 239, 124

If we were to sort this list using the insertion sort algorithm, for example, we will iterate through the items starting with the second item (48) and then try to place each item in its correct sorted position by looking backwards at the elements before it, which usually requires some comparison.

Below are the results after each iteration of the insertion sort (the results for nested iterations are not shown).

75, 48, 137, 61, 206, 43, 8, 239, 12448, 75, 137, 61, 206, 43, 8, 239, 12448, 75, 137, 61, 206, 43, 8, 239, 12448, 61, 75, 137, 206, 43, 8, 239, 12448, 61, 75, 137, 206, 43, 8, 239, 12443, 48, 61, 75, 137, 206, 8, 239, 1248, 43, 48, 61, 75, 137, 206, 239, 1248, 43, 48, 61, 75, 137, 206, 239, 1248, 43, 48, 61, 75, 124, 137, 206, 239

Since most of the efficient sorting algorithms require some form of comparison between items, does it mean that comparison is always required for sorting? Well, the answer is no. When the data set contains only integers, especially, it is possible to sort the items without comparing them â€” using radix sort.

Radix sort sorts items by grouping them into buckets according to their radix. This makes radix sort ideal for sorting items that can be ordered based on their component digits or letters, such as integers, words, etc. The grouping into buckets does not involve any comparisons.

The radix sort algorithm starts the grouping into buckets with either the least or most significant digit of each item of the data set, and then collapses the items in the buckets into a new data set containing items that are sorted based on the digit at the start position â€” this is the first iteration. The process is repeated for the other digits in each item until the data set is completely sorted.

Using our previous data set, below are the step-by-step results after each iteration of the radix sort until the data set is fully sorted.

// Initial data set[75, 48, 137, 61, 206, 43, 8, 239, 124]/* START ITERATION(#1) */// 1. Group into buckets based on unit digit // 2. Collapse items in buckets to form new data set[[], [61], [], [43], [124], [75], [206], [137], [48, 8], [239]][61, 43, 124, 75, 206, 137, 48, 8, 239] /* END ITERATION(#1) *//* START ITERATION(#2) */// 1. Group into buckets based on tens digit // 2. Collapse items in buckets to form new data set[[206, 8], [], [124], [137, 239], [43, 48], [], [61], [75], [], []][206, 8, 124, 137, 239, 43, 48, 61, 75] /* END ITERATION(#2) *//* START ITERATION(#3) */// 1. Group into buckets based on hundreds digit // 2. Collapse items in buckets to form new data set[[8, 43, 48, 61, 75], [124, 137], [206, 239], [], [], [], [], [], [], []][8, 43, 48, 61, 75, 124, 137, 206, 239] /* END ITERATION(#3) */// Final sorted data set[8, 43, 48, 61, 75, 124, 137, 206, 239]

You can see from the step-by-step process above that radix sort does not compare items at any point â€” no comparisons required. However, here are a few things to note from the above example:

All items in the data set are positive integers. It is important to note that radix sort cannot be used to sort a data set containing non-integers (numbers with decimals). However, radix sort can be implemented to sort a data set consisting of both positive and negative integers.

The first iteration groups the items into buckets based on their least significant digit, and then the iteration continues towards the most significant digit of each item. However, radix sort can be implemented to start the first iteration with the most significant digits instead.

On each iteration, 10 buckets are used because we are dealing with decimal (base 10) numbers. The buckets map to their corresponding digits in sequential order (0â€“9). Therefore, the number of buckets to be used depends on the radix (base) of the number system used for the items.

It is also important to notice that some buckets are empty for some iterations, which means that memory was allocated but never used to store anything â€” good optimization starting point.

Now that we have seen a simple example that demonstrates sorting a data set using radix sort, we can go ahead and describe the complete algorithm for radix sort as follows:

- Get the maximum digits count of the largest number
- Loop from
*k*= 0 up to the maximum digits count. For each iteration:- Create buckets for each digit (10 buckets for 0â€“9)
- Loop through the items, grouping them into buckets based on their
*k*th digits. - Collapse the items in the buckets (in order) to a flat array and update the current array reference with the new array

- Return the sorted array

The algorithm above requires some helper functions to make the implementation seamless. So before we move on to implement radix sort, letâ€™s define a couple of helper functions in the next section.

`asInteger()`

The first helper function is `asInteger()`

, which is a simple utility function we will be using in subsequent helper functions. It takes a number as its argument, removes the decimal portion of the number using `Math.trunc()`

, and returns the absolute (positive) representation of the result using `Math.abs()`

. For example, `asInteger(3.226)`

should return `3`

, while `asInteger(-12.035)`

should return `12`

.

function asInteger(num) { return Math.abs(Math.trunc(num)); }

`digitAtPosition()`

The second helper function is `digitAtPosition()`

, which takes a number (integer) and a zero-based position (integer) as its first and second arguments, and returns the digit at that position. The unit digit is at position `0`

, the tens digit at position `1`

, the hundreds digit at position `2`

, etc. For example, `digitAtPosition(3705, 2)`

should return `7`

, since 7 is the hundreds digit of 3705.

function digitAtPosition(num, pos) { return Math.floor(asInteger(num) / Math.pow(10, asInteger(pos))) % 10; }

This function uses the `asInteger()`

function defined earlier to normalize the number input and the position input. It uses the truncated position integer to get a power of 10 with which to divide the number. Finally, it floors the result and returns the remainder when divided by 10.

`digitsCount()`

The third helper function is `digitsCount()`

, which takes a number (integer) as its argument and returns the number of significant digits the integer has. For example, `digitsCount(3705)`

should return `4`

, because 3705 has 4 significant digits: 3, 7, 0, and 5.

function digitsCount(num) { return ((num = asInteger(num)) === 0) ? 1 : Math.floor(Math.log10(num)) + 1; }

Notice, again, that this function uses the `asInteger()`

function defined earlier to ensure the number is properly truncated to a positive integer. It also uses `Math.log10()`

to get the approximate power of 10 that equals the truncated number. To get the number of digits, it floors the logarithm using `Math.floor()`

and adds `1`

to the result.

- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to use the React children prop with TypeScript
- Explore creating a custom mouse cursor with CSS
- Advisory boards arenâ€™t just for executives. Join LogRocketâ€™s Content Advisory Board. Youâ€™ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Using `Math.log10()`

introduces an edge case. When the input number is `0`

, it returns `-Infinity`

. To handle this, the `digitsCount()`

function returns `1`

if the truncated number is 0, otherwise, it does the computations described above and returns the result.

`maxDigitsCount()`

The last helper function is `maxDigitsCount()`

, which takes an array of numbers (integers) and returns the `digitsCount()`

for the integer(s) in the array that have highest number of significant digits. For example, `maxDigitsCount([12, 5, 3048, 620])`

should return `4`

, since 3048 is the number in the array that has the highest number of significant digits (4).

function maxDigitsCount(nums) { return nums.reduce((max, num) => Math.max(max, digitsCount(num)), 0); }

This function simply reduces the array of numbers passed to it and returns the final `max`

value returned by the reducer function. It uses the `digitsCount()`

function inside the reducer function to get the number of digits and update the max digits count as required.

With our helper functions in place, we can now implement the `radixSort()`

function. But just before we do that, it is important to note that our version of radix sort can only correctly sort a data set containing positive integers.

That said, the following code snippet shows our implementation of the radix sort algorithm:

function radixSort(arr) { const len = arr.length;// the length of the arrayconst max = maxDigitsCount(arr);// the maximum digits countfor (let k = 0; k < max; k++) {// initialize the buckets again for grouping // create an array of 10 buckets (one for each digit)const buckets = Array(10).fill([]); for (let i = 0; i < len; i++) {// get the digit at the kth position of the number // and push the number into the corresponding bucket // based on that digitbuckets[digitAtPosition(arr[i], k)].push(arr[i]); }// collapse the items in the buckets to a flat array // updating the old array reference with the flat array // and continue to the next iterationarr = [].concat(...buckets); }// return the final sorted arrayreturn arr; }

The implementation in itself is very simple and straightforward. However, there are a few portions of the code worth highlighting.

The buckets are recreated (reset) at the beginning of each iteration. The `buckets`

array, when recreated, consists of 10 empty arrays (one for each base-10 digit, 0â€“9). Here, we are using `Array.prototype.fill()`

to fill the slots with empty arrays. However, here are some other ways you could do that:

// using spread operator and Array.prototype.map()const buckets = [...Array(10)].map(() => []);// using Array.from() and Array constructor, with map functionconst buckets = Array.from(Array(10), () => []);// using Array.from() and array-like object, with map functionconst buckets = Array.from({ length: 10 }, () => []);

Inside the nested `for`

loop, we are getting the digit at the *k*th position of the current number and also pushing into the correct bucket based on that digit. Given that the current number is 137 (`arr[i] = 137`

) and the current digit position is 1 (`k = 1`

), then this is what it looks like:

buckets[digitAtPosition(arr[i], k)].push(arr[i]);// => buckets[digitAtPosition(137, 1)].push(137); // => buckets[3].push(137);

The items in the buckets are collapsed to a flat array at the end of each iteration and used to update `arr`

. Here we are using `Array.prototype.concat()`

to flatten the `buckets`

array. It is important to pay attention to how the spread operator was used here:

const buckets = [[], [61], [], [43], [124], [75], [206], [137], [48, 8], [239]];/* without spread operator */[].concat(buckets);// [[], [61], [], [43], [124], [75], [206], [137], [48, 8], [239]]/* with spread operator(...) */[].concat(...buckets);// [61, 43, 124, 75, 206, 137, 48, 8, 239]

Letâ€™s take our radix sort one step further. Letâ€™s say we have a list of words that we want to arrange in alphabetical order. We can achieve this using radix sort. Here is a modified version of our radix sort function from before that sorts a list of words in alphabetical order.

const radixSortAlphabetical = (() => { const PADDING_CHAR = '_'; const REPLACE_REGEX = /[^a-z]/ig; const CHARS = [PADDING_CHAR].concat([ 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z' ]); function _maxStringLength(arr) { return arr.reduce((max, str) => Math.max(max || 0, str.replace(REPLACE_REGEX, '').length)); } function _charAtPosition(str, pos, maxlength = pos) { str = str.replace(REPLACE_REGEX, '').toLowerCase(); str += PADDING_CHAR.repeat(maxlength - str.length); return str.slice(-(pos + 1))[0]; } return function _radixSort(arr) { const len = arr.length; const maxlength = _maxStringLength(arr); for (let k = 0; k < maxlength; k++) { const buckets = {}; for (let i = 0; i < len; i++) { const char = _charAtPosition(arr[i], k, maxlength); buckets[char] = (buckets[char] || []).concat(arr[i]); } arr = CHARS.reduce((arr, char) => arr.concat(buckets[char] || []), []); } return arr; } })();

Here, we used an immediately invoked function expression to encapsulate the sorting logic and return the sort function. The logic is quite similar to what we had before for integers, but with some minor differences to handle alphabets. Here are some of the modifications made:

During each iteration, each string is padded at the end with a padding character (underscore in this case) until the length of the string reaches the length of the longest string in the data set. This is to ensure that all the strings are of equal length before the grouping is done.

The characters sequence contains only alphabetical characters in order (from aâ€“z). However, the padding character (underscore in this case) comes before the letters in the characters sequence. This effectively means that all strings in the data set must contain only alphabetical characters for the sort to be predictable.

An object was used here to group the items into buckets. The characters are used as keys and the array of items as values. If there are no items in the group for a character, it is taken to be an empty array.

After the strings have been padded, the grouping starts with the last character in the string up to the first character. Note that because shorter strings are padded at the end, their last character will initially be the padding character.

Our `radixSortAlphabetical()`

function works best when all the strings contain only alphabetical characters. Its behavior is highly unpredictable when other characters like numbers and symbols are present. However, the function can be improved to scale beyond some of these limitations.

Radix sort is a non-comparative sorting algorithm unlike the popular comparison sorts. At worst, the time complexity for the radix sort is ** O(kâ€¢n)** where

However, the performance of the radix sort is heavily influenced by variations in the digits count or component size of the items. Radix sort uses a lot of space in creating new arrays or objects for grouping items.

Also, it does not sort the array in place, but returns a sorted copy of the array. Hence, for very large data sets, where space optimization is a requirement, you should consider other sorting algorithms. Though we were able to come up with basic implementations of radix sort in this tutorial, it is possible to improve the implementations to scale beyond most of the inherent limitations.

Thanks for making time to go through this tutorial. I am really glad that you made it to the end, and do hope it was worth your time.

Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.

LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Let’s see how SolidJS props work to promote component reusability, exploring basic to advanced concepts for a complete understanding.

We evaluate Eleventy and Next.js and compare both static site generators in terms of performance, developer experience, scalability, and ecosystem.

We show how to use Goxgen to scaffold a full-stack React app. See how to integrate React with Go and modify Goxygen to suit your project requirements.

Express.js is a Node.js framework for creating maintainable and fast backend web applications in JavaScript. In the fast-paced world of […]

×

Would you be interested in joining LogRocket's developer community?