Home > front end >  haskell Ordering type by values and its field
haskell Ordering type by values and its field

Time:11-03

I have types like

data Fruit = Apple Date
           | Balana Date 
           | Orange Date

f = [Apple 2020-01-01 ,Apple 2020-02-01 
     ,Balana 2020-01-01 ,Balana 2020-02-01]

getDate:: Fruit -> Date
getDate (Apple x) = x
getDate (Balana x) = x
getDate (Orange x) = x

when sorting the list f by date field.

instance Ord Fruit where
    compare f1 f2 =  compare (getDate f1) (getDate f2)

But ,the question is , how to setup rule to order Apple 2020-01-01 and Balance 2020-01-01 ?

what if I want to sort the date first ,if the date are same for different types of fruits, I would order Apple first , then Balance then Orange ?

[Apple  2020-01-01, Balance  2020-01-01, Apple 2020-02-01  ,Balana 2020-02-01]

Thanks !

CodePudding user response:

There are some different approaches you can use:

The most straightfoward one is not what you want but usefull to know

data Fruit = Apple Date
           | Balana Date 
           | Orange Date
           deriving (Eq, Ord)

This creates automatic order for you type, but in this case it will compare the fruit first and the Date later... which is the opposite you want.

Therefore we need to manually define an Ord instance.


-- This get the date from fruit
getDate :: Fruit -> Date
getDate (Apple x) = x
getDate (Balana x) = x
getDate (Orange x) = x

-- This function compares the fruit with no date.
fruitOrder :: Fruit -> Fruit -> Ordering
fruitOrder (Apple _)  (Apple _)  = EQ
fruitOrder (Apple _)  _          = LT

fruitOrder (Orange _) (Orange _) = EQ
fruitOrder (Orange _) _          = GT

fruitOrder (Balana _)  (Balana _) = EQ
fruitOrder (Balana _)  (Apple _)  = GT
fruitOrder (Balana _)  (Orange _) = LT

-- Your ordering is a combination of both
instance Ord Fruit where
  compare f g = comparing getDate f g <> fruitOrder f g
  --            |                     |- This returns the first not EQ value or EQ if both are equal
  --            |- comparing func x y == compare (func y) (func y)

A different modeling

Now, probably you are modelling your types in a non idiomatic way. If all your items will have a date, then you should encode your type as pairs as shown below

-- This is an alternative representation. You have Fruit types ordered as written
data FruitType = Apple | Banana | Orange deriving (Eq, Ord)

-- A fruit is a Date and a FruitType, this is Record syntax
-- you can think of it as C's structs or Python's dataclasses.
data Fruit = Fruit {getDate :: Date, getType :: FruitType} deriving (Eq, Ord)

-- The derived order is lexicographic, meaning they are ordered by the first field, and then by the second field

As an example, this program orders your list as expected using the suggested types

import Data.List

type Date = String -- For the sake of example

-- This is an alternative representation. You have Fruit types ordered as written
data FruitType = Apple | Banana | Orange deriving (Show, Eq, Ord)

-- A fruit is a Date and a FruitType, this is Record syntax
-- you can think of it as C's structs of Python's dataclasses.
data Fruit = Fruit {getDate :: Date, getType :: FruitType} deriving (Show, Eq, Ord)

f = [ Fruit "2020-01-01" Apple
    , Fruit "2020-02-01" Apple
    , Fruit "2020-01-01" Banana
    , Fruit "2020-02-01" Banana]


main = print $ sort f
  • Related