Haskell expand function - haskell

I was recently handed an assignment I have almost completed and I am currently in need of some help.
The first functions I needed to implement were lookUp, split, combine and keyWordDefs.
I then had to implement a function expand :: FileContents -> FileContents -> FileContents that takes the contents of a text file and an info file and combines them using the above functions to build a string representing the output file.
Here is my code so far:
module MP where
import System.Environment
type FileContents = String
type Keyword = String
type KeywordValue = String
type KeywordDefs = [(Keyword, KeywordValue)]
separators :: String
separators
= " \n\t.,:;!\"\'()<>/\\"
lookUp :: String -> [(String, a)] -> [a]
-- Given a search string and a list of string/item pairs, returns
-- the list of items whose associated string matches the search string.
lookUp x y = [a|(b,a) <- y, x==b]
split :: String -> String -> (String, [String])
-- Breaks up a string.
split as [] = ("",[""])
split as (b:bs)
| elem b as = (b:xs,"":y:ys)
| otherwise = (xs, (b:y):ys)
where
(xs,y:ys) = split as bs
combine :: [Char] -> [String] -> [String]
-- Combines the components of a string from its constituent separator
-- characters and words, as generated by a call to split.
combine [] y = y
combine (x:xs)(y:ys) = y : [x] : combine xs ys
getKeywordDefs :: [String] -> KeywordDefs
-- Takes the contents of an information file in the form of a list
-- of lines and which returns a list of keyword/definition pairs.
getKeywordDefs [] = []
getKeywordDefs (x:xs) = (keyword, concat defs) : getKeywordDefs xs
where
(_, (keyword : def)) = split " " x
defs = combine spaces def
spaces = [ ' ' | s <- [2..length def]]
expand :: FileContents -> FileContents -> FileContents
An example of the function expand is this:
expand "The capital of $1 is $2" "$1 Peru\n$2 Lima."
"The capital of Peru is Lima."
I suppose that this is going to work by 1st looking up (with function lookUp) if there is a "$" in the input string, then split the words, then replacing words that begin with "$" with the second input string, then combining them again all together? I am really confused actually, and I would like to know if anyone here understand how function expand will work.
Any help is welcome :)

Your expand function should look something like this:
-- It's probably better to change the type signature a little bit
-- because we're not returning the contents of a file, we're returning a string.
expand :: FileContents -> FileContents -> String
expand fc1 fc2 = let
keywordDefs = getKeywordDefs fc2
in replaceSymbols fc1 keywordDefs
Then you need a function named replaceSymbols, which splits up fc1 whenever it sees a $X, and then substitutes that $X for the result of looking up $X in keywordDefs.
replaceSymbols :: FileContents -> KeywordDefs -> String
Have a go at implementing that function and reply to this answer if you still need help :).

Related

How do I create a printRow function in Haskell?

I have the following function in Haskell:
printRow :: [(Int, String)] → String
I want it, given a list of pairs (the left element
giving the desired length of a field and the right element its contents), to format one row in a table. For example,
printRow [ (5, "Alice"),(6, "Allen"),(6, "female"),(6, "82000")]
should return the formatted row
"|Alice|Allen |female| 82000|"
I know that I should probably use the functions intercalate, map and uncurry, but I'm a bit stuck, as I'm not very familiar with functional programming. I've tried something in the likes of:
printRow (int, string) = if all isDigit string
-- all_digits should be right-aligned
then ...
else map intercalate "|" uncurry (int, string)
As you can probably guess, this won't work, and I'm not sure how to do it.
Knowing how to handle one formatting specification,
printOne :: (Int, String) -> String
printOne (int, string) =
if all isDigit string
then a ++ b ++ "|"
else b ++ a ++ "|"
where
a = ....
b = ....
c = length string
we can turn each specification in a list into a formatted string, with map, and concatenate the results
printRow specs = a ++ concat ( map printOne specs )
where
a = ....
You should be able to complete the code. To repeatedly output a space character, use replicate :: Int -> a -> [a].

Haskell: Convert String to [(String,Double)]

I parse an XML and get an String like this:
"resourceA,3-resourceB,1-,...,resourceN,x"
I want to map that String into a list of tuples (String,Double), like this:
[(resourceA,3),(resourceB,1),...,(resourceN,x)]
How is it possible to do this? I ve looked into the map function and also the split one. I am able to split the string by "-" but anything else...
This is the code i have so far:
split :: Eq a => a -> [a] -> [[a]]
split d [] = []
split d s = x : split d (drop 1 y) where (x,y) = span (/= d) s
it is just a function to split my string into a list of Stirng, but then i dont know how to continue.
What I want to do know is to loop over that new list that i have created with the split method and for each element create a tuple. I hace tried with the map function but i dont get it to compile even
So in Haskell you dont really mutate any value, instead you'll create a new list of pairs from the string you've described, so the solution would look something similar to the following:
import Data.List.Split
xmlList = splitOn "-" "resourceA,3-resourceB,4-resourceC,6"
commaSplit :: String -> [String]
commaSplit = splitOn ","
xmlPair :: [String] -> [(String, Double)] -- might be more efficient to use Text instead of String
xmlPair [x] = [(\x' -> ((head x') :: String, (read (last x')) :: Double )) (commaSplit x)]
xmlPair (x:xs) = xmlPair [x] ++ xmlPair xs
main :: IO ()
main = mapM_ (\(a,b) -> putStrLn (show a++" = "++ show b)) (xmlPair $ xmlList)
This is my quick and dirty way of showing things but I'm sure someone can always add a more detailed answer.

Replacing Ints with Strings

I need help with an exercise. I'm trying to write a function called "refer" that will take a list of novels and any given text with citations ([n]) and will return that text with the citations replaced by the novel's name.
refer will have the following signature of:
refer :: [(String, String, Int)] -> String -> String
For example, this is how it will be ran:
> refer [("author1", "novel1", 1999),("author2", "novel2", 2000)] txt
> "novel1(author1, 1999) and novel2(author2, 2000) are my favorite books of all time, but I like novel2(author2, 2000) the most!"
I wrote a function called, txt, which will show my text that I will use.
txt :: String
txt = "[1] and [2] are my favorite books of all time, but I like [2] the most!"
I wrote a helper function called, format, which will help me format the novels from ("author1", "novel1", 1999) to "novel1(author1, 1999)"
format :: (String, String, Int) -> String
format (name, novel, yearInt) = novel ++ " (" ++ name ++
", " ++ (show yearInt) ++ ")"
WHAT I THINK I NEED TO DO:
Step 1: I need to use words to break the input into a list of strings.
Step 2: I should make a helper function to parse through the list and if I find a citation, I should use format to replace that citation and recursively parse through the rest of the list until I've checked everything.
Step 3: Make a helper function to convert the string representation of the citation number into an Int (possibly, unwords) since I have to replace the citation with its corresponding element in the given list.
Step 4: Then I need to use rewords to turn my updated list back into a string.
WHAT I HAVE SO FAR:
refer :: [(String, String, Int)] -> String -> String
refer [] "" = ""
refer books txt = [string'| y <- words txt, ........... ]
-- I'm trying to say that I want to take the inputted list of
-- novels and text and turn them all into strings and store
-- them into y which will be represented by string'. I am not
-- sure what to do after this.
You could use words, but then you lose information about the white space between the words - i.e. words "a b" equals words "a b". Maybe this is not important, but it is something to keep in mind.
Without providing the exact solution, here is a function which replaces a with a' and b with b' in a list:
replace a a' b b' [] = [] -- base case
replace a a' b b' (x:xs) = c : replace a a' b b' xs
where c = if x == a then a' else if x == b then b' else x
Perhaps you can figure out how to adapt this to your problem.
Note also that this function may be written using map:
replace a a' b b' xs = map f xs
where f x = if x == a then a' else if x == b then b' else x
Another approach to this kind of string processing is to pattern match against the characters. Here is a function which removes all occurrences of "cat" from a string:
removeCat :: String -> String
removeCat ('c':'a':'t':rest) = rest -- "cat" found so remove it
removeCat (x:rest) = x : removeCat rest
If this is for a homework problem please don't copy verbatim.
It's easier to solve the generic problem.
First, you need a replace function to replace a substring. A straightforward implementation can be
replace :: String -> String -> String -> String
replace old new [] = []
replace old new input = if (take n input == old) then new++(recurse n)
else (take 1 input)++(recurse 1)
where n = length old
recurse k = replace old new $ drop k input
tries to match the "old" from the beginning of input and if matched replace and skip the length of old, if not matched move one position and repeat.
So far so good, this will replace all occurrences of "old" to "new". However, you need multiple different replacements. For that let's write another function. To minimize validations assume we paired all replacements.
replaceAll :: [(String,String)] -> String -> String
replaceAll [] input = input
replaceAll ((old,new):xs) input = replaceAll xs $ replace old new input
You can make the top level signature better by defining a type such as
type Novel = (String,String,Int)
finally,
refer :: [Novel] -> String -> String
refer [] text = text
refer ns text = replaceAll p text
where p = [ ("["++show i++"]",format n) | (i,n) <- zip [1..] ns]
notice that the citation input is derived from the position of the Novels.

Haskell extract substring within a string

My goal is to find the number of times a substring exists within a string.
The substring I'm looking for will be of type "[n]", where n can be any variable.
My attempt involved splitting the string up using the words function,
then create a new list of strings if the 'head' of a string was '[' and
the 'last' of the same string was ']'
The problem I ran into was that I entered a String which when split using
the function words, created a String that looked like this "[2],"
Now, I still want this to count as an occurrence of the type "[n]"
An example would be I would want this String,
asdf[1]jkl[2]asdf[1]jkl
to return 3.
Here's the code I have:
-- String that will be tested on references function
txt :: String
txt = "[1] and [2] both feature characters who will do whatever it takes to " ++
"get to their goal, and in the end the thing they want the most ends " ++
"up destroying them. In case of [2], this is a whale..."
-- Function that will take a list of Strings and return a list that contains
-- any String of the type [n], where n is an variable
ref :: [String] -> [String]
ref [] = []
ref xs = [x | x <- xs, head x == '[', last x == ']']
-- Function takes a text with references in the format [n] and returns
-- the total number of references.
-- Example : ghci> references txt -- -> 3
references :: String -> Integer
references txt = len (ref (words txt))
If anyone can enlighten me on how to search for a substring within a string
or how to parse a string given a substring, that would be greatly appreciated.
I would just use a regular expression, and write it like this:
import Text.Regex.Posix
txt :: String
txt = "[1] and [2] both feature characters who will do whatever it takes to " ++
"get to their goal, and in the end the thing they want the most ends " ++
"up destroying them. In case of [2], this is a whale..."
-- references counts the number of references in the input string
references :: String -> Int
references str = str =~ "\\[[0-9]*\\]"
main = putStrLn $ show $ references txt -- outputs 3
regex is huge overkill for such a simple problem.
references = length . consume
consume [] = []
consume ('[':xs) = let (v,rest) = consume' xs in v:consume rest
consume (_ :xs) = consume xs
consume' [] = ([], [])
consume' (']':xs) = ([], xs)
consume' (x :xs) = let (v,rest) = consume' xs in (x:v, rest)
consume waits for a [ , then calls consume', which gathers everything until a ].
Here's a solution with
sepCap.
import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Either
import Data.Maybe
txt = "[1] and [2] both feature characters who will do whatever it takes to " ++
"get to their goal, and in the end the thing they want the most ends " ++
"up destroying them. In case of [2], this is a whale..."
pattern = single '[' *> anySingle <* single ']' :: Parsec Void String Char
length $ rights $ fromJust $ parseMaybe (sepCap pattern) txt
3

Haskell load external txt file into list

Hello the following code is a wordfeud program. It allows you to search through a list of words matching a prefix, suffix, and some letters. My question is, instead of using the list at the bottom, I want to use a external text file containing the words and load it into the list. How do I go about doing this?
count :: String -> String -> Int
count _[] = 0
count [] _ = 0
count (x:xs) square
|x `elem` square = 1 + count xs (delete x square)
|otherwise = count xs square
check :: String -> String -> String -> String -> Bool
check prefix suffix word square
| (length strippedWord) == (count strippedWord square) = True
| otherwise = False
where
strippedWord = drop (length prefix) (take ((length word ) - (length suffix)) word)
wordfeud :: String -> String -> String -> [String]
wordfeud a b c = test1
where
test =["horse","chair","chairman","bag","house","mouse","dirt","sport"]
test1 = [x| x <- test, a `isPrefixOf` x, b `isSuffixOf` x, check a b x c]
Very simple, with help of the lines function (or words, when words are seperated by some other form of whitespace than line breaks):
-- Loads words from a text file into a list.
getWords :: FilePath -> IO [String]
getWords path = do contents <- readFile path
return (lines contents)
Furthermore, you'll probably have to read up on IO in Haskell (I recommend googling 'io haskell tutorial'), if you haven't done so already. You'll also need it to introduce interactivity into your program.

Resources