Question

I want to replace full words in text. Type definition is: replace :: [(String, String)] -> String -> String. Function example is:

replace [("fox", "cat"), ("he", "she")] "The quick brown fox jumps over the lazy dog"
-- result: "The quick brown cat jumps over the lazy dog"`

It should be with functions words and unwords.

Do you have any idea?

Was it helpful?

Solution

Understanding the assignment

Your assignment says that you should use words and unwords:

words   :: String -> [String]
unwords :: [String] -> String

It is therefore clear, that you need something to operate on a list of Strings:

innerFunc :: [String] -> [String]

This will enable you to use unwords . innerFunc . words. However, that looks just like mapping over Strings with a correct replacement function:

innerFunc = map f

where f should be something like

f :: String -> String

So it's enough if you manage to create a function that replaces a single word correctly.

Solving the assignment

As stated above, we're now looking for a function that replaces a single word correctly. For convenience, lets create a type synonym Word*, so we know whether this is currently meant for a single word or a whole sentence.

* note that Haskell provides Word already in Data.Word, but that is something completely different, namely a unsigned Int variant, so it's just for convenience in the following code

type Word = String

Replace a single word by a single replacement patterns

Lets start very simple. We shall write a function that works only for a single word:

replaceSimple :: (Word, Word) -> Word -> Word
replaceSimple (orig, repl) word = if orig == word then repl else word

It's clear that this will replace word iff orig == word:

replaceSimple ("fox", "cat") "fox" == "cat"
replaceSimple ("fox", "cat") "dog" == "dog"

Replace a single word by a many replacement patterns

Now we want to use that knowledge for a function that still operates on a single Word, but takes it replacements from a list and applies the first one that matches:

replaceSingle :: [(Word, Word)] -> Word -> Word
replaceSingle []     word = word
replaceSingle (r:rs) word = if word == fst r then snd r else replaceMany rs word

Look closely: replaceSingle will leave the word unchanged iff the list of replacements is empty or there's a identitiy replacement like ("fox","fox") before any other replacement. Otherwise it's going to exchange word with the replacement if they match or try the rest of the replacement rules.

Putting things together

Now replaceSingle almost looks like f above, doesn't it? And it sure is! All you need to provide is the list of replacements:

:t replaceSingle [("fox","cat"),("he","she")]
replaceSingle [("fox","cat"),("he","she")] :: String -> String

All you need to do is to map this function as written above.

Putting together the functions map, replaceSingle, unwords and words is left as an exercise to the reader.

OTHER TIPS

For text manipulation in modern Haskell is generally recommended to not use the String type and instead use the text package. If for some reason you are starting with Strings, you can convert them to Text with pack. Then, with replace function that allows you to do something like this:

>>> :set -XOverloadedStrings
>>> import Data.Text
>>> replace "he" "she" . replace "fox" "cat" $ "The quick brown fox jumps over the lazy dog"
"Tshe quick brown cat jumps over tshe lazy dog"

If for some reason you are stuck with your original type signature here is a way to implement it:

>>> import Data.Monoid
>>> import Data.Foldable
>>> import Control.Monad
>>> import Control.Arrow
>>> let replace' l = unpack . appEndo (foldMap (Endo . uncurry replace . join (***) pack) l) . pack
>>> :t replace'
replace' :: Foldable t => t (String, String) -> String -> String
>>> replace' [("he","she"), ("fox","cat")] "The quick brown fox jumps over the lazy dog"
"Tshe quick brown cat jumps over tshe lazy dog"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top