How to extract an element from a tuple in haskell - list

I have the following tuples:
type Id = Int
type Name = String
type FSK = Int
type Movie = (Id, Name, FSK)
now I want to define a function that extracts a movie from a given list like that:
extract :: Id -> [Movie] -> (Maybe Movie, [Movie])
extract = .... ??
so that when I give an id and a list of Movies, it extracts:
1) Nothing +the given list, if the id is found
2) Just + the movie , and the new list without that movie, if the given id is found, and the movie is deleted from the list
example:
*Main> extract 0 [(1,"Matrix",16),(2,"Gladiator",0)]
(Nothing,[(1,"Matrix",16),(2,"Gladiator",0)])
*Main> extract 1 [(1,"Matrix",16),(2,"Gladiator",0)]
(Just (1,"Matrix",16),[(2,"Gladiator",0)])
How should I define the function?

extract :: Id -> [Movie] -> (Maybe Movie, [Movie])
extract id [] = (Nothing, [])
extract id ((fid, n, f):list) | fid == id = (Just (fid, n, f), list)
| otherwise = (found, (fid, n, f):tail)
where (found, tail) = extract id list

Related

Denote list to extract parts

I have a database table that I have tried to model in OCaml. However, I get problems when I try to write a function that extracts columns. I have managed to write a function that takes a table with only one column, but if I have a table with more columns then I get a match failure. I would need help with how to denotate my table in my pattern matching.
(* Table with many columns *)
let tableWithManyColumns =
"carTable",
[ ("RegNumber", ["1";"2";"3";"4"]);
("Brand", ["BMW";"SAAB";"Volvo";"Jeep"]);
("Year",["2000";"2003";"2001";"2012"]);
];;
(*Table with one columns*)
let tableWithOneColumn = "carTable",
[
("RegNumber", ["1";"2";"3";"4"])
];;
(*Current extractColumn*)
let extractColumn (t:string * (string * string list) list) =
match t with
(a,[(b,c)])-> b;;
(* Returns Regnumber *)
extractColumn(tableWithOneColumn);;
(*Returns match failure*)
extractColumn(tableWithManyColumns);;
The pattern [(b,c)] matches with a singleton list of pairs. So it will match with [("hello", "world)] but will not match with [("hello", "world"); ("another", "pair")], or [] or any list which length is not equal to one. If you want to match with any list with length more than one, you need to use the first :: rest pattern, where first will match with the first element of the list, and rest with the rest part of the list (everything beyond the first element).
The following function will extract the name of the first column,
type column = string * string list (* name, values *)
type base = string * column list (* tableName, columns *)
let firstColumnName : base -> string = fun table -> match table with
| (_tableName, (columnName,_values) :: _otherColumns) -> columnName
| _ -> failwith "wrong table representation"
Example,
# firstColumnName tableWithOneColumn;;
- : string = "RegNumber"
# firstColumnName tableWithManyColumns;;
- : string = "RegNumber"

How do I filter a list in Haskell based on a particular elements value?

I have a list of type Film, where Film(Director Title Year Likes Dislikes), which corresponds to Film -> String -> String -> Int -> [String] -> [String].
I also have a function that takes in the likes and dislikes lists and returns a percentage rating, as such that:
rating :: Likes -> Dislikes -> Int
rating likes dislikes = (((length [likes]) / ((length [dislikes]) + (length [likes]))) * 100)
My problem:
I cannot work out how to use filter to sort this list, to determine whether or not each film's rating is 75% or over.
This is my current attempt:
filterFilm :: Film -> Bool
filterFilm (Film t d y likes dislikes)
| (rating likes dislikes) > 74 = True
| otherwise = False
-- List film rating with and over 75%
listRating :: Database -> Database
listRating (x:xs) = filter (filterFilm x) (x:xs)
I get this error:
Cw 2018.hs:87:29: error:
• Couldn't match expected type ‘Film -> Bool’
with actual type ‘Bool’
• Possible cause: ‘filterFilm’ is applied to too many arguments
In the first argument of ‘filter’, namely ‘(filterFilm x)’
In the expression: filter (filterFilm x) (x : xs)
In an equation for ‘listRating’:
listRating (x : xs) = filter (filterFilm x) (x : xs)
|
87 | listRating (x:xs) = filter (filterFilm x) (x:xs) |
Any suggestions? Thanks in advance!
I think you use filter the wrong way. filter is a function that takes two parameters, the first one a predicate a -> Bool and the second one a list of as.
Now the parameter that is passed to listRating is that list, so listRating l, and you thus call the filter with filter filterFilm l, so:
listRating :: Database -> Database
listRating l = filter filterFilm l
We can also remove the l parameter both in the head and body of the function, like:
listRating :: Database -> Database
listRating = filter filterFilm
Note that you can simplify your filterFilm function to:
filterFilm :: Film -> Bool
filterFilm (Film t d y likes dislikes) = rating likes dislikes > 74

Haskell create a Phonebook as a list of tuples

I need to create a phonebook as a list of tuples:
type Phonebook :: [(String,String)]
As you can see in the code, the first element should represent the name and the second element the number. So the result of
Main> phonebookone = insert "Dad" "90213" (insert "mum" "8912" emptyPhonebook)
Main> phonebookone
should be [("Dad","90213"),("mum","8912")] but I only get [("Dad","90213")]
My code:
emptyPhonebook :: Phonebook
emptyPhonebook = [("","")]
insert :: String -> String -> Phonebook -> Phonebook
insert name number phonebook = [(name,number)]
Also i need to create a function search, which search for numbers or better if the first element of a tuple exists in the list and is the same like the String, who you search for, it will result the second element of the tuple.
So it should be:
Main> search "Dad" phonebook
"90213"
My unfinished code for this:
search :: String -> Phonebook -> String
search name phonebook = if ..???
A lot of things are wrong with your code:
Your emptyPhonebook actually is not empty: indeed you have defined one tuple in it: the one with an empty string as both the name and the phone number. You can correct this to:
emptyPhonebook :: Phonebook
emptyPhonebook = []
Your insertion method insert actually creates a Phonebook with one entry: the one you want to add, the remainder of the phonebook is ignored. You can use the CONS ((:)) function for this:
insert :: String -> String -> Phonebook -> Phonebook
insert name number = (:) (name,number)
Now to answer the main question. First of all, most Haskell programmers consider if to be rather un-Haskell: in Haskell one uses pattern matching and guards to set constraints on rules.
Since your search is probably supposed to error when it does not find a person with that name (you did not specify this), there are actually two code paths we have to consider:
the one where we are given a non-empty list where the first element matches our search: in that case we return the corresponding number; and
the one where the name does not match in which case we simply continue our search in th tail of the given list.
These rules in Haskell look like:
search query ((name,number):other) = ...
Now in case query and name match, we thus should return the number so:
search query ((name,number):other) | query == name = number
In the other case, we recursively continue our noble quest:
search query ((name,number):other) | otherwise = search query other
So putting it together, you get:
search :: String -> Phonebook -> String
search query ((name,number):other) | query == name = number
| otherwise = search query other
This will return the number given it is in the phonebook, and otherwise error.
EDIT:
Given you want to return "error" (as a string) when the search fails, you only have to add an additional rule:
search _ [] = "error"
So putting it all together gives:
search :: String -> Phonebook -> String
search query ((name,number):other) | query == name = number
| otherwise = search query other
search _ [] = "error"
As already mentioned your insert function is incomplete, otherwise no matter what you try to insert to it it'll only have 1 record, so if you try to search for a specific contact in your phonebook it'll only retrieve the only 1. But I won't answer that as that has already been answered.
Then just apply simple recursion over your phonebook
search :: String -> Phonebook -> String
search name ((contactName, contactNumber):phonebook) = if contactName == name then contactNumber else search name phonebook
or you can use filter
search :: String -> Phonebook -> String
search name phonebook = snd . head . filter ((==name) . fst) $ phonebook
The only problem with the second option that it will retrieve the first contact with the same name and it might not be the one we're querying for, a solution to this would be to use Either String phonebook as the return type if there is more than one individual with the same name, so this:
search :: String -> Phonebook -> Either String phonebook
search name phonebook = let getContacts nm contacts = filter ((==nm) . fst) $ contacts
in case (length $ getContacts name phonebook) == 1 of
True -> Left $ snd . head . getContacts name $ phonebook
False -> Right $ (getContacts name) $ phonebook
Ignore the last one, is untested and I don't think is exactly what you want, I just added this just in case as extra.

How to edit an Item in a mutable list in f# and allow the other items in the list retain their values?

I created a list in f# named tickets that contains 10 records called Ticket.The records are all initialized with their specific seat number and empty customer name.
type Ticket = {seat:int; customer:string}
let mutable tickets = [for n in 1..10 -> {Ticket.seat = n; Ticket.customer = ""}]
I want to write a function to book a specific seat in the list(add a customer name to the seat).
How can i edit an item in the list and have other items still retain their value
The functional F# list type is immutable, which means that you cannot change it. The typical way of working with it would be to return a new, modified, copy.
To do that, you can write a function bookSeat that takes list<Ticket> together with number & new name and produces a new list<Ticket> with that one field updated:
let bookSeat seatNo name tickets =
tickets |> List.map (fun ticket ->
if ticket.seat = seatNo then { ticket with customer = name }
else ticket )
bookSeat 3 "Tomas" tickets
Here is a way to use a list of mutable (ref-cells) tickets:
let maxSeats = 10
let tickets : (int * Ticket option) ref list =
[1..maxSeats]
|> List.map (fun s -> ref (s, None) )
let bookSeat seat name =
match List.tryFind (fun r -> let (s,_) = !r in s = seat) tickets with
| Some r ->
r :=
match !r with
| (s, Some ticket) -> (s, Some { ticket with customer = name })
| (s, None) -> (s, Some { seat = s; customer = name })
| None ->
failwith "seat not found"
obvious you can make tickets itself mutable too, if you want to add seats instead of initializing them with all the obvious seats
a better approach(?)
Still I think that this is the wrong way to do it - I think you want a Map:
type Ticket = {seat:int; customer:string}
type Tickets = Map<int, Ticket>
let bookSeat seat name (tickets : Tickets) =
match Map.tryFind seat tickets with
| Some oldTicket ->
tickets
|> Map.remove seat
|> Map.add seat { oldTicket with customer = name }
| None ->
tickets
|> Map.add seat { seat = seat; customer = name }
note that these are all immutable values, so bookSeat will return a new Ticket-reservation-map
hyprid using Dictionary
or you can use a common .net Dictionary and mutate this:
type Ticket = {seat:int; customer:string}
let tickets = System.Collections.Generic.Dictionary<int, Ticket>()
let bookSeat seat name =
match tickets.TryGetValue seat with
| (true, oldTicket) ->
tickets.[seat] <- { oldTicket with customer = name }
| (false, _) ->
tickets.[seat] <- { seat = seat; customer = name }
Here you don't have to pass the Tickets around - they will be mutated in place (but still the Ticket-objects themselves are still immutable)
Note that this right now is not thread-safe so be careful.
I think the most idiomatic option here would be to simply use a string array. Given you know in advance the size and you want to be able to update it, this is the structure that fills both those needs most idiomatically. So,
// create 10-element string array initialized with null
let (tickets : string array) = Array.zeroCreate 10
...
tickets.[3] <- "New Customer"
keep it simple.
Granted this is not "purely-functional" (but any purely functional solutions here just kick the non-pure parts down the road anyway), but if the goal is just to get the job done, this will do it.

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.