Problem overview
The function sort_by_length receives a vector of words, words, and returns it sorted from the shortest word to the longest. Words of the same length go in alphabetical order. A word that appears more than once stays in the result as many times as it appeared.
For ["banana", "fig", "apple", "kiwi"] the answer is ["fig", "kiwi", "apple", "banana"]: lengths 3, 4, 5 and 6. For ["dog", "cat", "ant", "bee"] every word has 3 letters, so the order is alphabetical: ["ant", "bee", "cat", "dog"]. For ["sun", "a", "moon", "sun"] the answer is ["a", "sun", "sun", "moon"].
Approach
A plain sort() would order the words alphabetically only. Here the main rule is length, and the alphabet only breaks ties. sort_by takes a closure that compares two items, so two rules fit in one closure: compare the lengths first, and compare the words themselves only when the lengths are equal.
The cmp method compares two values and returns an Ordering, which is Less, Equal or Greater. The method then on an Ordering keeps the first result unless it is Equal; only then does it use the second one. That is exactly the tie-breaking rule the statement asks for.
Step by step
- Sort in place. The parameter is declared
mut words, sowords.sort_by(...)can reorder the vector itself. The closure gets two items,aandb, as references. - First rule:
a.len().cmp(&b.len())puts the shorter word first. The argument ofcmpis a reference, hence the&. - Second rule: chain
.then(a.cmp(b)). Comparing two strings withcmporders them alphabetically, andthenuses it only for words of equal length. - Return
words. Sorting only moves words around, so duplicates are never dropped.
The solution in Rust
impl Solution {
fn sort_by_length(mut words: Vec<String>) -> Vec<String> {
words.sort_by(|a, b| a.len().cmp(&b.len()).then(a.cmp(b)));
return words;
}
}
len() counts bytes, which equals the number of letters here because the words use only lowercase letters a to z. String comparison is by bytes too, so lowercase words come out in A to Z order.
Another way
When both rules point the same way, sort_by_key can compare a tuple of keys. Tuples compare field by field, so the length decides first and the word breaks ties:
words.sort_by_key(|w| (w.len(), w.clone()));
On ["sun", "a", "moon", "sun", "kiwi", "fig"] this gives ["a", "fig", "sun", "sun", "kiwi", "moon"]. The clone() is needed because the key must be an owned value. The lesson's sort_by avoids that copy, and it is the form that also works when one rule must run in reverse, such as sorting scores from high to low.
Testing
The three examples give:
["fig", "kiwi", "apple", "banana"]
["ant", "bee", "cat", "dog"]
["a", "sun", "sun", "moon"]
Edge cases and common mistakes
- Forgetting the second rule. With only the length comparison, the order of equal-length words is not defined by the rule you wrote, so the tests for ties could fail.
- Swapping
aandbin the length rule. That sorts the longest word first. - Calling
sort_byon a vector declared withoutmut. The compiler reports that it cannot borrow the vector as mutable; the lesson's signature already hasmut words. - Thinking
sort_byreturns the sorted vector. It sorts in place and returns nothing, which is why the function sorts first and returnswordson the next line.
