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.
Related
I'm doing my Haskell Datentyp homework, and have try to solve this problem for a whole day.
The question is like this .
Use this Datentyp and only one function tokenize, other functions in the library are not permitted.
data Token = Text String
| Placeholder String
deriving (Eq, Show)
tokenize :: String -> [Token]
The brace is the sign of Placeholder
The result should like this:
when we give input"Hallo {name}, {xyz}."
should give [Text "Hallo ", Placeholder "name", Text ", ", Placeholder "xyz", Text "."] out
I have tried for a whole day, only Text output or Placehold is OK, but I have several problems:
How to handle curly brackets {}
How to use pattern match to match "{longtext...}"
Because of Datentype [token], tokenize (x: xs) = x :[ Text (xs:[])] do not works , so how to deal with it
how to combine the comma and all of them together, with only type [token] result function,I have tried ++ and : , but they all report error.
According to 4, there is a common question besides this homework: when using Datentype, and type of result doesn't fits type of input, how to use recursion.
This is the last edition of my answer, it is still far away
data Token = Text String
| Placeholder String
deriving (Eq, Show)
tokenize :: String -> [Token]
tokenize [] = []
tokenize ('{':x:xs) = [ Placeholder (x:xs)]
tokenize (x: xs) = [ Text (x:xs)]
otherwise = error "TODO: Implementierung vervollstaendigen"
The result should like this:
when we give input"Hallo {name}, {xyz}."
should give [Text "Hallo ", Placeholder "name", Text ", ", Placeholder "xyz", Text "."] out
Suppose all the input without internal bracket and are correctly denoted by curly brackets{}
In the real-world, you'd probably want to use a library like Parsec. But for a homework, something like the following is probably enough:
data Token = Text String
| Placeholder String
deriving (Eq, Show)
tokenize :: String -> [Token]
tokenize xs = parseText "" xs
parseText :: String -> String -> [Token]
parseText text ('{':xs) = Text text : parsePlaceholder "" xs
parseText text (x:xs) = parseText (text ++ [x]) xs
parseText text "" = [Text text]
parsePlaceholder :: String -> String -> [Token]
parsePlaceholder name ('}':xs) = Placeholder name : parseText "" xs
parsePlaceholder name (x:xs) = parsePlaceholder (name ++ [x]) xs
parsePlaceholder name "" = [Text name]
main :: IO ()
main = print (tokenize "Hallo {name}, {xyz}.")
Instead of studying the code closely right away, you may want to try to copy only the type signatures of parseText and parsePlaceholder to see whether you can implement them yourself.
A few points of critique:
Appending to the end of a linked list (as I do with text ++ [x] and name ++ [x]) is not very efficient. A possible approach would be to prepend (with (:)) and then in the end do once a reverse.
tokenize "{foo}" returns [Text "",Placeholder "foo",Text ""], which is not so nice, you may want to incorporate the following helper function:
createText "" = []
createText text = [Text text]
Since we are writing a parser, we can benefit a great deal from using traditional parser techniques. The key point is that you want a parse function to parse only a part of the string at once, and so it is most convenient for it to return two things: the tokens parsed so far, and the remainder of the string: what's left to be parsed after handling those tokens.
So, our main recursive function will be
parse :: String -> (String, [Token])
This signature does not match the required signature of tokenize, but it is easy to write tokenize to delegate to it:
tokenize :: String -> [Token]
tokenize s = case parse s of
("", r) -> r
(s, r) -> error ("unexpected text " ++ s)
Makes sense, right? If we parsed the stream and there's somehow text left, something went wrong; otherwise, the remainder is empty, so what's been parsed so far must be all of it.
parse itself is responsible for deciding, based on the first character it sees, whether to produce a Text or a Placeholder. In either case, it consumes the corresponding token, using break to identify the closing } (in the case of Placeholder), or the next opening { (in the case of Text). It conses the generated token onto the result of the recursive call to parse:
parse :: String -> (String, [Token])
parse "" = ("", [])
parse ('{':s) = let (name, ('}':s')) = break (== '}') s
in (Placeholder name :) <$> parse s'
parse s = let (text, s') = break (== '{') s
in (Text text :) <$> parse s'
Note that there is a hidden failure mode here, not exposed in the type signature: if the input contains a { character with no matching }, we will have a runtime error. But the signature of tokenize gives us little other choice: we have no way to indicate an invalid parse in the result type, so we can either lie by claiming there was a valid parse, or terminate the program abnormally; I prefer the latter.
Here's a simple solution, perhaps not efficient though. The general idea is to keep track of which tokens we've already parsed, which chars are to be added to the next Text token and what is left.
First we'll call a helper function named tokenize' (note the single quote at the end) which takes an additional param named partial that is a list of accumulated chars, which are partial Text-type Token.
When we encounter a '{' we scan the rest of the string to split on the first '}', create a Placeholder token and continue to process the rest of the string recursively. If we encounter any other char, we simply append it to partial.
Since it's a homework assignment, I've left the implementation of splitPlaceholder as an exercise.
tokenize :: String -> [Token]
tokenize str = tokenize' str []
partialToText :: String -> [Token]
partialToText [] = []
partialToText xs = [Text xs]
tokenize' :: String -> String -> [Token]
tokenize' [] partial = partialToText partial
tokenize' ('{':xs) partial =
let splitted = splitPlaceholder xs
placeholderText = Placeholder (head splitted)
rest = head $ tail splitted
nextToken = partialToText partial
in nextToken ++ (placeholderText : tokenize' rest [])
tokenize' (x:xs) partial = tokenize' xs (partial ++ [x])
-- split input to two parts, first is everything till the first '}' (not including)
-- and the second is the rest of the input string
splitPlaceholder :: String -> [String]
-- implementation left as an exercise
I have this function that checks if a character is one of these punctuation signs.
checkpunctuation:: Char -> Bool
checkpunctuationc = c `elem` ['.', ',', '?', '!', ':', ';', '(', ')']
I have to write another function that after every punctuation sign it adds a space
format :: String -> String
I know how to add space after a given number of characthers but don't know how to add after specific characters.
Simple recursive option:
format :: String -> String
format [] = []
format (x:xs) | checkpuntuationc x = x : ' ' : format xs
| otherwise = x : format xs
Another option is to use foldr with a helper function:
helper :: Char -> String -> String
helper x xs | checkpunctuation x = x : ' ' : xs
| otherwise = x : xs
The helper checks if the first character is a punctuation. If so it inserts a space, otherwise it does not.
and then define format as:
format :: String -> String
format = foldr helper []
A sample call:
*Main> format "Hello? Goodbye! You say goodbye!! (and I say Hello)"
"Hello? Goodbye! You say goodbye! ! ( and I say Hello) "
This function works also on "infinite strings":
*Main> take 50 $ format $ cycle "Hello?Goodbye!"
"Hello? Goodbye! Hello? Goodbye! Hello? Goodbye! He"
So although we feed it a string that keeps cycle-ing, and thus never ends, we can derive the first 50 characters of the result.
There's probably a more elegant way to do it, but
format :: String -> String
format s = concat [if (checkpunctuation c) then (c:" ") else [c] | c <- s]
will work (thanks, #Shou Ya!).
Edit based on comment
To count the total length of post-formatted punctuation characters, you can use
sumLength :: [String] -> Int
sumLength strings = 2 * (sum $ fmap length (fmap (filter checkpunctuation) strings))
as the it is twice the sum of the number of punctuation characters.
We have been given an exercise where we have to 'frame' a string with a character input by the user. For instance:
*******
* AAA *
*A A*
*AAAAA*
*A A*
*A A*
*******
I have previously defined strings to represent letters from a charecter into string, and also a function to manipulate the size of it, simply put as 'stretch'. I have tried to firstly surround these strings with asterisk and to try get the output, as the example above shows, but I keep receiving type errors. I am currently able to do it line by line (shown below) in terminal as I will show before but have had no luck implementing it into a function (also below)... any suggestions?
Line by line code at the interpreter:
EX01> map(\x-> "*" ++ x ++ "*")(letter 'a')
["* AAA *","*A A*","*AAAAA*","*A A*","*A A*"]
EX01> top_and_bottom $$
["*******","* AAA *","*A A*","*AAAAA*","*A A*","*A
A*","*******"]
EX01> map(\x-> x ++ "\n") $$
["*******\n","* AAA *\n","*A A*\n","*AAAAA*\n","*A A*\n","*A >A*\n","*******\n"]
EX01> concat $$
"*******\n* AAA *\n*A A*\n*AAAAA*\n*A A*\n*A A*\n*******\n"
EX01> putStr$$
*******
* AAA *
*A A*
*AAAAA*
*A A*
*A A*
*******
My attempt at implementing it into a function:
stars :: Int -> String
stars 0 = ""
stars n = '*':(stars (n-1))
top_and_bottom :: [String] -> [String]
top_and_bottom x = (fringe : x) ++ [fringe]
where
fringe = (stars (length (head x)))
framestar :: [String] -> [String]
framestar x = putStr(concat(top_and_bottom(map(\x -> "*" ++ x ++ "*")[x])))
(P.S.: How could I also do this so it could take any character?)
I suggest you solve a simpler more generic problem and think how you can use it in your case
wrap :: a -> [a] -> [a]
wrap x xs = x:xs++[x]
now you can apply this to your problem first for each row where xs is a row and x = *. Second you can think your xs is the rows and x = "*****" (use correct length). If you know and allowed to use map you use the wrap for each row, and second wrap once to the result. It will be a one liner.
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 :).
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