1console.log("hello-world);
console.log("hello-world);
.container {
width: 80%;
}
<pre><code class="language-css">
.container {
width: 80%;
}
</code></pre>
1console.log("hello-world);
console.log("hello-world);
.container {
width: 80%;
}
<pre><code class="language-css">
.container {
width: 80%;
}
</code></pre>
So, you wanna be a pro at sorting Maps in JavaScript? Great, in this blog you’ll learn through practical examples how to sort string and numeric keys, handle complex sorting scenarios, and navigate common pitfalls. Perfect for both beginners and seasoned coders, this guide also features a fun coding challenge to apply your skills. Lets dive in! 👨💻
Map: A Map in JavaScript holds key-value pairs where keys can be any data type. To populate a map, either you can individually set key-value pairs. Or, and importantly for our sorting needs, Map has a constructor which can utilize a 2 dimensional array (an array of pairs of key-values).
// Individually setting Map values:
let myMap = new Map();
myMap.set("c", 3);
myMap.set("a", 1);
myMap.set("b", 2);
console.log(myMap);
// Output: Map(3) {'c' => 3, 'a' => 1, 'b' => 2}
// Constructor using 2 dimensional Array (of key -value).
let anotherMap = new Map([
["Orange", "Orange"],
["Grape", "Purple"],
]);
console.log(anotherMap.get("Grape"));
// Output: Map(2) { 'Orange' => 'Orange', 'Grape' => 'Purple' }
The sort()
Method: JavaScript’s sort()
method is typically used on arrays. Note that Maps are not directly sortable. However, we can convert a Map into an array, then sort the array, and then reconstruct the Map. And we can sort using default sort method or using custom comparators (explained below).
Spread Syntax in JavaScript: Spread syntax allows an iterable (like an array) to expand in places where arguments or elements are expected. It’s a convenient way to convert Maps into arrays.
map.entries() method gives an iterable of key and value pairs.
// Sort() method on Numeric Array (not usable on Map directly):
let numbers = [3, 1, 4, 1, 5, 9];
// Ascending Order of Number Array. ↗
numbers.sort(); // used without a custom comparator.
// Output: [1, 1, 3, 4, 5, 9]
// Using Spread syntax to convert Map to 2-Dimensional Array
let exampleMap = new Map([
["c", 3],
["a", 1],
["b", 2],
]);
let mapToArray = [...exampleMap];
console.log(mapToArray); // [['c', 3], ['a', 1], ['b', 2]]
// Using map.entries() to convert Map to an iterable or a 2-Dimensional Array
let exampleMap2 = new Map([
["c", 3],
["a", 1],
["b", 2],
]);
let mapToArray2 = exampleMap2.entries();
console.log(mapToArray2);
// Output iterable: [Map Entries] { [ 'c', 3 ], [ 'a', 1 ], [ 'b', 2 ] }
console.log([...mapToArray2]);
// Output 2D array: [ [ 'c', 3 ], [ 'a', 1 ], [ 'b', 2 ] ]
We can sort an array using a custom comparator function, which specified how two elements being compared can be ordered. For an expression, a - b
, we can specify following cases depending upon value of a - b
:
a
should come before b
.a
should come after b
.a
and b
concerning each other. Can be useful in stable sorting algorithms (which don’t jumble left to right order of equal elements on sorting). Optionally, you can also implement tie-breaker logic inside this case.Lets see some examples, on number and string arrays (we will see Map in coming sections):
// 1️⃣ Sorting Number Array:
let numbers = [3, 1, 4, 1, 5, 9];
// Ascending Order of Number Array. ↗
numbers.sort((a, b) => a - b); // same as numbers.sort()
console.log(numbers);
// Output: [1, 1, 3, 4, 5, 9]
// Descending Sort of Number Array. ↘
numbers.sort((a, b) => b - a);
console.log(numbers);
// Output: [9, 5, 4, 3, 1, 1]
// 2️⃣ Sorting Strings (Lexicographically) :
let fruits = ["banana", "apple", "cherry"];
// Default Ascending Order of String Array. ↗
fruits.sort();
console.log(fruits);
// Output: ['apple', 'banana', 'cherry']
// Descending Sort of Number Array. ↘
fruits.sort((a, b) => b.localeCompare(a));
console.log(fruits);
// Output: ['cherry', 'banana', 'apple']
Sorting a Map in JavaScript requires a two-step process: first, converting the Map into an array of [key, value]
pairs, and then sorting this array using various criteria. We’ll explore different sorting techniques including sorting by keys, sorting by values, and using custom sorting functions.
JavaScript Maps are collections of key-value pairs, but they don’t have a built-in sorting method.
To sort a Map, we use Map.prototype.entries()
to get an iterable of [key, value]
pairs.
The spread syntax (...
) is used to transform this iterable into an array.
Once we have an array, the Array.prototype.sort()
method can be applied to it.
sort()
method, without a comparator, sorts elements as strings in ascending order.let stringKeyMap = new Map([
["c", 3],
["a", 1],
["b", 2],
]);
let sortedByKey = new Map([...stringKeyMap.entries()].sort());
console.log(sortedByKey);
// Map { 'a' => 1, 'b' => 2, 'c' => 3 }
sort()
treats elements as strings by default.(a, b) => a[0] - b[0]
ensures that the keys are compared numerically.let numericKeyMap = new Map([
[3, "c"],
[1, "a"],
[2, "b"],
]);
let sortedNumericMap = new Map(
[...numericKeyMap.entries()].sort((a, b) => a[0] - b[0]),
);
console.log(sortedNumericMap);
// Map { 1 => 'a', 2 => 'b', 3 => 'c' }
Custom sorting based on the length of keys demonstrates the flexibility of the sort()
method.
The keys are sorted according to their length, from shortest to longest.
let keyLengthMap = new Map([
["apple", 1],
["fig", 2],
["banana", 3],
]);
let sortedByKeyLength = new Map(
[...keyLengthMap.entries()].sort((a, b) => a[0].length - b[0].length),
);
console.log(sortedByKeyLength);
// Map { 'fig' => 2, 'apple' => 1, 'banana' => 3 }
Sorting by values involves targeting the second element of each [key, value]
pair, which we obtained from map.entries()
.
Target the second element of each [key, value]
pair to get the numerical values stored in following map.
Then the comparator function (a, b) => a[1] - b[1]
sorts the Map based on its values in ascending numerical order.
let valueMap = new Map([
["c", 3],
["a", 1],
["b", 2],
]);
let sortedByValue = new Map(
[...valueMap.entries()].sort((a, b) => a[1] - b[1]),
);
console.log(sortedByValue);
// Output: Map { 'a' => 1, 'b' => 2, 'c' => 3 }
(a, b) => b[1] - a[1]
places higher values before lower ones.let valueDescMap = new Map(
[...valueMap.entries()].sort((a, b) => b[1] - a[1]),
);
console.log(valueDescMap);
// Output: Map { 'c' => 3, 'b' => 2, 'a' => 1 }
With custom comparator and map.entries(), we have full flexibility to create complex sorts (using single or multiple criteria):
Custom sorting functions offer a significant level of flexibility.
In this example, the Map is sorted based on the ‘score’ property of object values.
let objectMap = new Map([
["first", { score: 2 }],
["second", { score: 1 }],
["third", { score: 3 }],
]);
let sortedObjectMap = new Map(
[...objectMap.entries()].sort(
(a, b) => a[1].score - b[1].score,
),
);
console.log(sortedObjectMap);
// Output: Map { 'second' => { score: 1 }, 'first' => { score: 2 }, 'third' => { score: 3 } }
let complexMap = new Map([
["a", { score: 10, age: 20 }],
["b", { score: 10, age: 15 }],
["c", { score: 5, age: 30 }],
]);
let complexSortedMap = new Map(
[...complexMap.entries()].sort((a, b) => {
if (a[1].score === b[1].score) {
// Secondary sorting by age (Tie-Breaker)
return a[1].age - b[1].age;
}
// Primary sorting by score
return a[1].score - b[1].score;
}),
);
console.log(complexSortedMap);
// Output:
// Map { 'b' => { score: 10, age: 15 },
// 'a' => { score: 10, age: 20 },
// 'c' => { score: 5, age: 30 }
// }
a
and b
.a
should come before b
, zero means their order doesn’t matter, and a positive return value means a
should come after b
.true
or false
from a comparator function (as one might do using relational operators for a boolean result) will not work as intended because true
is coerced to 1
and false
to 0
in JavaScript.let numericMapForComparison = new Map([
[3, "c"],
[1, "a"],
[2, "b"],
]);
// Incorrect comparator logic using boolean expressions
let incorrectlySortedNumericMap = new Map(
[...numericMapForComparison.entries()].sort(
(a, b) => a[0] < b[0],
),
);
console.log(incorrectlySortedNumericMap);
// Sorting results might be incorrect
// Output: Map(3) { 3 => 'c', 1 => 'a', 2 => 'b' }
In your custom comparator functions, you need to be mindful of the data type used inside the function and whether the operation done on them is supported. Like, subtraction operation doesn’t work on Strings, but you can use them on numbers. Lets see some examples:
a - b
) for sorting.<
, >
): Can be used, but not ideal for sorting since they compare based on Unicode values.localeCompare
for sorting strings. Handles differences in case and special characters effectively.a - b
) works since dates are converted to timestamps.localeCompare
on the string property.// Numbers
let numbers = [3, 1, 4, 1, 5, 9];
numbers.sort((a, b) => a - b);
// Strings
let strings = ['banana', 'apple', 'cherry'];
strings.sort((a, b) => a.localeCompare(b));
// Dates
let dates = [new Date(2020, 1, 1), new Date(2019, 1, 1), new Date(2021, 1, 1)];
dates.sort((a, b) => a - b);
// Objects with Numeric Property
let items = [{ value: 10 }, { value: 5 }, { value: 15 }];
items.sort((a, b) => a.value - b.value);
// Objects with String Property
let itemsWithString = [{ name: 'John' }, { name: 'Alice' }, { name: 'Bob' }];
itemsWithString.sort((a, b) => a.name.localeCompare(b.name));
Maps in JavaScript can have keys and values of different data types, including mixed types within the same Map. However, when it comes to sorting, mixing data types can lead to unpredictable and incorrect results.
For example, when sorting a mix of strings and numbers, JavaScript will convert numbers to strings and perform a lexicographic comparison, which may not align with the intended sorting logic.
Consistency in data types is key to achieving accurate sorting results. It’s important to either ensure that all keys or all values in a Map are of the same data type or to implement a comparator function that correctly handles mixed data types.
let mixedTypeMap = new Map([
["1", 10],
["2", "apple"],
["3", 5],
]);
// Attempting to sort mixed types numerically
let sortedMixedMap = new Map(
[...mixedTypeMap.entries()].sort((a, b) => {
if (
typeof a[1] === "number" &&
typeof b[1] === "number"
) {
// Numerical comparison for numbers
return a[1] - b[1];
} else if (
typeof a[1] === "string" &&
typeof b[1] === "string"
) {
// Lexicographic comparison for strings
return a[1].localeCompare(b[1]);
}
// Default case if types are mixed or unmatched
return 0;
}),
);
console.log(sortedMixedMap);
// Sorted with type-specific logic
// Output: Map(3) { '1' => 10, '2' => 'apple', '3' => 5 }
let originalMap = new Map([['b', 2], ['a', 1], ['c', 3]]);
// Creating a new sorted map without altering the original
let sortedMap = new Map([...originalMap.entries()].sort(
(a, b) => a[0].localeCompare(b[0])
));
console.log('Sorted Map:', sortedMap);
// Sorted Map: Map(3) { 'a' => 1, 'b' => 2, 'c' => 3 }
console.log('Original Map:', originalMap);
// Original Map: Map(3) { 'b' => 2, 'a' => 1, 'c' => 3 }
The presence of undefined
and null
values in a Map can complicate the sorting process. These values often require special handling to ensure they are sorted in a manner consistent with the application’s requirements.
In JavaScript, undefined
and null
are falsy values, but they are not equal to each other. undefined
is typically used to indicate that a variable has not been assigned a value, while null
is an assignment value that represents the intentional absence of any object value.
When sorting, you must decide where these values should appear in the order. For instance, you might want null
or undefined
values to be sorted to the beginning or end of the Map, or you might want to handle them in a way that they don’t affect the sorting of other elements.
Explicit handling in the comparator function is essential to ensure that these values are sorted as per the desired logic.
let mapWithNullUndefined = new Map([['a', undefined], ['b', 2], ['c', null]]);
// Sorting while handling null and undefined explicitly
let sortedMapWithNullUndefined = new Map([...mapWithNullUndefined.entries()].sort((a, b) => {
if (a[1] === null || a[1] === undefined) {
// Place null or undefined at the start
return -1;
} else if (b[1] === null || b[1] === undefined) {
// Place null or undefined at the start
return 1;
}
// Default numerical sorting for other values
return a[1] - b[1];
}));
console.log(sortedMapWithNullUndefined);
// Sorted considering null and undefined
// Map(3) { 'c' => null, 'a' => undefined, 'b' => 2 }
Sort stability is a concept in sorting algorithms where equal elements retain their original relative order post-sorting. This feature is particularly important when sorting complex data structures or when the sorting criteria might result in equal sort keys.
In some JavaScript environments, especially older ones, the sort()
method did not guarantee stability. This means that if two elements are considered equal by the comparator function, their order after sorting might not be the same as their order before sorting.
As of ECMAScript 2019 (ES10), sort stability is guaranteed in JavaScript. However, when working in environments that may not be fully up-to-date, it’s important to be aware that sort stability might not be guaranteed.
When relying on sort stability, be cautious and verify the behavior in the specific JavaScript environment you’re targeting.
let stableSortExample = new Map([
["b", 1],
["a", 1],
["c", 2],
]);
// Assuming stable sort is supported in the environment
let sortedStably = new Map(
[...stableSortExample.entries()].sort(
(a, b) => a[1] - b[1],
),
);
console.log(sortedStably);
// 'b' and 'a' should retain their original order
// Output: Map(3) { 'b' => 1, 'a' => 1, 'c' => 2 }
In the spirit of Test Driven Development ( 😁), lets test our understanding by solving a problem.
Create a function
invertAndSortMap
that takes a Map with string keys and numeric values. The function should return a new Map where each original value becomes a key, and the original key becomes part of the value, sorted in ascending order based on the original numeric values.
Problem (JavaScript)
function invertAndSortMap(inputMap) {
// > > > 👉 Write code here 👈 < < <
}
// Example usage
const inputMap = new Map([['apple', 3], ['banana', 1], ['cherry', 2]]);
const outputMap = invertAndSortMap(inputMap);
console.log(outputMap);
// Expected output: Map { 1 => 'banana', 2 => 'cherry', 3 => 'apple' }
Please attempt before seeing the Answer:
function invertAndSortMap(inputMap) {
let invertedArray = [...inputMap.entries()].map(([key, value]) => [value, key]);
invertedArray.sort((a, b) => a[0] - b[0]);
return new Map(invertedArray);
}
Explanation:
invertAndSortMap
function starts by converting the input Map into an array of [key, value]
pairs using the spread syntax and Map.prototype.entries()
.Array.prototype.map()
method to invert each pair, making the value the new key and the original key the new value.Array.prototype.sort()
method, comparing the numeric keys (original values) to sort them in ascending order.Now you are an expert in sorting your JavaScript maps.
Keep learning, keep experimenting and keep coding! 🚀👨💻
Feel free to reach out!