Reputation: 25
I am new to Haskell, and I have created a function to allow a "user" to add a new film to a database (a text file called Films.txt).
main = do
putStrLn "Insert film title:"
film <- getLine
putStrLn ("Who directed " ++ film ++ "?")
director <- getLine
putStrLn ("What year was " ++ film ++ " released?")
year <- getLine
appendFile "Films.txt" $ (film ++ "\n")
appendFile "Films.txt" $ (director ++ "\n")
appendFile "Films.txt" $ (year ++ "\n")
appendFile "Films.txt" $ (" " ++ "\n")
An example of the text file created is:
Blade Runner
Ridley Scott
1982
The Fly
David Cronenberg
1986
etc...
How would I then search this file, line by line, if I wanted to return only films by a certain director. In another language I would use a FOR loop to search the .txt file line by line until a line matched the search term e.g. "Ridley Scott". Then I would return to the line above the matched line, output it (film name) and continue the search until the .txt file was finished.
However in Haskell I am having trouble turning this thought process into code, mainly because I can't find a way search the file line by line.
Thanks
Upvotes: 1
Views: 659
Reputation: 11940
Using your own approach, scan the file looking for a name:
main = do
putStrLn "Enter Director's name"
name <- getLine
base <- readFile "Films.txt" -- base is the whole file contents as a single string
print $moviesBy name $lines base
moviesBy :: String -> [String] -> [[String]]
moviesBy name (title:director:year:_:others) | director == name = [title, director, year]:moviesBy name others
| otherwise = moviesBy name others -- a different director, scan the rest of the file
moviesBy _ _ = [] -- when there's no more records
In more detail...
lines base
splits file contents in lines (newlines removed) producing a list of strings.
moviesBy
accepts a string as a search pattern, and a list of strings which is your file's contents. From how this file was created, we expect to find director's name at each second line out of every four. Which is done as the first pattern-match:
moviesBy name (title:director:year:_:others)
Here, (title:director:year:_:others)
matches a list of at least four elements, binding them to relevant variables (_
in the fourth position is a wildcard pattern that is here matched against the whitespace string which is of no importance for us). others
, as the rightmost operand to :
, thus matches the remaining part of the list (4th tail, in Lisp parlance). After the pipe |
we add an extra constraint on the match, that the second element from the quadruplet should equal to the director's name of concern. If true, a list of [title, director, year]
is inserted in the output (being the left operand to :
on the RHS of =
, the right-hand part of a production rule) and the remaining part of the list (other
) is examined via the recursive call (the right operand to that RHS :
); otherwise
this quadruplet is skipped and simply the remaining part is considered.
The match in the last line deals with any list that is less than four elements (which is probably the end of file). Since we're unlikely to find any more director's movies in such a short list, we simply return []
.
So, for instance, if we have a list
["Blade Runner", "Ridley Scott", "1982", " ", "Alien", "Ridley Scott", "1979", " "]
then looking for Scott's films we obtain:
At first, list is matched against (title:director:year:_:others)
. Then variables are bound: title
is "Blade Runner", director
is "Riddley Scott", year
is "1982" and others
is ["Alien", "Ridley Scott", "1979", " "]
. Since director
equals the name we're looking for, we take the first path and proclaim ["Blade Runner", "Ridley Scott", "1982"]:moviesBy "Ridley Scott" ["Alien", "Ridley Scott", "1979", " "]
to be our result.
Next, a recursive call follows to construct the tail of resulting list. Here again, title
is "Alien", director
"Ridley Scott", year
is "1979" and others
is bound to an empty list. So, the tail of the resulting list from step 1. is ["Alien", "Ridley Scott", "1979"]:moviesBy "Ridley Scott" []
.
The last recursive call. We cannot bind the at-least-four elements pattern to the empty list, so we take the last alternative, moviesBy _ _
that matches any combination of a string and a list, and here the result is [] (the recursion stops). So, the result of step 2. is ["Alien", "Ridley Scott", "1979"]:[]
, in other words, [["Alien", "Ridley Scott", "1979"]]
. and with head prepended in 1., the total result of the function is [["Blade Runner", "Ridley Scott", "1982"], ["Alien", "Ridley Scott", "1979"]]
.
Upvotes: 6