Updates list in haskell to remove duplication

Posted on

Problem

The task in question allows a user to re-rate a film such that only the latest rating from the user should remain in the database.
Initially I solved this problem in a very basic manor?

This was my result:

type Rating = (String, Int)
type Film = (String, String, Int, [Rating])
--Example ("Blade Runner","Ridley Scott",1982, [("Amy",5),("Bill",8)])

where the functions:

--Find selected film, isolate into new List
getFilm :: String -> [Film] -> [(Film)]
getFilm search aFilm = [(title,dir,year,ratings) | (title,dir,year,ratings) <- aFilm, search == title]

--Update the new film and pull details for amendment, STORE
pullTitle :: [(Film)] -> String
pullTitle [(title,_,_,_)] = title

pullDirector :: [(Film)] -> String
pullDirector [(_,director,_,_)] = director

pullYear :: [(Film)] -> Int
pullYear [(_,_,year,_)] = year

pullRatings :: [(Film)] -> [Rating]
pullRatings [(_,_,_,rating)] = rating

newRatings :: [Rating] -> Rating -> [Rating]
newRatings oldRatings (newUser,newRate) = [(user,rate) | (user,rate) <- oldRatings, user /= newUser] ++ [(newUser,newRate)]

--Database without selected film
updateDatabase :: String -> [Film] -> [(Film)]
updateDatabase search aFilm = [(title,dir,year,ratings) | (title,dir,year,ratings) <- aFilm, search /= title]

--Adds new film to a database
updatedFilm :: String -> String -> Int -> [Rating] -> [Film] -> [Film]
updatedFilm name director yearOfRel ratings database = database ++ [(name,director,yearOfRel,ratings)]

--Add new film to updated Database
formulateFilm :: [(Film)] -> [(Film)] -> Rating -> [(Film)]
formulateFilm soloFilm updatedFilms updateRating = updatedFilm (pullTitle soloFilm) (pullDirector soloFilm) (pullYear soloFilm) (newRatings (pullRatings soloFilm) updateRating) updatedFilms

--Compiling to for finalcome
rateMovie :: String -> Rating -> [Film] -> [(Film)]
rateMovie mTitle newRating oldDatabase = formulateFilm (getFilm mTitle oldDatabase) (updateDatabase mTitle oldDatabase) newRating

I have changed the following functions to make use of filters:

getFilm :: String -> [Film]
getFilm search = filter((title,_,_,_) -> search == title) testDatabase

updateDatabase :: String -> [Film]
updateDatabase search = filter((title,_,_,_) -> search /= title) testDatabase

Could someone advise me on how I would use maps to get the selected films data from getFilm :: String -> [Film], similar to the (pull*) functions?

Solution

It is better to use records instead of tuples here:

data Rating = Rating
  { user :: String
  , rate :: Int
  } deriving Show

data Film = Film
  { title    :: String
  , director :: String
  , year     :: Int
  , ratings  :: [Rating]
  } deriving Show

Thus you get self documenting code and all of pull* functions for free (for each field compiler generates accessor function with the same name).

This also leads to more concise implementation of other functions:

-- Find films by name
getFilm :: String -> [Film] -> [Film]
getFilm search = filter ((== search) . title)

Note that in Haskell there is no single element tuple, so parentheses around Film are redundant ([Film] but not [(Film)]).

newRating :: Rating -> [Rating] -> [Rating]
newRating r = (r :) . filter ((/=user r) . user) 

I switched arguments in newRating because this allows to define function more concisely and it is also correlates with update/insert from Data.Map: they have collection as a last argument.

Now it is possible to skip directly to rateMovie and implement it in couple of lines:

rateMovie :: String -> Rating -> [Film] -> [Film]
rateMovie mTitle nRating database
  = [m {ratings = newRating nRating (ratings m)} | m <- matchingMovies]
  ++ database'
  where
    (matchingMovies, database') = partition ((==mTitle).title) database

(Btw, why “movie”? we were talking about “films” all the way.)

I’m using partition from Data.List to extract matching movie(s) from database.

Also there is funny syntax I’m using to update movies:

m {ratings = ...}

You can read more about it here.

And of course it is better to use Data.Map instead of simple lists if the code is full of lookups.

Leave a Reply

Your email address will not be published. Required fields are marked *