CS 119 Lab 6Lists

Objectives

  • Use and manipulate lists
  • Perform the following tasks in the order given.

    1. Download the Lab6 project and import it into Eclipse as a Haskell project.
       
    2. Test out the example functions from the notes and make sure you understand how they work.  You can always use the substitution model to trace through some computations.
       
    3. Now we are going to write a Movie Query System that could be used at a video store.  Suppose that for every movie, we have a collection of information like the title, director, actors, and the year it was made.  We will store all these movie records in a list.  To access the information we will allow users to be able to ask questions (in regular English sentences!) to the system and it will look up the answer.  Such a feature is called a natural language query system.

      You will write a portion of this system.  The database will be a list of movie records.  We will need a way of searching through the list in various ways.  We will also need a way of programming the search using patterns on the user's queries.

      Let's start by building the database.  We will define our types for building a movie record:

      type Title = String
      type Director = String
      type Year = Int
      type Actor = String
      type Actors = [Actor]

      type Movie = (Title, Director, Year, Actors)

      Note that we have a list of actors and that the movie record is a tuple containing the information in a given order.  Our movie database will simply be a list of type Movie.  Here is a database with just two movie records:

      movieDB :: [Movie]

      movieDB = [ ("Amarcord",
                              "Federico Fellini",
                              1974,
                              ["Magali Noel", "Bruno Zanin", "Pupella Maggio", "Armando Drancia"]),
                          ("The Godfather",
                              "Francis Ford Coppola",
                              1972,
                              ["Marlon Brando", "Al Pacino","James Caan", "Robert Duvall","Diane Keaton"])
                          ]

      We will also need selector functions:

      movieTitle :: Movie -> Title
      movieTitle (t,_,_,_) = t

      movieDirector :: Movie -> Director
      movieDirector (_,d,_,_) = d

      movieYearMade :: Movie -> Year
      movieYearMade (_,_,y,_) = y

      movieActors :: Movie -> Actors
      movieActors (_,_,_,a) = a


       

    4. We can use the filter higher order function to search for movies with certain properties.  For example:

      > filter f movieDB where f movie = (movieYearMade movie) == 1974


      This will only keep those movies that were made in 1974.  Try it out.

      Assignment:
      Write  functions which will search for movies in a given year, by a given director, or containing a given actor:
              moviesMadeInYear :: Year -> [Movie] -> [Movie]
              moviesDirectedBy
      :: Director -> [Movie] -> [Movie]
              moviesWithActor :: Actor -> [Movie] -> [Movie]

      For moviesWithActor,  you will want to check if a certain String is contained in the list of actors.  The function elem :: a -> [a] -> Bool will do this.

      You can test these out by

                > moviesMadeInYear 1974 movieDB

                 > moviesDirectedBy "Francis Ford Coppola" movieDB

                > moviesWithActor "Al Pacino" movieDB


       

    5. The functions you just wrote return a list of movie records.  We would prefer to get a list of just the titles.

       
      Assignment:
      Write  function titlesOfMoviesSatisfying :: (Movie->Bool) -> [Movie] -> [Title]
      which returns the list of titles satisfying the given predicate.

      For example

      > titlesOfMoviesSatisfying f movieDB where f movie = (movieYearMade movie) == 1974

      Hint:  Use the higher order function map.

       

    6. We might want some attribute other than the title when we are searching. 
       
      Assignment:
      Write  function moviesSatisfying :: (Movie->Bool) ->(Movie->a) -> [Movie] -> [a]
      which uses a selector to determine which attribute to display.

      For example

      > moviesSatisfying f movieTitle movieDB where f movie = (movieYearMade movie) == 1974

       


       

    7. Now that we have a way to search through the database, we need to work on the natural language interface.  We will start with a function called queryLoop which repeatedly reads and responds to the user's questions.  To accomplish this, we will create a list of pattern/action pairs in which the pattern describes the type of question the user is asking (like "Who is the director of ..." or "What movies were made between _ and _") and the action will be a function for performing the search.  I have written the queryLoop function for you already.  It uses some code that we have not learned yet (but will!).  Basically it inputs a line of text repeatedly and calls the function answerByPattern to process the user's query.

      All the real work is handled by answerByPattern.  This function takes as arguments the string that the user entered as well as the list of pattern/action pairs.   This function will try to match the query with each of the patterns in the list of pattern/action pairs.  If it finds a match,  it then executes the action associated with that pattern, using the words in the query that fill in the blanks in the pattern as arguments.   For example, if the pattern is "Who is the director of ..." and the query is "Who is the director of The Godfather", then the pattern and query match and the associated action function will be executed with the string "The Godfather" sent in as an argument.  This function has also been provided but you will need to supply the matches and substInMatch functions which we will describe shortly.

      Let's look at the pattern/action pairs.  The patterns will contain wild cards which will stand for parts of the pattern which will be matched in the query.  The wild card "..." will stand for a blank that will be filled in by one or more words matching the rest of the query.   The pattern will therefore be a string which contains wild cards.  The action will be a function using the moviesSatisfying function that you have written. 

      In Haskell, we can define an un-named or anonymous function by using something like \x->x+1.  The \x indicates that the function takes one argument, x, and then the body of the function appears after the ->.  So

                moviesSatisfying f movieTitle movieDB where f movie = (movieYearMade movie) == 1974

      could be rewritten as

             
      moviesSatisfying (\m->(movieYearMade m) == 1974)  movieTitle movieDB

      where we substituted an anonymous function in place of function f.  We will use anonymous functions to define the actions. These functions will be of type [String] -> [String].  The functions take a list of strings which are the parts of the query matching the wildcards, and return a list of strings giving the possible multiple answers.  The type of the pattern/action pair will therefore be (String, [String] -> [String]).

      Here is an example of a pattern/action pair for looking for the director of a movie with a given title:

             
      ("Who is the director of ...", (\[title]-> moviesSatisfying (\m-> title == (movieTitle m))
                                                                                                                    movieDirector
                                                                                                                    movieDB))

      So if we match that pattern, we have a title matching the "..." wild card.  This title is the argument to an anonymous function which performs the moviesSatisfying search using the filter (\m-> title == (movieTitle m)), to find all movies, m, with that given title, and then maps the function movieDirector to extract the director's name from that movie.

      To make the answerByPattern function work we need to write matches and substInMatch.  The function matches is of type [String]->[String]->Bool.  It takes a list of words making up the pattern and a list of words making up the query and returns whether or not the query matches the pattern.  Clearly a word in the pattern will have to exactly match the corresponding word in the query.  The wild card "..." will match all the remaining words in the query.  Here is the code for matches:

      matches :: [String] -> [String] -> Bool
      matches [][] = True
      matches _ [] = False
      matches ("..." : ps) q = True
      matches (p : ps) (q : qs) = p == q && matches ps qs

      The function substInMatch is similar in that it takes two lists of the pattern and query.  However it is called only when we already know that the two lists match.  It returns a list of the substitutions for the wild cards in the pattern that will make it match the query.  For example:

      > substInMatch ["foo", "..."] ["foo","bar","baz"]

      [["bar","baz"]]


       
      Assignment:
      Write  function substInMatch :: [String]->[String]->[[String]]

      Be sure to return a list containing the list of symbols matched by the "..." wild card. 

      Note:  We will later add additional wild cards to our patterns so that we may have multiple items in the list later on.

       

    8. We should now be able to test out the query system with our one pattern in the patternActionList.  It should look something like this:

      > queryLoop

      Who is the director of The Godfather

      Francis Ford Coppola


      Try it out.
       

      Assignment:
      Add another pattern of the form "Who acted in ..." to the patternActionList with its corresponding action.

      Hint:  Since the movieActors selector will return [String] rather than String you will need to use the function listToString to convert this to a single String.  Therefore use listToString.movieActors as the selector.  The "." here is function composition.  It performs the movieActors function and then performs listToString on the result.

       

    9. Now that our program can recognize simple patterns, we can start adding more complicated ones.  The next pattern is typified by the following sentences:

      What movies were made in 1974
      What movie was made in 1972


      The pattern can be written as

      "What ( movie movies ) ( was were ) made in _ "

      We have extended our pattern language in two ways.  The "_" wild card matches exactly one word and it need not occur at the end of the pattern as the "..." does.  The "(  )" wild card gives a list of words that may match.  So ( movie movies ) can match either the word "movie" or the word "movies".

      Here is matches extended for the list wild card:

      matches :: [String] -> [String] -> Bool
      matches [] [] = True
      matches _ [] = False
      matches ("..." : ps) q = True
      matches ("(" : ps) (q : qs) = elem q (makeList ps) && matches (restPattern ps) qs
      matches (p : ps) (q : qs) = p == q && matches ps qs

      The pattern, matches ("(" : ps) (q : qs), sees that we are starting a list.  We use the function makeList to get the list of items up to the closing ")" and check to see if the first word in the query is contained in that list.  The function restPattern returns the rest of the pattern after the ")" so that we can continue checking for a match after the list.
       

      Assignment:
      Extend matches to check for the "_" wild card.  Remember that this matches a single word. Also there can be more than one "_" in a pattern and it need not be at the end of the pattern.

       


    10.  
      Assignment:
      Extend substInMatch to account for both of these new wild cards.  It should return a list of substitutions, one for each wild card.

       

    11. We can test our extensions with the new pattern/action pair:

      ("What ( movie movies ) ( was were ) made in _",
      (\[noun,verb,year]-> moviesSatisfying (\m-> (stringToNum year) == (movieYearMade m))
                                                                           movieTitle
                                                                           movieDB))

      Note that the action functions takes a list with three values for the three substitutions into the wild cards.  It only uses the last substitution value, however.  The function stringToNum converts the year from a String to and Int.

      Assignment:
      Add a pattern/action pair for the pattern:

      "What ( movie movies ) ( was were ) made between _ and _"


       


    12.  
      Assignment:
      Add a pattern/action pair for the pattern:

      "What ( movie movies ) ( was were ) made ( before after since ) _"

       

    13. Email your modified zipped project to me for grading.