Homework 1
Haskell warmup
This homework is written in literate Haskell; you can download the raw source to fill in yourself. You’re welcome to submit literate Haskell yourself, or to start fresh in a new file, literate or not.
Please submit homeworks via the new submission page.
Let’s learn some Haskell! We’ll be going over some rudiments in class, and there’s excellent documentation online.
In most places where I’d like you to fill in a definition, I’ve used the convenient Haskell term undefined, which let’s you compile an incomplete program. (Running undefined parts of your program is an error, and your program will crash.)
Please leave the following line in. (If you take it out, the grader will reject your program.) We’ll talk more about Haskell’s module system later in the semester.
module Hw01 whereYou can test this program by running ghci on it. If you edit your code, you can use the :reload command to load in your new definitions.
If your program has type errors, it won’t compile. If you change the types of any functions, it won’t compile with my tester. If you take things out, like type definitions, your program won’t compile. If your submitted program doesn’t compile, you will get no points. If you’re unsure, ask!
The following imports are needed for Problem 9.
import qualified Data.Map as Map
import Data.Map (Map, (!))
import qualified Data.Set as Set
import Data.Set (Set)Problem 1: natural recursion
Please don’t use any Prelude functions to implement these—just write natural recursion, like we did in class.
Write a function called sumUp that sums a list of numbers.
sumUp :: [Int] -> Int
sumUp [] = undefined
sumUp (x:xs) = undefinedWrite a function called evens that selects out the even numbers from a list. For example, evens [1,2,3,4,5] should yield [2,4]. You can use the library function even.
evens :: [Int] -> [Int]
evens [] = undefined
evens (x:xs) = undefinedWrite a function called incAll that increments a list of numbers by one. You’ll have to fill in the arguments and write the cases yourself.
incAll :: [Int] -> [Int]
incAll = undefinedNow write a function called incBy that takes a number and increments a list of numbers by that number.
incBy :: Int -> [Int] -> [Int]
incBy = undefinedWrite a function append that takes two lists and appends them. For example, append [1,2] [3,4] == [1,2,3,4]. (This function is called (++) in the standard library… but don’t use that to define your version!)
append :: [Int] -> [Int] -> [Int]
append = undefinedProblem 2: data types
Haskell (and functional programming in general) is centered around datatype definitions. Here’s a definition for a simple tree:
data IntTree = Empty | Node IntTree Int IntTree deriving (Eq,Show)Write a function isLeaf that determines whether a given node is a leaf, i.e., both its children are Empty.
isLeaf :: IntTree -> Bool
isLeaf Empty = undefined
isLeaf (Node l x r) = undefinedWrite a function sumTree that sums up all of the values in an IntTree.
sumTree :: IntTree -> Int
sumTree = undefinedWrite a function fringe that yields the fringe of the tree from left to right, i.e., the list of values in the leaves of the tree, reading left to right.
For example, the fringe of Node (Node Empty 1 (Node Empty 2 Empty)) 5 (Node (Node Empty 7 Empty) 10 Empty) is [2,7].
fringe :: IntTree -> [Int]
fringe = undefinedProblem 3: insertion sort
Write a function insertionSort that takes a list of Ints and produces one in sorted order. Use the insertion sort algorithm. You might want to write a helper function.
insertionSort :: [Int] -> [Int]
insertionSort = undefinedProblem 4: binary search trees
Write a function isBST to determine whether or not a given tree is a strict binary search tree, i.e., the tree is either empty, or it is node such that:
- all values in the left branch are less than the value of the node, and
- all values in the right branch are greater than the value of the node,
- both children are BSTs.
I’ve given you a helper function maybeBounded that checks whether a given Int is bounded. It uses the Haskell Maybe type, which is essentially defined as:
data Maybe Int = Nothing | Just IntMaybe makes a type nullable. In Java, every non-primitive type is nullable—the null object can have any class. In Haskell, you must explicitly ask for nullability, and nullness and non-nullness are both explicit: Nothing is null, and the non-null Just x holds a value x. We’ll look at this more deeply in the next assignment, when we talk about datatypes.
maybeBounded :: Maybe Int -> Maybe Int -> Int -> Bool
maybeBounded Nothing Nothing x = True
maybeBounded Nothing (Just upper) x = x < upper
maybeBounded (Just lower) Nothing x = lower < x
maybeBounded (Just lower) (Just upper) x = lower < x && x < upperisBST :: IntTree -> Bool
isBST = undefinedWrite a function insertBST that performs BST insert. You may assume your input is a BST.
insertBST :: Int -> IntTree -> IntTree
insertBST = undefinedWrite a function deleteBST that removes a given value from a BST. You may assume your input is a BST. Feel free to look up the algorithm… I had to!
It doesn’t really matter which algorithm you use, so long as the function works correctly, i.e., for all BSTs t:
deleteBST x tis a BST,deleteBST x truns in O(log n) time in expectation,xdoesn’t appear indeleteBST x t,- for all
yint, ify /= x, thenyappears indeleteBST y t.
You are, as always, free to introduce any helper functions you might need.
deleteBST :: Int -> IntTree -> IntTree
deleteBST = undefinedProblem 5: maps and folds
We’re going to define each of the functions we defined in Problem 1, but we’re going to do it using higher-order functions that are built into the Prelude. In particular, we’re going to use map, filter, and the two folds, foldr and foldl. To avoid name conflicts, we’ll name all of the new versions with a prime, '.
Define a function sumUp' that sums up a list of numbers.
sumUp' :: [Int] -> Int
sumUp' l = undefinedDefine a function evens' that selects out the even numbers from a list.
evens' :: [Int] -> [Int]
evens' l = undefinedDefine a function incAll' that increments a list of numbers by one.
incAll' :: [Int] -> [Int]
incAll' l = undefinedDefine a function incBy' that takes a number and then increments a list of numbers by that number.
incBy' :: Int -> [Int] -> [Int]
incBy' n l = undefinedDefine a function rev' that reverses a list. Don’t use anything but a folding function (your choice), the list constructors, and lambdas/higher-order functions.
rev' :: [Int] -> [Int]
rev' l = undefinedDefine two versions of the function append' that appends two lists. One, appendr, should use foldr; the other, appendl, should use foldl. You can use the list constructors, higher-order functions, and rev'.
appendr :: [Int] -> [Int] -> [Int]
appendr l1 l2 = undefined
appendl :: [Int] -> [Int] -> [Int]
appendl l1 l2 = undefinedProblem 6: defining higher-order functions
We’re going to define several versions of the map and filter functions manually, using only natural recursion and folds—no using the Prelude or list comprehensions. Note that I’ve written the polymorphic types for you.
Define map1 using natural recursion.
map1 :: (a -> b) -> [a] -> [b]
map1 = undefinedDefine map2 using a folding function.
map2 :: (a -> b) -> [a] -> [b]
map2 f l = undefinedDefine filter1 using natural recursion.
filter1 :: (a -> Bool) -> [a] -> [a]
filter1 = undefinedDefine filter2 using a folding function.
filter2 :: (a -> Bool) -> [a] -> [a]
filter2 p l = undefinedProblem 7: polymorphic datatypes
We’ve already briefly seen the Maybe type in the first homework. In the next two problems, we’ll look at Maybe, pairs, and Either in more detail.
Haskell’s type system is rigid compared to most other languages. In time, you will come to view this as a feature—languages that let you ‘cheat’ their safety mechanisms end up making you pay for it with complexity elsewhere. But for now, let’s get familiar with the structures and strictures of types.
The Maybe datatype introduces nullability in a controlled fashion—values of the type Maybe a can be Nothing or Just x, where x is a value of type a. Note that Maybe is polymorphpic: we can choose whatever type we want for a, e.g., Just 5 :: Maybe Int, or we can leave a abstract, e.g., Just x :: Maybe a iff x :: a.
Write a function mapMaybe that behaves like map when its higher-order function argument returns Just x, but filters out results where the function returns Nothing.
mapMaybe :: (a -> Maybe b) -> [a] -> [b]
mapMaybe = undefinedThe pair datatype allows us to aggregate values: values of type (a,b) will have the form (x,y), where x has type a and y has type b.
Write a function swap that takes a pair of type (a,b) and returns a pair of type (b,a).
swap :: (a,b) -> (b,a)
swap = undefinedWrite a function pairUp that takes two lists and returns a list of paired elements. If the lists have different lengths, return a list of the shorter length. (This is called zip in the prelude. Don’t define this function using zip!)
pairUp :: [a] -> [b] -> [(a,b)]
pairUp = undefinedWrite a function splitUp that takes a list of pairs and returns a pair of lists. (This is called unzip in the prelude. Don’t define this function using unzip!)
splitUp :: [(a,b)] -> ([a],[b])
splitUp = undefinedWrite a function sumAndLength that simultaneously sums a list and computes its length. You can define it using natural recursion or as a fold, but—traverse the list only once!
sumAndLength :: [Int] -> (Int,Int)
sumAndLength l = undefinedProblem 8: defining polymorphic datatypes
The Either datatype introduces choice in a controlled fashion—values of the type Either a b can be either Left x (where x is an a) or Right y (where y is a b).
Define a datatype EitherList that embeds the Either type into a list. (This isn’t a good idea, but it’s a good exercise!)
To see what I mean, let’s combine lists and the Maybe datatype. Here’s Haskell’s list datatype:
data [a] = [] | a:[a]
Here’s the Maybe datatype:
data Maybe a = Nothing | Just a
What kinds of values inhabit the type [Maybe a]? There are two cases:
[], the empty lista:as, whereahas typeMaybe aandasis a list of type[Maybe a]
But we can really split it into three cases:
[], the empty lista:as, whereasis a list of type[Maybe a], and:aisNothingaisJust a', wherea'has typea
Put another way:
[], the empty listNothing:as, whereasis a list of type[Maybe a]Just a:as, whereahas typeaandashas type[Maybe a]
To define MaybeList, we’ll write a data structure that has those constructors expliclty.
data MaybeList a =
Nil
| ConsNothing (MaybeList a)
| ConsJust a (MaybeList a)
Note that these match up exactly with the last itemized list of cases.
Okay: do it for Either! Fill in the functions below—they should behave like the Prelude functions. You’ll also have to fill in the type. We’ve given you the constructors’ names. Make sure your Cons constructors takes arguments in the correct order, or we won’t be able to give you credit for any of this problem.
data EitherList a b =
Nil
| ConsLeft {- fill in -}
| ConsRight {- fill in -}
deriving (Eq, Show)
toEither :: [Either a b] -> EitherList a b
toEither = undefined
fromEither :: EitherList a b -> [Either a b]
fromEither = undefined
mapLeft :: (a -> c) -> EitherList a b -> EitherList c b
mapLeft = undefined
mapRight :: (b -> c) -> EitherList a b -> EitherList a c
mapRight = undefined
foldrEither :: (a -> c -> c) -> (b -> c -> c) -> c -> EitherList a b -> c
foldrEither = undefined
foldlEither :: (c -> a -> c) -> (c -> b -> c) -> c -> EitherList a b -> c
foldlEither = undefinedProblem 9: maps and sets
Haskell has many convenient data structures in its standard library. We’ll be playing with sets and maps today. Data.Map and Data.set are well documented on-line.
In this problem, we’ll use maps and sets to reason about graphs (in the network/graph theory sense, not in the statistical plotting sense).
We can start by defining what we mean by the nodes of the graph: we’ll have them just be strings. We can achieve this by using a type synonym.
type Node = StringTo create a Node, we can use the constructor, like so:
a = "a"
b = "b"
c = "c"
d = "d"
e = "e"We can define a graph now as a map from Nodes to sets of Nodes. The Map type takes two arguments: the type of the map’s key and the type of the map’s value. Here the keys will be Nodes and the values will be sets of nodes. The Set type takes just one argument, like lists: the type of the set’s elements.
type Graph = Map Node (Set Node)We don’t need to use newtype here, because we’re less worried about confusing graphs with other kinds of maps.
Let’s start by building a simple graph, g1:
- b -
/ \
a - - d
\ /
- c -
g1 = Map.fromList [(a, Set.fromList [b,c]),
(b, Set.fromList [a,d]),
(c, Set.fromList [a,d]),
(d, Set.fromList [b,c])]Note that we’ve been careful to make sure the links are bidirectional: if the b is in the value mapped by a, then a is in the value mapped by b.
We can see what a has edges to by looking it up in g1:
aEdges = g1 ! aWrite a function isBidi that checks whether a mapping is bidirectional. Feel free to use any function in Data.Map, Data.Set, or the Prelude, and write as many helper functions as you need.
You can assume that if you find a node in a set, then that node has a (possibly empty) entry in the graph. That is, I won’t give you a graph like:
badGraph = Map.fromList [(a, Set.singleton b)]isBidi :: Graph -> Bool
isBidi = undefinedWrite a function bidify that takes an arbitrary graph and makes it bidirectional by adding edges, i.e., if the node a points to b but not vice versa in a graph g, then a points to b and b points to a in the graph bidify g.
bidify :: Graph -> Graph
bidify = undefinedBe sure to test your code!