Start learning free

LESSON 267 OF 674 · TUPLES, SORTING & BUILT-INS

Shortest Words First in C#: Sorting with OrderBy and ThenBy

Practice Read the full explanation

You get a List of words, words. Return the words sorted from shortest to longest.

Words of the same length go in alphabetical order, from A to Z.

A word can appear more than once. Every copy stays in the result.

 

Example 1:

Input: words = ["banana", "fig", "apple", "kiwi"]
Output: ["fig", "kiwi", "apple", "banana"]
Explanation: "fig" has 3 letters and "kiwi" has 4.
"apple" has 5 and "banana" has 6.

Example 2:

Input: words = ["dog", "cat", "ant", "bee"]
Output: ["ant", "bee", "cat", "dog"]
Explanation: All four words have 3 letters.
So they go from A to Z.

Example 3:

Input: words = ["sun", "a", "moon", "sun"]
Output: ["a", "sun", "sun", "moon"]
Explanation: "sun" appears twice, so it stays twice.
Both copies come after "a" and before "moon".

 

Constraints:

  • words has 1 to 100 words.
  • Each word has 1 to 20 lowercase letters.
STEP 1 OF 2
    Ctrl + Enter to check

    C# · TUPLES, SORTING & BUILT-INS

    Shortest Words First in C#, explained

    Problem overview

    SortByLength gets a List<string> named words and returns a new list with the words from shortest to longest. Words of the same length go in alphabetical order. A repeated word stays repeated.

    For ["banana", "fig", "apple", "kiwi"] the result is ["fig", "kiwi", "apple", "banana"]. For ["sun", "a", "moon", "sun"] it is ["a", "sun", "sun", "moon"]: both copies of "sun" are kept.

    Approach: sort by a key, then by a second key

    Sorting by a rule is the job of LINQ's OrderBy. You give it a lambda that returns the key to sort by, here w.Length, and it puts the items in increasing key order. It does not change the original list. It gives back a new sequence.

    Words with the same length are ties. ThenBy breaks ties with a second key, here the word itself, which for strings means alphabetical order. ThenBy only reorders items that tie on the first key, so the lengths stay in order.

    Since OrderBy gives a lazy sequence and the method returns a List<string>, the chain ends with ToList().

    Read w => w.Length as "given a word w, the key is its length". The name w is yours to choose, and the lambda is called once per word. To reverse the order, use OrderByDescending: on ["banana", "fig", "apple", "kiwi"] it gives [banana, apple, kiwi, fig]. Sorting by the word alone, OrderBy(w => w), gives [apple, banana, fig, kiwi].

    Step by step

    1. Build ordered: words.OrderBy(w => w.Length).ThenBy(w => w).ToList(), stored in a List<string>.
    2. Return ordered. Nothing was dropped, so repeated words are still there.

    The solution in C#

    class Solution {
        public List<string> SortByLength(List<string> words) {
            List<string> ordered = words.OrderBy(w => w.Length).ThenBy(w => w).ToList();
            return ordered;
        }
    }
    

    The lesson's tests run this with the LINQ methods available. In a file of your own you need using System.Linq; at the top.

    Details that matter

    • The original list is untouched. After the call, words still holds ["banana", "fig", "apple", "kiwi"], and ordered holds ["fig", "kiwi", "apple", "banana"]. List<T>.Sort() is the in-place alternative.
    • The sort is stable. Items that compare equal keep their original relative order. That does not matter here, because ThenBy orders ties, and identical words look the same.
    • ThenBy, not a second OrderBy. Each OrderBy starts a new sort that only cares about its own key. With ["b", "aa", "a", "bb"], OrderBy(w => w.Length).ThenBy(w => w) gives [a, b, aa, bb], but OrderBy(w => w.Length).OrderBy(w => w) gives [a, aa, b, bb], which is plain alphabetical order.

    Alphabetical order and capital letters

    The constraints give only lowercase letters, so ThenBy(w => w) is enough. With mixed case the default string comparison depends on the machine's culture settings, and it may place a next to A. If you need plain character-code order, pass a comparer: OrderBy(x => x, StringComparer.Ordinal) puts ["b", "A", "a", "B"] in the order [A, B, a, b], with every capital before every lowercase letter.

    Short inputs need no special care: a one-word list comes back as it is, and an empty list gives an empty list.

    Complexity

    Sorting n words takes O(n log n) comparisons, and the new list needs O(n) space.

    Testing

    Outputs from running the solution:

    wordsResult
    ["banana", "fig", "apple", "kiwi"][fig, kiwi, apple, banana]
    ["dog", "cat", "ant", "bee"][ant, bee, cat, dog]
    ["sun", "a", "moon", "sun"][a, sun, sun, moon]

    Common mistakes

    • Leaving out ToList(). The chain gives an IOrderedEnumerable<string>, which the method's return type does not accept.
    • Sorting only by length. The result looks right for some inputs because the original order of tied words is kept, but the tied words are not alphabetical in general.
    • Removing duplicates. Sorting never drops items. Do not reach for Distinct() here.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free