You don’t have to program for very long before you need to sort a list of things. Scala’s collections modules are nice enough to give you a canned sorted
method.
Maybe you have a list of words, and you want to sort them:
val xs = List( "Our", "fathers", "trusted", "in", "You", "They", "trusted", "and", "You", "delivered", "them") val sorted = xs.sorted // sorted: List[String] = // List(Our, They, You, You, and, // delivered, fathers, in, them, // trusted, trusted)
Easy enough. But isn’t that interesting? ‘Y’ words come before ‘A’ words. Or rather, more correctly, ‘Y’ words come before ‘a’ words because, by default, all capital letters are sorted before lowercase letters. And that’s all that sorted
does for you: It sorts using the default ordering.
Wouldn’t it be great if you could alter what sort
uses to sort by? Well, you can: Use sortBy
.
Here’s how to sort this list of words the way your 2nd-grade teacher taught you, that is, without respect to case. Pass sortBy
a lambda that returns the lowercase version of each string:
val sortedCaseInsensitive = xs.sortBy(_.toLowerCase) // sortedCaseInsensitive: List[String] = // List(and, delivered, fathers, in, // Our, them, They, trusted, trusted, // You, You)
Or maybe you need to sort a list of words in order of length. Employ a lambda that returns the length of each word:
val sortedByLength = xs.sortBy(_.length) // sortedByLength: List[String] = // List(in, Our, You, and, You, They, // them, fathers, trusted, trusted, // delivered)
The sortBy
method also works really well for classes. You can use sortBy
to specify which member or combination of members to sort by.
Maybe you have a class for album tracks. It has two members: track length (in minutes) and track title.
case class Track(length : Double, name : String)
Let’s say that you have the tracks of Genesis’s Foxtrot:
val ts = List( Track(7.35, "Watcher of the Skies"), Track(4.783, "Time Table"), Track(8.583, "Get 'Em Out by Friday"), Track(5.75, "Can-Utility and the Coastliners"), Track(1.65, "Horizons"), Track(22.95, "Supper's Ready") )
Sort the tracks by track length by passing a lambda to sortBy
that returns the length of each track:
val sortedByTrackLength = ts.sortBy(_.length) // sortedByTrackLength: List[Track] = List( // Track(1.65,Horizons), // Track(4.783,Time Table), // Track(5.75,Can-Utility and the Coastliners), // Track(7.35,Watcher of the Skies), // Track(8.583,Get 'Em Out by Friday), // Track(22.95,Supper's Ready))
To sort according to track name instead, use a lambda that returns the track name rather than track length:
val sortedByTrackName = ts.sortBy(_.name) // sortedByTrackName: List[Track] = List( // Track(5.75,Can-Utility and the Coastliners), // Track(8.583,Get 'Em Out by Friday), // Track(1.65,Horizons), // Track(22.95,Supper's Ready), // Track(4.783,Time Table), // Track(7.35,Watcher of the Skies))
Another sorting method is sortWith
. It differs from sortBy
in the kind of lambda that it takes to perform the sorting. Whereas sortBy
accepts a function that takes a single element and generates a single value to sort by, sortWith
accepts a function that takes two elements as inputs and returns true
if the first element is “less than” the second element, or otherwise false
. In other words, you define the pairwise comparison.
For example, if you want to sort your list of words from Psalm 22 in reverse, you could do so with sortWith
by reversing the comparison—do a greater-than comparison rather than a less-than:
val reversed = xs.sortWith(_ > _) // reversed: List[String] = List( // trusted, trusted, them, in, fathers, // delivered, and, You, You, They, Our)
sortWith
is very nice when you need to compare more than one object member. Perhaps you need to sort a list of names according to last name and then first name. The lambda can first compare the last names and then the first names only if necessary:
case class Name(first: String, last: String) val names = List( Name("Phil", "Collins"), Name("Jackie", "Collins"), Name("Joan", "Collins"), Name("Tom", "Collins"), Name("George", "Bush"), Name("Jeb", "Bush"), Name("Neil", "Bush"), Name("Marvin", "Bush")) val lastThenFirst = names.sortWith { (a, b) => if (a.last == b.last) a.first < b.first else a.last < b.last } // lastThenFirst: List[Name] = List( // Name(George,Bush), // Name(Jeb,Bush), // Name(Marvin,Bush), // Name(Neil,Bush), // Name(Jackie,Collins), // Name(Joan,Collins), // Name(Phil,Collins), // Name(Tom,Collins))