660 likes | 1.11k Views
Tuples and Lists. Lecture 3, Programmeringsteknik del A. Tuples. A tuple is a value made up of several other values, its components . Examples : A point in the plane (x,y) A point in space (x,y,z) A purchase (”Dagens Lunch”, 55) The null tuple (). Round brackets and commas.
E N D
Tuples and Lists Lecture 3, Programmeringsteknik del A.
Tuples • A tuple is a value made up of several other values, its components. • Examples: • A point in the plane (x,y) • A point in space (x,y,z) • A purchase (”Dagens Lunch”, 55) • The null tuple () Round brackets and commas.
Tuple Types A tuple type specifies the type of each component. Examples: (1,2) :: (Int, Int) (1.5, 2.25) :: (Float, Float) (”Dagens Lunch”, 55) :: (String, Int) () :: ()
Type Definitions We can give names to types with a type definition: type Purchase = (String, Int) Now Purchase and (String, Int) are interchangeable: dagens :: Purchase dagens = (”Dagens Lunch”, 55) All types start with a capital letter.
Pattern Matching Functions on tuples can be defined by pattern matching: name :: Purchase -> String name (s, i) = s price :: Purchase -> Int price (s, i) = i A pattern which must match the actual argument.
Standard Selector Functions fst (x, y) = x snd (x, y) = y But we cannot define: select 1 (x, y) = x select 2 (x, y) = y The type of a function’s result is not allowed to depend on the values of the arguments. What would the type of the result be?
Lists A list is also a value made up of other values, its elements. Examples: [1, 2, 3] :: [Int] [True, False, True] :: [Bool] [] :: [Float] [(”Dagens Lunch”, 55)] :: [Purchase]
Tuples vs. Lists A tuple consists of a fixed number of components, of various types. Useful, but not much to know. A list consists of any number of elements, all of the same type. Ubiquitous! Supported by a rich set of standard functions and constructions.
Lists Can Make Programs Simpler! Whenever there is a sequence of values, consider using a list.
Lists for Counting We often need to count: [1..10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1,3..10] = [1, 3, 5, 7, 9] Example: fac :: Int -> Int fac n = product [1..n] Standard function to multiply together list elements.
List Comprehensions We often do the same thing to every element of a list: Example: doubles :: [Int] -> [Int] doubles xs = [2 * x | x <- xs] doubles [1..3] [2, 4, 6] For each element x of xs... Add 2*x to the list produced.
Summing Squares with Lists n [1, 2, 3, …, n] [1, 4, 9, …, n^2] 1 + 4 + 9 + … + n^2 sumsq :: Int -> Int sumsq n = sum [i^2 | i <- [1..n]] Add up the elements of a list.
Filtering Lists A list comprehension can include a condition which elements must satisfy. Example: factors :: Int -> [Int] factors n = [i | i <- [2..n], n `mod` i == 0] factors 12 [2, 3, 4, 6, 12] Include only those i which pass this test.
Using factors Counting the number of factors… numFactors :: Int -> Int numFactors n = length (factors n) Testing for a prime number… isPrime :: Int -> Bool isPrime n = factors n == [n] The number of elements in a list.
Designing List Comprehensions Ask yourself: Do I want to process a sequence of elements in turn? If so, use a list comprehension! [ | <- ] Start by writing down a framework with gaps to be filled in.
Designing List Comprehensions Ask yourself: What is the list whose elements I want to process? [ | <- [2..n] ] Write it in the gap after the left arrow.
Designing List Comprehensions Ask yourself: What will I call each element? [ | i <- [2..n] ] Fill the name in to the left of the arrow.
Designing List Comprehensions Ask yourself: Do I want to process every element, or make a selection from them? [ | i <- [2..n], n `mod` i == 0] Write a comma and a condition at the end of the comprehension.
Designing List Comprehensions Ask yourself: What values do I want in the result? [ i | i <- [2..n], n `mod` i == 0] Write an expression before the bar.
Lists vs. Recursion? Even problems which do not mention lists can often be solved easily by using lists internally. Haskell’s powerful list constructions often offer a simpler alternative to using recursion. But not always! Recursion is a powerful and general tool -- therefore harder to use than special purpose tools, when they are applicable.
Example: A Library Database Computerise the librarian’s card file: John Hughes borrowed The Craft of Functional Programming Simon Thompson borrowed Science Goes Boink!
Representing a Loan Card What is the information on a loan card? John Hughes borrowed The Craft of Functional Programming Borrower’s name. Book title. type Borrower = String type Book = String type Card = (Borrower, Book)
Representing the Card File What is the contents of the card file? -- a sequence of loan cards! type Database = [Card] example :: Database example = [ (”John Hughes”, ”The Craft of Functional Programming”), (”Simon Thompson”, ”Science Goes Boink!”) ]
Querying the Database What information would we wish to extract? books :: Database -> Borrower -> [Book] borrowers :: Database -> Book -> [Borrower] borrowed :: Database -> Book -> Bool numBorrowed :: Database -> Borrower -> Int
Which Books Have I Borrowed? books example ”John Hughes” = [”The Craft of Functional Programming”] books example ”Mary Sheeran” = [] books db person = [book | (borrower, book) <- db, borrower == person] No books borrowed. Pattern matching again.
Quiz Define: numBorrowed :: Database -> Borrower -> Int
Quiz Define: numBorrowed :: Database -> Borrower -> Int numBorrowed db person = length (books db person)
Updating the Database Making and returning loans changes the contents of the database. makeLoan :: Database -> Borrower -> Book -> Database returnLoan :: Database -> Book -> Database … to compute the information in the database afterwards. These functions use the information in the database beforehand...
Making a Loan makeLoan :: Database -> Borrower -> Book -> Database makeLoan db borrower book = [(borrower, book)] ++ db Make a new card.
Returning a Loan returnLoan :: Database -> Book -> Database returnLoan db book = [(borrower, book’) | (borrower, book’) <- db, book’ /= book]
The Role of Lists Lists offer a natural way to model real world information -- sequences are everywhere! Lists are easy to manipulate thanks to Haskell’s support: a good first choice of data structure. Lists are not always efficient: candidates for replacement by faster structures later in program development.
Some Standard List Functions Haskell provides many standard functions to manipulate lists. Let’s look at a few. But first: What is the type of length?
Polymorphic Types length has many types! It is polymorphic. That is why it is useful. length :: [Int] -> Int length :: [Float] -> Int length :: [Card] -> Int For any type t, length :: [t] -> Int A type variable, which may stand for any type.
Taking and Dropping • take n xs the first n elements of xs, • drop n xs all but the first n elements. • Examples: • take 3 (factors 12) [2, 3, 4] • drop 3 (factors 12) [6, 12]
Quiz take and drop have the same (polymorphic) type. What is it?
Quiz take and drop have the same (polymorphic) type. What is it? take, drop :: Int -> [a] -> [a] Examples: Int -> [Float] -> [Float] Int -> [String] -> [String] Not Int -> [Float] -> [String] A must stand for the same type everywhere.
The Zipper Often we want to combine corresponding elements of two lists: zip [ ”John” , ”Simon”, ”Mary” ] [ ”Welsh”, ”English”, ”Irish” ] [ (”John”,”Welsh”), (”Simon”,”English”), (”Mary”,”Irish”)]
The type of zip zip :: [a] -> [b] -> [(a, b)] Example: zip [1, 2, 3] [”a”, ”b”, ”c”] [(1,”a”), (2,”b”), (3,”c”)] Here zip :: [Int] -> [String] -> [(Int, String)]
Example: The Position of x in xs position :: a -> [a] -> Int Example: position ”b” [”a”, ”b”, ”c”] 2 Idea: Mark every element with its position, and search for the one we want.
Marking Elements with Positions [”a”, ”b”, ”c”] [(”a”,1), (”b”,2), (”c”,3)] zip [”a”, ”b”, ”c”] [ 1, 2, 3 ] [(”a”,1), (”b”,2), (”c”,3)] Use zip xs [1..length xs]
Searching for the Right Element Select the positions where the element we’re searching for appears: positions x xs = [pos | (x’,pos) <- zip xs [1..length xs], x’ == x] positions ”b” [”a”, ”b”, ”c”] [2] A list. We want just a number.
The Complete Position Function position :: a -> [a] -> Int position x xs = head [pos | (x’,pos) <- zip xs [1..length xs], x’ == x] Select the first element from the list of positions.
Example: Calculate Path Lengths How long is the path?
Representing a Point type Point = (Float, Float) distance :: Point -> Point -> Float distance (x, y) (x’, y’) = sqrt ((x-x’)^2 + (y-y’)^2) x- and y-coordinates.
Representing a Path Q S R P type Path = [Point] examplePath = [p, q, r, s] path length = distance p q + distance q r + distance r s
Two Useful Functions • init xs -- all but the last element of xs, • tail xs -- all but the first element of xs. • init [p, q, r, s] [p, q, r] • tail [p, q, r, s] [q, r, s] • zip … [(p,q), (q,r), (r,s)]
The pathLength Function pathLength :: Path -> Float pathLength xs = sum [distance p q | (p,q) <- zip (init xs) (tail xs)] Example: pathLength [p, q, r, s] distance p q + distance q r + distance r s
Rule of Thumb Question: Do I want to go through the elements of two lists together? Answer: Use zip!
Lessons Lists model any kind of sequence in the real world. Lists can express many kinds of repeated computation in programs. Special constructions and a rich set of polymorphic standard functions let us manipulate lists very easily indeed. Look for opportunities to use them!
Reading This lecture is largely based on Chapter 5 of the text book, up to section 5.7. Read this, and try exercises 5.11, 5.12, 5.13, and 5.14. Section 5.8 summarises a number of standard functions on lists. Knowing these functions will make list programming much easier for you.