Home > database >  Haskell - Sort by first second element and then by first element
Haskell - Sort by first second element and then by first element

Time:05-05

I have a list of tuples and I would like to sort it by second element (descending) and then by first element (ascending).

My code looks like this:

sortedOcc :: Eq a => [a] -> [(a, Int)]
sortedOcc = sortBy (flip compare `on` snd) . occurences

and this is the first sorting by the second element of list returned by occurences (function). How should I add the second sort (ascending) by the first element?

CodePudding user response:

The Data.Ord module provides a Down newtype whose purpose is solely to reverse the ordering.

It also provides a comparing function:

comparing :: Ord a => (b -> a) -> b -> b -> Ordering

which must be fed some transformation function before it can be passed to sortBy.

Like this:

$ ghci
 GHCi, version 8.8.4: https://www.haskell.org/ghc/  :? for help
 λ> 
 λ> sortBy  (comparing (\(a,v) -> (Down v, a)))   [(1,2),(1,3),(5,2),(5,3)]
 [(1,3),(5,3),(1,2),(5,2)]
 λ> 

The values returned by the transformation function are then sorted using their own “natural” order. In our case, this is the lexicographic order on pairs of ordered types.

Overall, the code would require an Ord a constraint:

sortedOcc :: Ord a => [a] -> [(a, Int)]
sortedOcc = sortBy (comparing (\(a,v) -> (Down v, a)))  .  occurences

CodePudding user response:

What is sortBy's signature?

sortBy :: (a -> a -> Ordering) -> [a] -> [a]

This means that its first argument must have the type a -> a -> Ordering:

sortedOcc :: Eq a => [a] -> [(a, Int)]
sortedOcc = sortBy g . occurences

g :: a -> a -> Ordering
g = (flip compare `on` snd)

but that means that

g :: a -> a -> Ordering
g x y = (flip compare `on` snd) x y
      = flip compare (snd x) (snd y)
      = compare (snd y) (snd x)

and so to add your requirement into the mix we simply have to write it down,

      = let test1 = compare (snd y) (snd x)
            test2 = compare (snd y) (snd x)
        in ......

right?

The above intentionally contains errors, which should be straightforward for you to fix.

A word of advice, only use point-free code if it is easy and natural for you to read and write, and modify.

CodePudding user response:

I'd probably write this using the Monoid instance on Ordering and on function types.

Sorting on the second value in the tuple looks like flip compare `on` snd, as you've already determined, while sorting on the first value looks like compare `on` fst. These can be combined Monoidally with <>.

d :: [(String , Int)]
d = [("b", 1), ("a", 1), ("c",3), ("d",4)] 

sortedD = sortBy ((flip compare `on` snd) <> (compare `on` fst)) d

CodePudding user response:

I know that the rest of the answers are shorter, but I recommend you to implement these lazy functions yourself before using the already Haskell implemented ones, so you understand how it works.

-- Order a list of tuples by the first item
orderBy1stTupleItem :: Ord a => (a, b1) -> (a, b2) -> Ordering
orderBy1stTupleItem tup1 tup2
  | item1 > item2 = GT
  | item1 < item2 = LT
  | otherwise = EQ
  where
      item1 = fst tup1
      item2 = fst tup2

-- Order a list of tuples by the second item
orderBy2ndTupleItem :: Ord a1 => (a2, a1) -> (a3, a1) -> Ordering
orderBy2ndTupleItem tup1 tup2
  | item1 > item2 = GT
  | item1 < item2 = LT
  | otherwise = EQ
  where
      item1 = snd tup1
      item2 = snd tup2

-- Wrapper Function: Order a list of tuples by the first item and later by the second item
orderTuplesBy1stThenBy2ndItem :: (Ord a1, Ord a2) => [(a2, a1)] -> [(a2, a1)]
orderTuplesBy1stThenBy2ndItem listTuples = 
    sortBy orderBy2ndTupleItem (sortBy orderBy1stTupleItem listTuples)

Example

let exampleListTuples = [(1,2),(0,8),(6,1),(3,6),(9,1),(7,8),(0,9)]

Then let's get the 1st list, ordered by the first item of each tuple:

> listOrderedByTuple1stItem = sortBy orderBy1stTupleItem exampleListTuples
> listOrderedByTuple1stItem
[(0,8),(0,9),(1,2),(3,6),(6,1),(7,8),(9,1)]

Now we order this result list by the second item of each tuple

> sortBy orderBy2ndTupleItem listOrderedByTuple1stItem
[(6,1),(9,1),(1,2),(3,6),(0,8),(7,8),(0,9)]

Or, you can just run the wrapper function orderTuplesBy1stThenBy2ndItem as follows:

> sortBy orderTuplesBy1stThenBy2ndItem exampleListTuples
  • Related