Haskell - Selectively Adding Lists - list

I have the following type:
type Rating = (String, Int)
type Film = (String, String, Int, [Rating])
testDatabase :: [Film]
testDatabase = [("Director 1","Film 1",2012,[("TestRat",8)]),("Director 2","Film 2",2,[])]
I need to find out what the average rating of the Director is based on all of their films combined and then all of their ratings combined. I genuinely have no idea how to approach this, I found it hard enough just to get the average of the tuples in the Film let alone work through all of them and do it that way.
My code for working out averages:
filmRating :: [(String,Int)] -> Float
filmRating ratings = average (map snd ratings)
average ratings = realToFrac (sum ratings) / genericLength ratings

There are many useful functions in Data.List for data analysis.
In particular, groupBy is super useful:
> :t groupBy
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
Given an equality function, group a list into buckets.
A function to access the directory:
> let fst4 (x,_,_,_) = x
Then, sort on the director name, and bucket (group) by that director.
> let db0 = sortBy (comparing fst4) testDatabase
> let db1 = groupBy ((==) `on` fst4) db0
[ [("Director 1","Film 1",2012, [("TestRat",8)])]
, [("Director 2","Film 2",2 ,[])]
]
groupBy is very useful...

Related

Filter a list of tuples and return the first item based on a condition that applies to the second item

The task is as follows: I have a list of tuples containing country names, and the official language. I have another list with "3 item tuples" of translators in (first name, last name, language) format. I need to get a list of the countries, which official language is NOT spoken by any of the translators.
It has to be one call to a function of such form:
foo :: [(String, String)] -> [(String, String, String)] -> [String]
I have tried all sorts of map and filter combinations to no avail. The closest I came was using sets.
third :: (a, b, c) -> c
third (_, _, z) = z
translators x = Set.fromList (map third x)
languages x = Set.fromList (map snd x)
diff x y = Set.toList (Set.difference (languages x) (translators y))
This gives me all the languages that are in the first list but not spoken by the translators. However of course, the task is to give a list of the country names, not the languages. So I tried this as well, but it does not work:
foo x y = filter ((Set.notMember (translators y)).snd) x
I'm a beginner and I would much appreciate some help.
Your first function gives you the correct answer - except it gives you the list of languages. How about an assoc list so you can lookup country associated with the language?
To do that, you just have to swap the country and language in the (Country, Language) tuple
import Data.Tuple ( swap )
langMap :: [(String, String)] -> [(String, String)]
langMap = map swap
Now once you have the result of your own diff function, you just have to map a lookup function over the resulting list
diff :: [(String, String)] -> [(String, String, String)] -> [String]
diff x y = map (fromJust . (`lookup` langMap')) diffList
where
diffList = Set.toList $ Set.difference (languages x) (translators y)
langMap' = langMap x
If you are concerned about performance, you can replace the assoc list by a Data.Map
import qualified Data.Map as Map
langMap :: [(String, String)] -> Map.Map String String
langMap = Map.fromList . map swap
And also change the lookup in diff to Map.lookup.
diff :: [(String, String)] -> [(String, String, String)] -> [String]
diff x y = map (fromJust . (`Map.lookup` langMap')) diffList
where
diffList = Set.toList $ Set.difference (languages x) (translators y)
langMap' = langMap x
However, there's one caveat here - the lookup method will only work if you have only one country associated with each language.
If you want to support the situation when there are multiple countries associated with the same language. You need some extra batteries on the langMap-
langMap :: [(String, String)] -> Map.Map String [String]
langMap x = Map.fromListWith (++) [(v, [k]) | (k, v) <- x]
This is a slight modification of this answer. It creates a map of String and [String]. The key string is the language, and the value is a list of countries
Now, since the value of the map is a list of strings instead of just a string - we also need to change the diff a bit
diff :: [(String, String)] -> [(String, String)] -> [String]
diff x y = concatMap (fromJust . (`Map.lookup` langMap')) diffList
where
diffList = Set.toList $ Set.difference (languages x) (translators y)
langMap' = langMap x
Huh, that was simple. Just changing map to concatMap is enough - it'll flatten the list of list of strings, into just a list of strings.
Let's see it in action-
λ> xs = [("US", "English"), ("Mexico", "Spanish"), ("France", "French"), ("Spain", "Spanish"), ("UK", "English"), ("Italy", "Italian")]
λ> ys = [("Foo", "Spanish"), ("Bar", "French")]
λ> diff xs ys
["UK","US","Italy"]
Let's focus on correctness, on solving the problem, not on the solution being an efficient one. That we can take care of later. Correctness first, efficiency later!
Simplest and most visual way to code this is using List Comprehensions.
foo :: [(String, String)] -> [(String, String, String)] -> [String]
-- Country Language FirstN LastN Language
-- return: list of Contries with no translator
foo cls nnls = [ c | (c, ___) <- cls, -- for each country/language pair,
-- test the {list of ()s for each translator that ...}
___ [ () | (_, _, ___) <- nnls, ___ == ___] ]
Fill in the ___ blanks (you don't have to fill the _ blanks, those are Haskell's anonymous, throwaway variables).
Now you can make it efficient, treating the above as executable specification.

Filter a haskell list

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

Haskell Adding Elements to Tuples/Lists in order

I have defined a list of (String, Int) pairs.
type PatientList = [(String,Int)]
I need to add data to this list in form 'name' and 'number', where number will increment for each addition to the list, for example the list (or tuple) after 3 name additions will look like:
[("bob", 1), ("ted", 2), ("harry", 3)]
Name will be captured using the following code:
do putStr "You are? "
name <- getLine
My current solution is to create a list of names e.g. (bob, ted, harry) and then using zip, combine these lists as follows:
zip = [1...]["bob","ted","harry"]
This solution doesn't fulfil my requirements as I wish to add to the list at different times and not combine together. How can I do this?
Isn't it better to keep the list in reverse order?
[("harry", 3), ("ted", 2), ("bob", 1)]
Than adding will be in constant time:
add :: PatientList -> String -> PatientList
add [] newName = [newName]
add ((oldName, x):xs) newName = (newName, x+1):(oldName, x):xs
When you need whole list in order, you just in O(lenght yourList) linear time:
reverse patientList
You could use an IntMap, from the containers package.
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
type PatientList = IntMap String
registerPatient :: PatientList -> String -> PatientList
registerPatient pList name
| IntMap.null plist = IntMap.singleton 1 name
| otherwise = let (n, _) = findMax pList
in IntMap.insert (succ n) name plist
As said before if speed isn't a concern use length
add :: String -> [(String, Int)] -> [(String, Int)]
add name xs = xs ++ [(name, length xs)]
But if you remove an element this will mess up you id so maybe
add name xs = xs ++ [(name, 1 + ( snd (last xs) ) )]
I haven't tried running any of this because I'm not a computer with ghc, but you should get the idea.

Haskell select elements in list

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)]

Haskell List of Data Type Sorting

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