I am a beginner in Haskell so please bear with me. (Just started learning yesterday!) How can I sort a list of tuples primarily by their first components (highest to smallest) and secondarily by their second components (smallest to highest)? A sample input/output would be:
[(1, "b"), (1, "a"), (2, "b"), (2, "a")]
(input)
[(1, "a"), (2, "a"), (1, "b"), (2, "b")]
(middle step)
[(2, "a"), (2, "b"), (1, "a"), (1, "b")]
(output)
I tried using the following but it gave wrong output:
sortGT a b = GT
sortBy sortGT lst
I am sure that I can do this by using sortBy
only, but I can't figure it out myself. Any help would be highly appreciated!
You need to construct your function sortGT
, so that it compares pairs the way you want it:
sortGT (a1, b1) (a2, b2)
| a1 < a2 = GT
| a1 > a2 = LT
| a1 == a2 = compare b1 b2
Using this you get the following results (I used ghci):
*Main Data.List> sortBy sortGT [(1, "b"), (1, "a"), (2, "b"), (2, "a")]
[(2,"a"),(2,"b"),(1,"a"),(1,"b")]
May I suggest the following?
import Data.List (sortBy)
import Data.Monoid (mconcat)
myPredicate (a1, a2) (b1, b2) = mconcat [compare b1 a1, compare a2 b2]
You can then sort by writing sortBy myPredicate lst
. The function mconcat
simply scans through the list and obtains the first non- EQ
occurence (or EQ
if all elements are EQ
and thus both pairs are considered equal).
On second thought, building the list isn't necessary.
import Data.List (sortBy)
import Data.Monoid (mappend)
myPredicate (a1, a2) (b1, b2) = compare b1 a1 `mappend` compare a2 b2
The definition of mappend
for Ordering
is essentially:
EQ `mappend` x = x
x `mappend` _ = x
Which is exactly what we need.
Just for fun, generalizing gbacon's answer and making the use a little more flexible:
import Data.Ord
import Data.List
import Data.Monoid
ascending = id
descending = flip
sortPairs f x g y = f (comparing x) `mappend` g (comparing y)
mySort = sortBy (sortPairs descending fst ascending snd)
First we should make the ordering function wich takes two touples and returns either EQ
, LT
or GT
(ie. sortGT :: (a,b) -> (a,b) -> Ordering
.) Then we can give this ordering function to sortBy
and it will sort it's input according to this ordering.
Since you want the first components to have first priority, we check that first and if they are equal we check the second argument,if the first components is not equal we give it the opposite value of it's original ordering, so that it is ordered highest to lowest.
This is what I think is easiest on the eyes :
sortGT (a1,b1) (a2,b2) =
case compare a1 a2 of
EQ -> compare b1 b2
LT -> GT
GT -> LT
Now we use sortBy as you suggested :
*Main> sortBy sortGT [(1, "b"), (1, "a"), (2, "b"), (2, "a")]
[(2,"a"),(2,"b"),(1,"a"),(1,"b")]
Congratulations on taking your first steps to learn Haskell. It's a great journey!
Riffing on FredOverflow's answer :
import Data.Ord
import Data.List
import Data.Monoid
main :: IO ()
main = do
print $ sortBy cmp [(1, "b"), (1, "a"), (2, "b"), (2, "a")]
where
cmp = flip (comparing fst) `mappend` comparing snd
Output:
[(2,"a"),(2,"b"),(1,"a"),(1,"b")]
The following solution works best for me, a Haskell newbie. It looks a lot like 3lectrologos ' answer: I actually only added the function definition and the List import, but it can cause some confusion if left out.
Create a function 'myCompare' and don't forget to import the List module. You'll need it to make the sortBy work. The function should look like this:
import Data.List
myCompare :: (Ord a, Ord b) => (a,b) -> (a,b) -> Ordering
myCompare (a1,b1) (a2,b2)
| a1 < a2 = GT
| a2 == a1 = EQ
| otherwise = LT
After loading the Haskell file, you can write the following in your terminal:
*Main> sortBy myCompare [(1, "b"), (1, "a"), (2, "b"), (2, "a")]
Which will return:
[(2,"a"),(2,"b"),(1,"a"),(1,"b")]
It's always tricky composing two-argument functions. Here's an implementation:
invert :: Ordering -> Ordering
invert GT = LT
invert LT = GT
invert EQ = EQ
sosort :: (Ord a, Ord b) => [(a, b)] -> [(a, b)]
sosort = sortBy (\p p' -> invert $ uncurry compare $ double fst p p') .
sortBy (\p p' -> uncurry compare $ double snd p p')
where double f a a' = (f a, f a')
Because sortBy
expects a function of two arguments, function composition isn't so nice.
I have tested this code, and it works on your example.
As Fred points out, you can write compare EQ
instead of invert
. As Dario points out, I could be using on
from Data.Function
, but in fact on compare == comparing
, which I can use instead. Now the code can be read only by a Haskell Master:
sosort :: (Ord a, Ord b) => [(a, b)] -> [(a, b)]
sosort = sortBy (compare EQ `post` comparing fst) . sortBy (comparing snd)
where post f g x x' = f (g x x')
I have compiled and run this code and it works on the original example.
(I haven't got any votes for this answer, but thanks to good comments, I sure have learned a lot about the Haskell library. Who knows what function post
is equivalent to? Not Hoogle...)
It would be more idiomatic to write a suitable comparison function for pairs , but your question asked for consecutive sorts.
I like the comparing function in Data.Ord. This is basically Greg's answer in even more compact form:
Prelude Data.Ord Data.List Data.Function> (reverse.sortBy (comparing fst)) [(1, "b"), (1, "a"), (2, "b"), (2, "a")]
[(2,"a"),(2,"b"),(1,"a"),(1,"b")]
"comparing fst" gives an ordering based on the first element of the tuple.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.