Kert Kukk
Kert Kukk

Reputation: 715

How to sort custom Type by String

How can I sort animals by their name? Names are represented by String values.

data Animal = Cat String | Dog String | Fox String deriving (Show)

testAnimals = [(Dog "c"),(Fox "a"),(Cat "b")]

sortAnimalsByName :: [Animal] -> [Animal]
sortAnimalsByName animals = undefined

The query:

sortAnimalsByName testAnimals

should return:

[(Fox "a"),(Cat "b"),(Dog "c")]

I think the function sort :: Ord a => [a] -> [a] from Data.List should be used, but how?

Upvotes: 2

Views: 115

Answers (3)

samsergey
samsergey

Reputation: 228

Joining answers, given by @Willem Van Onsem (record-syntax) and @urbanslug (implementing Ord class instance) we obtain the simplest one:

data Animal a = Cat {name :: a}
              | Dog {name :: a}
              | Fox {name :: a} deriving (Show, Eq)

instance Ord a => Ord (Animal a) where
  a1 `compare` a2 = name a1 `compare` name a2

So that it is possible to say simply

λ> sort [(Dog "c"),(Fox "a"),(Cat "b")]
[Fox {name = "a"},Cat {name = "b"},Dog {name = "c"}]

Using Data.Ord.comparing function one coud write even more precisely:

instance Ord a => Ord (Animal a) where compare = comparing name

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476567

First you better define an animalName function:

animalName :: Animal -> String
animalName (Cat n) = n
animalName (Dog n) = n
animalName (Fox n) = n

Simply use sortBy where the comparator is simply compare but on the animalName:

import Data.Function(on)
import Data.List(sortBy)

sortAnimalsByName :: [Animal] -> [Animal]
sortAnimalsByName = sortBy (compare `on` animalName)

Or you can - like @JonPurdy suggests, use comparing which is basically the same as on compare:

import Data.Ord(comparing)

sortAnimalsByName :: [Animal] -> [Animal]
sortAnimalsByName = sortBy (comparing animalName)

or even shorter:

import Data.List(sortOn)

sortAnimalsByName :: [Animal] -> [Animal]
sortAnimalsByName = sortOn animalName

You can furthermore omit the definition of animalName if you use record-syntax:

--omitting the `animalName` function
data Animal = Cat { animalName :: String} |
              Dog { animalName :: String} |
              Fox { animalName :: String} deriving (Show)

Upvotes: 8

urbanslug
urbanslug

Reputation: 146

Edited Thank you @WillemVanOnsem for your comment.

First of all this should be a proper sum type so that you can avoid repeating String all over. Meaning, you should make it

type Animal a = Dog a | Cat a | Fox a

This way you would have a more general Animal type (which is a good thing). One of the advantages of a more general type would be it would allow you to create more precise typeclass instances of Animal.

However in your case the easiest way would be to derive Show, Eq, and then write an Ord instance like so:

data Animal = Cat String | Dog String | Fox String deriving (Show, Eq)

instance Ord Animal where
  (Cat x) `compare` (Cat y) = x `compare` y
  (Cat x) `compare` (Dog y) = x `compare` y
  (Cat x) `compare` (Fox y) = x `compare` y
  (Dog x) `compare` (Cat y) = x `compare` y
  (Dog x) `compare` (Dog y) = x `compare` y
  (Dog x) `compare` (Fox y) = x `compare` y
  (Fox x) `compare` (Cat y) = x `compare` y
  (Fox x) `compare` (Dog y) = x `compare` y
  (Fox x) `compare` (Fox y) = x `compare` y

or

data Animal a = Cat a | Dog a | Fox a deriving (Show, Eq)

instance Ord a => Ord (Animal a) where
  (Cat x) `compare` (Cat y) = x `compare` y
  (Cat x) `compare` (Dog y) = x `compare` y
  (Cat x) `compare` (Fox y) = x `compare` y
  (Dog x) `compare` (Cat y) = x `compare` y
  (Dog x) `compare` (Dog y) = x `compare` y
  (Dog x) `compare` (Fox y) = x `compare` y
  (Fox x) `compare` (Cat y) = x `compare` y
  (Fox x) `compare` (Dog y) = x `compare` y
  (Fox x) `compare` (Fox y) = x `compare` y

You can use sort because now Animal has an Ord instance

-- sort :: Ord a => [a] -> [a]

sort testAnimals

Point is: use typeclasses. That is just the easiest way for your simple type.

Upvotes: 3

Related Questions