Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting a list of custom data types by certain attribute in Haskell

Let's say we have a custom data type:

data Person = Person {  first_name :: String,
        last_name :: String,
        age :: Int
        } deriving (Ord, Eq, Show)

Let's also say I have a list of these Person data types. I've already made a function that sorts these Persons in order, but this is limited to the first value of each person, first_name. What I'm trying to do is modify the Person data type so that this sort function sorts by the age instead of first_name (besides just swapping the value order so that age is first). I know I need to use the instance keyword to write my own compare function for Ord. This is where I'm stuck. Can anyone help me out?

Edit: Yep, this is HW-- I unfortunately need to do it the way I'm describing.

like image 943
Haskell Avatar asked Apr 24 '14 00:04

Haskell


2 Answers

The easiest and most flexible way to deal with that problem, given that there are multiple valid ways to sort Person values, is not by changing the Ord instance, but by using a custom sorting function.

import Data.List (sortBy)
import Data.Ord (comparing)

sortByAge :: [Person] -> [Person]
sortByAge = sortBy (comparing age)

sortBy :: (a -> a -> Ordering) -> [a] -> [a] makes a custom sorting function from a comparison function, while comparing :: Ord a => (b -> a) -> b -> b -> Ordering makes such a comparison function given, for instance, a field acessor. Be sure to have a good look at the involved type signatures in order to grok how everything fits together.

If you do need to change the Ord instance, here is how it would go. The syntax is as follows:

instance Ord Person where
    compare = undefined -- placeholder

The documentation tells us that, from all of the Ord methods, we just need to implement compare. Now, what should we replace undefined with? We want the comparison to be based on age, which is an Int field. Since Int is an instance of Ord, the answer is immediate:

instance Ord Person where
    compare x y = compare (age x) (age y)

Incidentally, the definition of comparing is:

comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering
comparing p x y = compare (p x) (p y)

And so we could write the instance in the style we used for the first solution:

instance Ord Person where
    compare = comparing age
like image 150
duplode Avatar answered Dec 18 '22 01:12

duplode


Remove Ord from the list of derived type classes, if you have to write your own instance, the compiler generated one will clash. Also, this is not idiomatic, and I don't recommend this in general.

You can write the Ord instance yourself now the usual way:

instance Ord Person where
    compare b1 b2 = ...

I'll let you fill in the details as a useful exercise. Once that's done, the normal sort function will do what you want.

like image 29
cassandracomar Avatar answered Dec 18 '22 02:12

cassandracomar