Sort by more than one key
Sort by a tuple key instead of a single field - a tuple's Compare is generated lexicographically by position, which a type never gets because an order is a decision, not a structure.
sort and sorted both take one key selector, not a list of them. A tuple key is how several fields become one
key: a tuple has a generated Compare that orders by its elements in position order, so (level, score) already
compares the way "first by level, then by score" reads.
Steps#
Build the key as a tuple, in the order you want to compare by.
(player.level, player.score)compares bylevelfirst and only looks atscorewhere two players have the samelevel.players.sort { player => (player.level, player.score) }Use
sortto reorder in place,sortedto keep the original and get a copy. Both take the same key selector;sortneeds avarpath to the list,sortedworks on aconstone and answers a newIterate.const ranked = players.sorted { player => (player.level, player.score) }Negate a numeric key to reverse its direction inside the tuple. There is no per-key ascending/descending flag, so "level ascending, score descending" is the key
(player.level, -player.score).const ranked = players.sorted { player => (player.level, -player.score) }Turn a
Boolfield into anIntfor the key.Boolhas noCompare, soplayer.eliminatedcannot stand in a tuple key directly -if player.eliminated { 1 } else { 0 }can.players.sort { player => (if player.eliminated { 1 } else { 0 }, player.score) }
Pitfalls#
- A
typehas no generated order. The tuple is what suppliesComparehere; declaring your own type for the key would need its ownComparewritten by hand, because an order is a decision atypenever makes on its own. Boolis notCompare. A tuple key that includes aBoolfield directly is rejected with "Booldoes not implementCompare" - convert it to anIntfirst, as in step 4.sortneeds avarpath to the whole list.players.sort { ... }on aconstbinding is rejected the same way any other verb on aconstpath is; usesortedfor a copy or make the bindingvar.- Every key in the tuple has to be
Compare. A field that is itself atypewith no generatedComparecannot be a tuple key element directly - pull out theInt,FloatorStringthat actually orders it. - The key closure runs again for every comparison, not once per item. A key that is expensive to compute should
be read into a separate list first and sorted alongside an index, rather than recomputed on every comparison inside
sort.
Full example#
type Player {
name: String
score: Int
level: Int
}
const players = [
Player("Ada", 10, 2),
Player("Alan", 10, 1),
Player("Grace", 20, 1),
]
const ranked = players.sorted { player => (player.level, -player.score) }
for player in ranked {
print player
}Related#
- Copy and equality - why a
typehas no generatedComparethe way it has a generatedEqualsandHash. - Tuples - the generated
Compare, lexicographic by position. - Lists -
sortandsortedalongside every other verb/participle pair.