Problem overview
The function sortByLength receives an array of lowercase words and returns them from shortest to longest. Words of the same length go in alphabetical order. A word that appears twice stays twice.
For ["banana", "fig", "apple", "kiwi"] the result is ["fig", "kiwi", "apple", "banana"]. For ["sun", "a", "moon", "sun"] it is ["a", "sun", "sun", "moon"].
Approach
Sorting by one rule is not enough, because words of equal length need a tie-breaker. sorted accepts a closure that receives two items, a and b, and returns true when a must go before b. Put the two rules in that order:
- If the lengths differ, the shorter word goes first.
- Otherwise, the word that comes first from A to Z goes first.
The second rule runs only when the first one doesn't decide, which is exactly what a tie-breaker means.
On the first example, the lengths are 6, 3, 5 and 4. The first rule alone puts the words in the order "fig", "kiwi", "apple", "banana". The second rule would matter if two of them had the same length, as "dog" and "cat" do in the second example, where the first rule says nothing and the alphabet decides.
The order of the rules matters. If the alphabetical check came first, it would decide every comparison between two different words, and length would never be used. The more important rule always goes first.
Step by step
- Call
words.sortedwith a closure that names its itemsa, b in. A closure with more than one line needs these names;$0and$1suit one-liners. - First rule: if
a.count != b.count, returna.count < b.count.countis the number of characters in a string. - Second rule: for equal lengths, return
a < b. On two strings,<is true when the first comes before the second in alphabetical order. - Return
ordered. Sorting only moves words around, so duplicates survive.
The solution in Swift
class Solution {
func sortByLength(_ words: [String]) -> [String] {
let ordered = words.sorted { a, b in
if a.count != b.count {
return a.count < b.count
}
return a < b
}
return ordered
}
}
Swift details:
sortedreturns a new array.wordsitself is unchanged. The in-place version issort, which needs avararray.- The closure returns a
Bool: "doesago beforeb?" Two items that are equal should givefalsein both directions. That is why the rules use strict<, never<=. - String comparison is by character values. Uppercase letters sort before lowercase ones:
["pear", "Zed", "apple"].sorted()gives["Zed", "apple", "pear"]. The lesson's words are all lowercase, so plain<gives A to Z.
Edge cases and common mistakes
- Only the second rule.
sorted { $0 < $1 }alone gives alphabetical order and ignores length. - Only the first rule. A closure that returns just
a.count < b.countorders by length but gives equal-length words no A-to-Z order:["dog", "cat", "ant"]sorted that way comes out as["dog", "cat", "ant"]. - Duplicates.
["sun", "a", "moon", "sun"]keeps both"sun"entries. - Comparing lengths the wrong way.
a.count > b.countputs the longest words first. - Sorting twice. Sorting alphabetically first and then by length alone does not reliably keep the alphabetical order inside each length. Put both rules in one closure instead.
- Reversed second rule.
return a > bfor the tie-break gives Z to A inside each length.
Testing
["banana", "fig", "apple", "kiwi"]gives["fig", "kiwi", "apple", "banana"].["dog", "cat", "ant", "bee"]gives["ant", "bee", "cat", "dog"].["sun", "a", "moon", "sun"]gives["a", "sun", "sun", "moon"].
