Problem overview
sortByLength(words) receives an array of lowercase words and returns them sorted from shortest to longest. Words of the same length go in alphabetical order. A repeated word stays repeated.
For ["banana", "fig", "apple", "kiwi"] the answer is ["fig", "kiwi", "apple", "banana"]. For ["sun", "a", "moon", "sun"] it is ["a", "sun", "sun", "moon"]: both copies of "sun" stay.
Approach: a compare function with two rules
Array.prototype.sort takes an optional compare function. It gets two items, a and b, and returns a number:
- negative:
agoes beforeb; - positive:
bgoes beforea; - zero: the two are equal for sorting purposes.
Without a compare function, sort compares items as strings. For numbers that gives surprising results: [10, 9, 1].sort() gives [1, 10, 9]. For words it would give plain alphabetical order, which ignores length. So this lesson gives sort its own rule.
The rule has two parts. First compare lengths: if they differ, return a.length - b.length. That is negative when a is shorter, so the shorter word goes first. Only when the lengths are equal does the second rule matter: return a.localeCompare(b), which is negative, zero or positive according to alphabetical order.
Step by step
- Call
words.sortwith an arrow function ofaandb, with a body in braces. - The first line of the body handles different lengths:
if (a.length !== b.length), then return the difference. - The next line is the tie-break for equal lengths: return
a.localeCompare(b). - After the sort, return
words.
The solution in JavaScript
class Solution {
sortByLength(words) {
words.sort((a, b) => {
if (a.length !== b.length) {
return a.length - b.length;
}
return a.localeCompare(b);
});
return words;
}
}
sort changes the array in place and also returns it, so words is already in order after the call. The tests only look at the returned array. If you need to keep the original order, sort a copy: [...words].sort(...).
The three examples give:
["fig","kiwi","apple","banana"]
["ant","bee","cat","dog"]
["a","sun","sun","moon"]
What the compare function returns
Calling the rule by hand shows how it decides. With the function above as cmp, a shorter word first gives a negative number and a longer one a positive number, and for equal words the result is 0:
cmp("fig", "kiwi") -> -1 (fig is shorter, so it goes first)
cmp("kiwi", "fig") -> 1
cmp("cat", "ant") -> 1 (same length, "ant" comes first)
cmp("sun", "sun") -> 0 (equal, so both copies stay)
The exact positive or negative number can differ (the length rule returns the difference, such as -1 or 5), so only its sign matters. localeCompare follows the language's alphabetical rules, which for lowercase letters a to z is plain A to Z.
Complexity
The sort does O(n log n) comparisons for n words, and each comparison costs at most the length of a word, which is at most 20 here.
Edge cases and common mistakes
- Forgetting the compare function.
words.sort()alone orders alphabetically, so"banana"would come before"fig". - Using only the length rule. Words of equal length would then keep the order they started in. A sort with only
a.length - b.lengthturns["dog", "cat", "ant", "bee"]into["dog", "cat", "ant", "bee"]again, not A to Z. - Using
===or a boolean. A compare function must return a number. Returninga.length > b.lengthgivestrueorfalse, which sort reads as 1 or 0, never negative. On["banana", "fig", "apple", "kiwi"]that comparison leaves the array as["banana", "fig", "apple", "kiwi"], unsorted. - Dropping duplicates. Sorting only moves items. Both copies of a repeated word stay.
