Solutions to the Problem Sheet for LI Functional Programming - Week 5 & 6
Directories
The following solutions are written for the version of the Directory data type that we developed in the Online Lab Session. Here was the result:
data File = File {
fileName :: String,
fileContents :: String
} deriving (Eq,Show)
data Directory = Dir {
dirName :: String,
dirContents :: [ DirectoryEntry ]
} deriving (Eq,Show)
data DirectoryEntry =
FileEntry File
| DirEntry Directory
deriving (Eq,Show)
Create your own sample directory structure
sampleFile :: File
sampleFile = File {
fileName = "notes.txt",
fileContents = "don't forget the milk!"
}
sampleDir :: Directory
sampleDir = Dir {
dirName = "root",
dirContents = [
FileEntry sampleFile,
DirEntry Dir {
dirName = "home",
dirContents = [
FileEntry File {
fileName = "lab-ideas.txt",
fileContents = "work on directories ..."
},
FileEntry File {
fileName = "todo.txt",
fileContents = "finish the online lab"
}
]}
]}Write a function to return a list of all the files.
allFileNames :: Directory -> [ String ]
allFileNames d = [ fnm | e <- dirContents d, fnm <- fileNamesOfEntry e ]
where
fileNamesOfEntry :: DirectoryEntry -> [ String ]
fileNamesOfEntry (FileEntry f) = [ fileName f ]
fileNamesOfEntry (DirEntry sd) = allFileNames sdHere is a function to find a substring and return its index:
import Data.List
findString :: (Eq a) => [a] -> [a] -> Maybe Int
findString search str = findIndex (isPrefixOf search) (tails str)Write a function to search the directory for a file containing some string.
grep :: String -> Directory -> [(String,Int)]
grep searchStr d = [ r | e <- dirContents d, r <- grepEntry e ]
where grepEntry :: DirectoryEntry -> [(String,Int)]
grepEntry (FileEntry f) =
case findString searchStr (fileContents f) of
Nothing -> []
Just idx -> [(fileName f,idx)]
grepEntry (DirEntry sd) = grep searchStr sdWrite a function to search for files by name.
Note You may notice that it is possible to write a generic program to find all files which satisfy some given predicate. We use that approach to simplify out task later on.
findFiles :: (File -> Bool) -> Directory -> [File]
findFiles p d = [ f | e <- dirContents d , f <- filesInEntry e ]
where
filesInEntry (FileEntry f) | p f = [ f ]
| otherwise = []
filesInEntry (DirEntry sd) = findFiles p sdNow we can simply use this auxillary function as follows:
findFilesByName :: String -> Directory -> [File]
findFilesByName nm d = findFiles (\f -> nm == fileName f) dModify the
File
type to contain different types of data: binary (you may wish to look atByteString
from the Haskell prelude) and string. Write functions to find all binary or text files in the given directory structure.import Data.ByteString
data File = File {
fileName :: String,
fileContents :: Either String ByteString
} deriving (Eq,Show)
isBinary :: File -> Bool
isBinary f = case fileContents f of
Left _ -> False
Right _ -> True
findBinaryFiles :: Directory -> [ File ]
findBinaryFiles = findFiles isBinary
findTextFiles :: Directory -> [ File ]
findTextFiles = findFiles (\f -> not (isBinary f))Write a function to sort the directory contents alphbetically.
instance Ord DirectoryEntry where
compare e f = compare (nameOf e) (nameOf f)
where
nameOf (FileEntry f) = fileName f
nameOf (DirEntry d) = dirName d
sortDir :: Directory -> Directory
sortDir d = Dir {
dirName = dirName d,
dirContents = sort (map sortEntry (dirContents d))
}
where
sortEntry (FileEntry f) = FileEntry f
sortEntry (DirEntry sd) = DirEntry (sortDir sd)Harder Write a function to print the directory contents as a text tree.
dirTree :: Directory -> String
dirTree d = unlines ((dirName d):go d 2)
go :: Directory -> Int -> [ String ]
go d i = concat (map (\e -> entryTree e i) (dirContents d))
entryTree :: DirectoryEntry -> Int -> [ String ]
entryTree (FileEntry f) i = [ replicate (i-1) '.' ++ "> " ++ fileName f ]
entryTree (DirEntry sd) i = (replicate (i-1) '.' ++ "> " ++ dirName sd):(go sd (i+2))
XML
Consider the following simplified data type for representing XML documents:
data Element = Element {
elName :: String,
elAttribs :: [Attr],
elContent :: [Content]
} deriving Show
data Content = Elem Element
| Text String
deriving Show
data Attr = Attr {
attrKey :: String,
attrVal :: String
} deriving (Eq, Show)
Encode the following example:
<person gender="female">
<firstname>Anna</firstname>
<lastname>Smith</lastname>
</person>example1 :: Element
example1 = Element {
elName = "person",
elAttribs = [
Attr {
attrKey = "gender",
attrVal = "female"
},
Attr {
attrKey = "hometown",
attrVal = "Birmingham"
}
],
elContent = [
Elem Element {
elName = "firstname",
elAttribs = [],
elContent = [
Text "Anna"
]
},
Elem Element {
elName = "lastname",
elAttribs = [],
elContent = [
Text "Smith"
]
}
]
}Encode the following example:
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>example2 :: Element
example2 = Element {
elName = "note",
elAttribs = [],
elContent = [
Elem Element {
elName = "to",
elAttribs = [],
elContent = [Text "Tove"]
},
Elem Element {
elName = "from",
elAttribs = [],
elContent = [Text "Jani"]
},
Elem Element {
elName = "heading",
elAttribs = [],
elContent = [Text "Reminder"]
},
Elem Element {
elName = "body",
elAttribs = [],
elContent = [Text "Don't forget me this weekend!"]
}
]
}Write a function which returns all elements containing only text in their content
Again, we will use the strategy of first writing a generic find function. We can then specialize it to the cases we like.
findAllElements :: (Element -> Bool) -> Element -> [Element]
findAllElements p root | p root = root:childEls
| otherwise = childEls
where
contentEls :: Content -> [Element]
contentEls (Text _) = []
contentEls (Elem c) = findAllElements p c
childEls :: [ Element ]
childEls = [ el | child <- elContent root ,
el <- contentEls child ]Now we write a predicate to detect elements with only text children and then use the previous function:
hasOnlyTextChildren :: Element -> Bool
hasOnlyTextChildren e = all isText (elContent e)
where isText :: Content -> Bool
isText (Text _) = True
isText _ = False
findAllTextOnlyElements :: Element -> [Element]
findAllTextOnlyElements = findAllElements hasOnlyTextChildren
```
Write a function which returns all elements which have exactly one child element
findAllOneChildElements :: Element -> [Element]
findAllOneChildElements = findAllElements (\e -> length (elContent e) == 1)Write a function which searches for all elements with a given attribute key
findAllElementsWithAttr :: String -> Element -> [Element]
findAllElementsWithAttr attr =
findAllElements
(\e -> any (\a -> attrKey a == attr) (elAttribs e))Write a function which formats the XML data to a string
printXml :: Element -> String
printXml e = unlines (printElement e 0)
printElement :: Element -> Int -> [ String ]
printElement e i =
[indentation ++ "<" ++ unwords (elName e : map printAttrib (elAttribs e)) ++ ">"] ++
map (\s -> indentation ++ s) (concat (map (\c -> printContent c (i+2)) (elContent e))) ++
[indentation ++ "</" ++ elName e ++ ">"]
where indentation = replicate i ' '
printAttrib :: Attr -> String
printAttrib a = attrKey a ++ "=\"" ++ attrVal a ++ "\""
printContent :: Content -> Int -> [ String ]
printContent (Text s) i = [ " " ++ s ]
printContent (Elem e) i = printElement e i
You can continue with examples like the above by inventing your own transformations and queries on the directory and XML examples. Be creative!