Start learning free

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

Custom Sorting in Scala: sortBy with a Function

Lesson Read the full explanation

TL ; DR:

To sort by your own rule, give sortBy a small function. It says what to sort each item by.


val nums = scala.collection.mutable.ArrayBuffer(25, 13, 5, 41, 33)

val byLastDigit = nums.sortBy(n => (n % 10, n))
// ArrayBuffer(41, 13, 33, 5, 25)

val scores = scala.collection.mutable.ArrayBuffer(70, 95, 82)

val highFirst = scores.sortBy(s => -s)
// ArrayBuffer(95, 82, 70)


Full lesson:

You met sorted in the Shopping Cart project. It sorts from small to large, or from A to Z. Sometimes you need another order. The shortest word may have to come first, or the highest score.

Then use sortBy with a small function. The function w => w.length is written in one line. It has no name. It gets one value, w, and returns its length. The sortBy method sorts the items by what the function returns.

  • The call sortBy(w => w.length) sorts by length.
  • The call sortBy(n => (n % 10, n)) uses two rules. The function returns a tuple: the last digit, then the number. Tuples are compared item by item. So numbers with the same last digit go from small to large.
  • The call sortBy(s => -s) sorts numbers from high to low. The biggest number has the smallest -s.

The sortBy method makes a new ArrayBuffer. The old one stays as it was.

To sort an ArrayBuffer itself, use sortInPlaceBy instead.


Your task

A pet shop sells toys. Their prices are in prices. Make an ArrayBuffer named highFirst with the prices from high to low.

The shop's pets are in names. Make an ArrayBuffer named byLastLetter with the names sorted by their last letter, in alphabetical order. Names with the same last letter go from A to Z.

The code already prints both, one item per line.

Expected output:

30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
STEP 1 OF 2
    Ctrl + Enter to check

    SCALA · TUPLES, SORTING & BUILT-INS

    Custom Sorting in Scala, explained

    Why custom sorting

    sorted puts numbers from small to large and strings from A to Z. Often you need another order: the highest price first, the shortest word first, or names ordered by their last letter. For that, give sortBy a small function that says what to sort each item by. The function w => w.length has no name. It gets one value, w, and returns its length. sortBy then sorts the items by what the function returns.

    Three common rules

    • sortBy(w => w.length) sorts by length, shortest first.
    • sortBy(n => (n % 10, n)) uses two rules. The function returns a tuple: the last digit, then the number. Tuples are compared item by item, so numbers with the same last digit go from small to large.
    • sortBy(s => -s) sorts numbers from high to low: the biggest number has the smallest -s.
    val nums = scala.collection.mutable.ArrayBuffer(25, 13, 5, 41, 33)
    val byLastDigit = nums.sortBy(n => (n % 10, n))

    Printed, byLastDigit is ArrayBuffer(41, 13, 33, 5, 25). The first item of the tuple sorts by last digit; the second puts 13 before 33 and 5 before 25.

    Things to know

    • sortBy makes a new ArrayBuffer and leaves the old one as it was. With words holding ArrayBuffer(pear, fig, apple), words.sortBy(w => w.length) gives ArrayBuffer(fig, pear, apple), and words still prints ArrayBuffer(pear, fig, apple).
    • To sort an ArrayBuffer itself, use sortInPlaceBy instead. After words.sortInPlaceBy(w => w.length), words prints ArrayBuffer(fig, pear, apple).
    • The -s trick has one trap: -Int.MinValue is still Int.MinValue because Int overflow wraps silently. For ArrayBuffer(Int.MinValue, 3), sortBy(s => -s) leaves Int.MinValue first, which is wrong for high to low. Ordinary prices are safe.

    The lesson's task

    A pet shop has toy prices 12, 5, 30 and 8, and pets Rex, Luna, Bella, Max, Bo and Coco. Make highFirst with the prices from high to low, and byLastLetter with the names sorted by their last letter, with names that share a last letter going from A to Z.

    1. Call sortBy on prices with a function that turns each price into -price. The biggest price has the smallest -price, so it comes first.
    2. For the names, call sortBy with a function that returns the tuple (name(name.length - 1), name). The first item is the last letter: a string indexed with parentheses gives a Char, and chars sort in alphabetical order.
    3. The second item is the name itself, so names with the same last letter go from A to Z. Max and Rex both end in x, and this puts Max first.
    class Solution {
        def mainFunction(): Unit = {
            val prices = scala.collection.mutable.ArrayBuffer(12, 5, 30, 8)
            val names = scala.collection.mutable.ArrayBuffer("Rex", "Luna", "Bella", "Max", "Bo", "Coco")
    
            val highFirst = prices.sortBy(price => -price)
            for (price <- highFirst) {
                println(price)
            }
    
            val byLastLetter = names.sortBy(name => (name(name.length - 1), name))
            for (name <- byLastLetter) {
                println(name)
            }
        }
    }

    The prices print as 30, 12, 8, 5. The names end in a (Bella, Luna), o (Bo, Coco) and x (Max, Rex), so they print as:

    Bella
    Luna
    Bo
    Coco
    Max
    Rex

    Common mistakes

    • Expecting the original buffer to change. sortBy returns a new one; print that one, not prices or names.
    • Forgetting the second rule. With only the last letter, names that share it keep whatever order they started in.
    • Mixing up the direction. sortBy(s => s) goes small to large; -s flips it for numbers.

    Key takeaways

    • sortBy sorts by what a small function returns for each item.
    • Return a tuple for several rules; later items only matter on ties.
    • Negating a number sorts high to low; sortBy returns a new buffer, sortInPlaceBy changes the old one.
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free