This is the code i have:
data Review = Review { artiest :: String,
score :: Integer,
tour :: Tour,
datum :: String,
plaats :: String,
soortLocatie :: Locatie,
topSongs :: [String]
} deriving (Eq, Ord, Show)
getBestLoc [] beste = beste
getBestLoc (x:xs) beste
| (score x) > beste = getBestLoc xs (score x)
| otherwise = getBestLoc xs beste
What I'm trying to do is to get the review whit the best score, but I want the Locatie to be returned. Now I get the best score returned. How can i solve this?
EDIT
So this is the new function I tried
tester :: [Review] -> Locatie
tester = loc
where mxscr = maximumBy (compare `on` score)
loc = map soortLocatie mxscr
import Data.List (maximumBy)
import Data.Function (on)
getBestLoc :: [Review] -> Review
getBestLoc = maximumBy (compare `on` score)
This function will return the Review with the highest score. After that, getting any field of the resulting review is trivial; your desired function would be soortLocatie . getBestLoc.
A brief explanation of what is going on: according to the docs, on follows the property:
g `on` f = \x y -> f x `g` f y
so
compare `on` score == \x y -> score x `compare` score y
In other words, it compares the two scores, return one of LT, GT, EQ. Then, maximumBy takes a comparison function and a list, and returns the maximum according to the comparison function. You can think of it as maximum == maximumBy compare.
While user2407038 provides a perfectly correct answer, I want to provide a solution written slightly differently for clarity.
You want to return the Locatie of the Review with the best score. That means that all the other information in Review is immaterial for this process. We should drop it.
simplifyReview :: Review -> (Integer, Locatie)
simplifyReview r = (score r, soortLocatie r)
Now we simply want to return the pair which has the largest fst element and then we can get the second one. We'll use maximumBy to search a list of our simplified reviews
import Data.List (maximumBy)
getBestPair :: [(Integer, Locatie)] -> (Integer, Locatie)
getBestPair pairs = maximumBy comparePairs pairs where
comparePairs (score1, locatie1) (score2, locatie2) = compare score1 score2
Finally we can combine these pieces to make the desired function
getBestLocatie :: [Review] -> Locatie
getBestLocatie reviews = snd (getBestPair (map simplifyReview reviews))
Often you'll see this written in "function composition form"
getBestLocatie :: [Review] -> Locatie
getBestLocatie = snd . getBestPair . map simplifyReview
Related
I'm dabbling in some Haskell and have stumbled upon a bump in the road
I want to write a function that an AI uses to choose a row in a simple board game. The board in said game is represented by a list of integers. Like this:
board = [1,2,3,4,5]
The index + 1 of the numbers in the row on the board and the Int itself is the number of pieces left on said row. How I aim to make this function work is by first to find the largest number in the list and then return (index + 1) of this number in the form of an IO Int.
This is where I'm struggling since I can't seem to find any good answer to this online
This is what I'm working with so far:
-- Returns index of row with largest number along the number itself
aiHelper :: Board -> (Int, Int)
aiHelper xs = maximumBy (comparing fst) (zip xs [1..])
-- Returns row with largest number as IO Int
aiRow :: Board -> IO Int
aiRow xs = do
let y = snd $ aiHelper xs
return $ y
I'm not quite sure if this code does what I'm looking for, and is there an easier and cleaner solution to my code?
You can simplify the code like this:
aiRow :: Board -> IO Int
aiRow xs = return $ snd $ aiHelper xs
Or, by making an Eta reduction, to this:
aiRow :: Board -> IO Int
aiRow = return . snd . aiHelper
But, really, you don't need to return IO Int. Why not just the following?
aiRow :: Board -> Int
aiRow = snd . aiHelper
At this point, however, you're probably below the Fairbairn threshold.
After Mark's proposal, aiRow now does so little on its own that you might as well combine it with aiHelper:
maximumIndex :: Ord a => [a] -> Int
maximumIndex = fst . maximumBy (comparing snd) . zip [1..]
(Switching fst and snd.)
I need to write a function which checks if a list has two or more same elements and returns true or false.
For example [3,3,6,1] should return true, but [3,8] should return false.
Here is my code:
identical :: [Int] -> Bool
identical x = (\n-> filter (>= 2) n )( group x )
I know this is bad, and it does not work.
I wanted to group the list into list of lists, and if the length of a list is >= 2, then it is should return with true otherwise false.
Use any to get a Bool result.
any ( . . . ) ( group x )
Don’t forget to sort the list, group works on consecutive elements.
any ( . . . ) ( group ( sort x ) )
You can use (not . null . tail) for a predicate, as one of the options.
Just yesterday I posted a similar algorithm here. A possible way to go about it is,
generate the sequence of cumulative sets of elements
{}, {x0}, {x0,x1}, {x0,x1,x2} ...
pair the original sequence of elements with the cumulative sets
x0, x1 , x2 , x3 ...
{}, {x0}, {x0,x1}, {x0,x1,x2} ...
check repeated insertions, i.e.
xi such that xi ∈ {x0..xi-1}
This can be implemented for instance, via the functions below.
First we use scanl to iteratively add the elements of the list to a set, producing the cumulative sequence of these iterations.
sets :: [Int] -> [Set Int]
sets = scanl (\s x -> insert x s) empty
Then we zip the original list with this sequence, so each xi is paired with {x0...xi-1}.
elsets :: [Int] -> [(Int, Set Int)]
elsets xs = zip xs (sets xs)
Finally we use find to search for an element that is "about to be inserted" in a set which already contains it. The function find returns the pair element / set, and we pattern match to keep only the element, and return it.
result :: [Int] -> Maybe Int
result xs = do (x,_) <- find(\(y,s)->y `elem` s) (elsets xs)
return x
The another way to do that using Data.Map as below is not efficient than ..group . sort.. solution, it is still O(n log n) but able to work with infinite list.
import Data.Map.Lazy as Map (empty, lookup, insert)
identical :: [Int] -> Bool
identical = loop Map.empty
where loop _ [] = False
loop m (x:xs) = if Map.lookup x m == Nothing
then loop (insert x 0 m) xs
else True
OK basically this is one of the rare cases where you really need sort for efficiency. In fact Data.List.Unique package has a repeated function just for this job and if the source is checked one can see that sort and group strategy is chosen. I guess this is not the most efficient algorithm. I will come to how we can make sort even more efficient but for the time being let's enjoy a little since this is a nice question.
So we have the tails :: [a] -> [[a]] functions in Data.List package. Accordingly;
*Main> tails [3,3,6,1]
[[3,3,6,1],[3,6,1],[6,1],[1],[]]
As you may quickly notice we can zipWith the tail of tails list which is [[3,6,1],[6,1],[1],[]], with the given original list by applying a function to check if all item are different. This function could be a list comprehension or simply the all :: Foldable t => (a -> Bool) -> t a -> Bool function. The thing is, I would like to short circuit zipWith so that once i meet the first dupe let's just stop zipWith doing wasteful work by checking the rest. For this purpose i can use the monadic version of zipWith, namely zipWithM :: Applicative m => (a -> b -> m c) -> [a] -> [b] -> m [c] which lives in Control.Monad package. The reason being, from it's type signature we understand that it shall stop calculating any further when it accounts for a Nothing or Left whatever in the middle if my monad happens to be Maybe or Either.
Oh..! In Haskell I also love to use the bool :: a -> a -> Bool -> a function instead of if and then. bool is the ternary operation of Haskell which goes like
bool "work time" "coffee break" isCoffeeTime
The negative choice is on the left and the positive one is on the right where isCoffeeTime :: Bool is a function to return True if it is coffee time. Very composable as well.. so cool..!
So since we now have all the background knowledge we may proceed with the code
import Control.Monad (zipWithM)
import Data.List (tails)
import Data.Bool (bool)
anyDupe :: Eq a => [a] -> Either a [a]
anyDupe xs = zipWithM f xs ts
where ts = tail $ tails xs
f = \x t -> bool (Left x) (Right x) $ all (x /=) t
*Main> anyDupe [1,2,3,4,5]
Right [1,2,3,4,5] -- no dupes so we get the `Right` with the original list
*Main> anyDupe [3,3,6,1]
Left 3 -- here we have the first duplicate since zipWithM short circuits.
*Main> anyDupe $ 10^7:[1..10^7]
Left 10000000 -- wow zipWithM worked and returned reasonably fast.
But again.. as i said, this is still a naive approach because theoretically we are doing n(n+1)/2 operations. Yes zipWithM cuts redundancy down greatly if the first met dupe is close to the head but still this algorithm is O(n^2).
I believe it would be best to use the heavenly sort algorithm of Haskell (which is not merge sort as we know it by the way) in this particular case.
Now the algorithm award goes to -> drum roll here -> sort and fold -> applause. Sorry no grouping.
So now... once again we will use a monadic trick to utilize short circuits. We will use foldM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b. This, when used with Either monad also allows us to return a more meaningful result. OK lets do it. Any Left n means n is the first dupe and no more calculations while any Right _ means there are no dupes.
import Control.Monad (foldM)
import Data.List (sort)
import Data.Bool (bool)
anyDupe' :: (Eq a, Ord a, Enum a) => [a] -> Either a a
anyDupe' xs = foldM f i $ sort xs
where i = succ $ head xs -- prevent the initial value to be equal with the value at the head
f = \b a -> bool (Left a) (Right a) (a /= b)
*Main> anyDupe' [1,2,3,4,5]
Right 5
*Main> anyDupe' [3,3,6,1]
Left 3
*Main> anyDupe' $ 1:[10^7,(10^7-1)..1]
Left 1
(2.97 secs, 1,040,110,448 bytes)
*Main> anyDupe $ 1:[10^7,(10^7-1)..1]
Left 1
(2.94 secs, 1,440,112,888 bytes)
*Main> anyDupe' $ [1..10^7]++[10^7]
Left 10000000
(5.71 secs, 3,600,116,808 bytes) -- winner by far
*Main> anyDupe $ [1..10^7]++[10^7] -- don't try at home, it's waste of energy
In real world scenarios anyDupe' should always be the winner.
I am having trouble with an assignment question!
Write the function
freq2 :: String -> -> [(Int,[Char])]
Like freq, the function freq2 counts frequency of occurrence of alphabetic characters.
Given the string:
We hold these truths to be self-evident, that all men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty and the pursuit of Happiness
I need to end up with:
[(1,"qv"), (2,"gm"), (3,"cfpwy"), (4,"b"), (5,"u"), (6,"do"),(8,"s"), (9,"ln"), (10,"i"), (12,"r"), (13,"h"), (16,"a"),(22,"t"), (28,"e")]
So far I can get to:
[('q',1),('v',1),('g',2),('m',2),('c',3),('f',3),('p',3),('w',3),('y',3),('b',4),('u',5),('d',6),('o',6),('s',8),('l',9),('n',9),('i',10),('r',12),('h',13),('a',16),('t',22),('e',28)]
Using:
freq2 :: String -> [(Char,Int)]
freq2 input = result2
where
lower_case_list = L.map C.toLower input
filtered_list = L.filter C.isAlpha lower_case_list
result = L.map (\a -> (L.head a, L.length a)) $ L.group $ sort filtered_list
result2 = sortBy (compare `on` snd) result
Is there an easy way to get to the last stage or to do the whole thing, possibly using library functions? Or can you please provide some direction on how to finish off this question?
Thanks
Something like this appended to your solution should work:
result3 = map (\xs#((_,x):_) -> (x, map fst xs)) $ L.groupBy ((==) `on` snd) result2
My preference would be to use a Map for these types of problems though:
import qualified Data.Map as Map
import qualified Data.Char as C
import qualified Data.Tuple as T
string = filter C.isAlpha $ map C.toLower "We hold these truths to be self-evident, that all men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty and the pursuit of Happiness"
swapMapWith f = Map.fromListWith f . map T.swap . Map.toList
freq2 :: String -> [(Int, String)]
freq2 = Map.toList . swapMapWith (++) . foldl (\agg c -> Map.insertWith (+) [c] 1 agg) Map.empty
Method 1:
import needed modules
import Data.Char
import Data.List
filter out uninterested characters and convert the rest to lower case
toLowerAlpha :: String -> String
toLowerAlpha = map toLower . filter isAlpha
sort first, then group, after that the length of each group is the frequency of character in that group
elemFreq :: (Ord a) => [a] -> [(Int, a)]
elemFreq = map (\l -> (length l, head l)) . group . sort
sort and group as step 2, but according to frequency at here, then combine all those characters that have the same frequencies
groupByFreq :: (Integral a, Ord b) => [(a, b)] -> [[(a, b)]]
groupByFreq = groupBy (onFreq (==)) . sortBy (onFreq compare)
where onFreq op (f1,_) (f2,_) = op f1 f2
collectByFreq :: (Integral a) => [[(a, b)]] -> [(a, [b])]
collectByFreq = map (\ls -> (fst . head $ ls, map snd ls))
sequence the above functions will give the required function
freq2 = collectByFreq . groupByFreq . elemFreq . toLowerAlpha
Method 2:
import needed modules
import qualified Data.Char as Char
import qualified Data.Map as Map
filter out uninterested characters and convert the rest to lower case
toLowerAlpha :: String -> String
toLowerAlpha = map Char.toLower . filter Char.isAlpha
create a map, key and value are character and corresponding frequency, respectively
toFreqMap :: (Ord a, Num b) => [a] -> Map.Map a b
toFreqMap = foldr (\c -> Map.insertWith (+) c 1) Map.empty
convert the map created in step 2 to another map, using frequency as key, and characters have that frequency as value
toFreqCol :: (Ord a, Ord b) => Map.Map a b -> Map.Map b [a]
toFreqCol = Map.foldrWithKey (\k a m -> Map.insertWith (++) a [k] m) Map.empty
sequence the above functions will give the required function
freq2 = Map.toAscList . toFreqCol . toFreqMap . toLowerAlpha
I've a problem with a function like this:
data City = City {Car :: String, Weight :: Int, Color :: String}
-I've a list of "City" and my function has to make a list of tuples, and each tuple is (Car, "sum of the weight"), therefore, if a the Car is equal, the weight has to be added, making something like this:
main> [(Porche,180),(Ferrari,400),(Opel,340)]
The car's can't be repeated on the output list, because their wheights must be added.
I was thinking of doing something like making a list with all the car types, and then filtering the weights and add them, making a list, but I just can't make it work.
I will guide you to the solution. It is better to understand how to arrive at the solution than the solution itself.
import Data.List
data City = City {car :: String, weight :: Int, color :: String} deriving (Show)
If color has nothing to do with City being equal you can convert the City to a tuple. You can use map to do that.
city2tup :: [City] -> [(String,Int)]
city2tup = map (\(City c w _) -> (c,w))
Now look at function sort and groupBy from Data.List. Sorting and then grouping on the first element will collect together similar cars in a list. So you will have a list of list.
Now you just need to fold on each sublist and add corresponding weights.
collect :: [City] -> [(String,Int)]
collect = map (foldl1 collectWeight) . groupBy ((==) `on` fst) . sort . city2tup
You still need to define what collectWeight is but that should be easy.
In terms of performance, perhaps it's best to use the Data.HashMap.Lazy package for this job. Accordingly you may do it as follows;
import qualified Data.HashMap.Lazy as M
data City = City {car :: String, weight :: Int, color :: String}
ccw :: [City] -> [(String, Int)]
ccw [] = []
ccw (x:xs) = M.toList $ foldr addWeight (M.singleton (car x) (weight x)) xs
where
addWeight :: City -> M.HashMap String Int -> M.HashMap String Int
addWeight c r = case M.lookup (car c) r of
Nothing -> M.insert (car c) (weight c) r
Just x -> M.adjust (+ weight c) (car c) r
λ> ccw [City "Porsche" 180 "Black", City "Ferrari" 400 "Red", City "Opel" 340 "White", City "Porsche" 210 "Yellow"]
[("Opel",340),("Ferrari",400),("Porsche",390)]
I got a custom Data type named Student which has marks for 2 subjects. I have created a function named average that calculates the average of two. All work fine.
My question is how can I sort list of students by their average?
data Student = Student
{studentName :: String,
subject1 :: Double,
subject2 :: Double} deriving (Show)
average :: Student -> Double
average (Student _ sub1 sub2) = (sub1 + sub2) / 2
students :: [Student]
students = [Student "Dave" 50.0 40.0,
Student "Joe" 65.0 90.0,
Student "Ann" 75.0 82.0]
P.S. I am a beginner in Haskell and don't know whether its got a
inbuilt average function but I prefer if I can sort my list in similar
way without using inbuilt average function (if it is there) as this
small test solution to be use with a different type of function
instead average.
import Data.Function (on)
import Data.List (sortBy)
studentsSortedByAverage = sortBy (compare `on` average) students
Note that those are backticks around on, not single quotes.
Here are links to docs for sortBy and on.
If you are using an old compiler that doesn't come with Data.Function, here is the definition of on:
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
(.*.) `on` f = \x y -> f x .*. f y