Need help - Haskell Replace - string

I need to change one string from a list of strings (chars if a string is just one letter) for another one from the other list. The signature looks like this:
replace :: [(String, String)] -> String -> String
and I should use functions words/unwords. for e.g. I have [("train","Car")] "John got hit by train." and if I run it the result has to be "John got hit by car.". You see the "train" string was replaced for the "car" one.
I tried everything but I couldn't figure it out.
Can you help me with this?

Please note that the other provided solutions are more idiomatic. Since it sounds like you're being taught and need to structure it this way, here's an example using words/unwords and explicit recursion:
replace :: [(String, String)] -> String -> String
replace a b = unwords $ foldl inner (words b) a
where inner ls tup = replace' tup ls
replace' :: (String, String) -> [String] -> [String]
replace' tup (x:xs)
| x == fst tup = snd tup : replace' tup xs
| otherwise = x : replace' tup xs
replace' _ [] = []
This words approach breaks when you use punctuation, but it works for simple examples:
*Main> replace [("train", "car"), ("dog", "cat")] "dog cannot drive a train"
"cat cannot drive a car"

Let's use map change to apply the change function to each string.
lookup :: Eq a => a -> [(a, b)] -> Maybe b checks a list of pairs for a matching a and gives you Just b if it found one and Nothing otherwise. Let's check the output of lookup oldString changeList and replace the Nothings with the oldString if we get it, but use a newString if we get one:
replace changeList input = map change input where
change oldString = case lookup oldString changeList of
Just newString -> newString
Nothing -> oldString

I'm not certain I understand your question, but you might want
replace xs w w' = map (\(a,b) -> (a,if b == w then w' else b)) xs
For example: replace [(1,2), (3,2), (5,4)] 2 0 => [(1,0), (3,0), (5,4)]. This function generalizes, and should work on strings.

Related

Taking an input string and return a string list of only the words that start with "ba"?

I'm trying to write a code in which would take a input string say:
I love bacon, but I love bananas more.
and return ["bacon","bananas"] as output.
However I've ran into some troubles with my code, as I can't seem to properly implement this, currently my idea is that I would input a string and then use word() to split up the string into a string list, and then call getWrods to extract all the words with "ba" as their prefix and then return a list composed of words that start with "ba" for the main function allWords.
My code is as follows:
getWords:: String -> [String] -> [String]
getWords n [] = []
getWords n (x:xs)
| n isPrefixOf x = [x] ++ getWords n xs
|otherwise = getWords n xs
allWordss:: String -> [String]
allWordss n = getWords("ba" words(n))
I think that by using filter :: (a -> Bool) -> [a] -> [a] here, you make the problem easier.
You can as filter condition use - like in your code isPrefixOf :: Eq a => [a] -> [a] -> Bool, but you here wrote in in "infix" notation, but without writing backticks. You thus can call the function with:
isPrefixOf n x
or:
n `isPrefixOf` x
A final problem with your code is that you write:
getWords("ba" words(n))
Here you seem to call a function with brackets, which is quite common in languages like Java, C++, etc. In Haskell however, a function f is called with a parameter x like f x, so you make a call with:
getWords "ba" (words n)
If we use filter here, we thus obtain:
allBaWords :: String -> [String]
allBaWords n = filter (\x -> isPrefixOf "ba" x) (words n)
or shorter:
allBaWords :: String -> [String]
allBaWords = filter (isPrefixOf "ba") . words
We can break up the problem into three logical parts:
Separate a string into a list of words.
Recognize whether a word starts with "ba".
Given a list, get a list of all the elements that satisfy a certain condition (often called a predicate).
Let's start by importing a couple standard modules:
import Data.List (isPrefixOf)
import Data.String (words)
Let's start with (2):
startsWithBa :: String -> Bool
startsWithBa s = -- something using isPrefixOf
As others have noted, you have to enclose isPrefixOf in backticks if you want to use it infix (which most people tend to do so it reads nicely).
Now to separate the string into words, we use
words :: String -> [String]
To extract just the strings that start with "ba", we can use the function
filter :: (a -> Bool) -> [a] -> [a]
I'll let you try to put these pieces together.

mapping over entire data set to get results

Suppose I have the arrays:
A = "ABACUS"
B = "YELLOW"
And they are zipped so: Pairing = zip A B
I also have a function Connect :: Char -> [(Char,Char)] -> [(Char,Char,Int)]
What I want to do is given a char such as A, find the indices of where it is present in the first string and return the character in the same positions in the second string, as well as the position e.g. if I did Connect 'A' Pairing I'd want (A,Y,0) and (A,L,2) as results.
I know I can do
pos = x!!map fst pairing
to retrieve the positions. And fnd = findIndices (==pos) map snd pairing to get what's in this position in the second string but in Haskell how would I do this over the whole set of data (as if I were using a for loop) and how would I get my outputs?
To do exactly as you asked (but correct the initial letter of function names to be lowercase), I could define
connect :: Char -> [(Char,Char)] -> [(Char,Char,Int)]
connect c pairs = [(a,b,n)|((a,b),n) <- zip pairs [0..], a == c]
so if
pairing = zip "ABACUS" "YELLOW"
we get
ghci> connect 'A' pairing
[('A','Y',0),('A','L',2)]
However, I think it'd be neater to zip once, not twice, using zip3:
connect3 :: Char -> String -> String -> [(Char,Char,Int)]
connect3 c xs ys = filter (\(a,_,_) -> a==c) (zip3 xs ys [0..])
which is equivalent to
connect3' c xs ys = [(a,b,n)| (a,b,n) <- zip3 xs ys [0..], a==c]
they all work as you wanted:
ghci> connect3 'A' "ABACUS" "YELLOW"
[('A','Y',0),('A','L',2)]
ghci> connect3' 'A' "ABACUS" "AQUAMARINE"
[('A','A',0),('A','U',2)]
In comments, you said you'd like to get pairs for matches the other way round.
This time, it'd be most convenient to use the monadic do notation, since lists are an example of a monad.
connectEither :: (Char,Char) -> String -> String -> [(Char,Char,Int)]
connectEither (c1,c2) xs ys = do
(a,b,n) <- zip3 xs ys [0..]
if a == c1 then return (a,b,n) else
if b == c2 then return (b,a,n) else
fail "Doesn't match - leave it out"
I've used the fail function to leave out ones that don't match. The three lines starting if, if and fail are increasingly indented because they're actually one line from Haskell's point of view.
ghci> connectEither ('a','n') "abacus" "banana"
[('a','b',0),('a','n',2),('n','u',4)]
In this case, it hasn't included ('n','a',2) because it's only checking one way.
We can allow both ways by reusing existing functions:
connectBoth :: (Char,Char) -> String -> String -> [(Char,Char,Int)]
connectBoth (c1,c2) xs ys = lefts ++ rights where
lefts = connect3 c1 xs ys
rights = connect3 c2 ys xs
which gives us everything we want to get:
ghci> connectBoth ('a','n') "abacus" "banana"
[('a','b',0),('a','n',2),('n','a',2),('n','u',4)]
but unfortunately things more than once:
ghci> connectBoth ('A','A') "Austria" "Antwerp"
[('A','A',0),('A','A',0)]
So we can get rid of that using nub from Data.List. (Add import Data.List at the top of your file.)
connectBothOnce (c1,c2) xs ys = nub $ connectBoth (c1,c2) xs ys
giving
ghci> connectBothOnce ('A','A') "ABACUS" "Antwerp"
[('A','A',0),('A','t',2)]
I would recommend not zipping the lists together, since that'd just make it more difficult to use the function elemIndices from Data.List. You then have a list of the indices that you can use directly to get the values out of the second list.
You can add indices with another zip, then filter on the given character and convert tuples to triples. Especially because of this repackaging, a list comprehension seems appropriate:
connect c pairs = [(a, b, idx) | ((a, b), idx) <- zip pairs [0..], a == c]

Haskell: Pattern Matching to combine String

I'm trying to write a function which adds single characters from a string to a list of strings, for instance
combine ", !" ["Hello", "", "..."] = ["Hello,", " ", "...!"]
I've tried this:
combine :: String -> [String] -> [String]
combine (y:ys) (x:xs) =
[x:y, combine ys xs]
A simple one would be
combine :: [Char] -> [String] -> [String]
combine [] _ = []
combine _ [] = []
combine (c:cs) (x:xs) = x ++ [c] : combine cs xs
Or even more simply using zipWith
combine :: [Char] -> [String] -> [String]
combine = zipWith (\c x -> x ++ [c])
I had to do a bit extra to get this to work. I'll break it down for you.
First, I specified the type of the function as [Char] -> [String] -> [String]. I could have used String for the first argument, but what you're operating on conceptually is a list of characters and a list of strings, not a string and a list of strings.
Next, I had to specify the edge cases for this function. What happens when either argument is the empty list []? The easy answer is to just end the computation then, so we can write
combine [] _ = []
combine _ [] = []
Here the _ is matching anything, but throwing it away because it isn't used in the return value.
Next, for the actual body of the function We want to take the first character and the first string, then append that character to the end of the string:
combine (c:cs) (x:xs) = x ++ [c]
But this doesn't do anything with cs or xs, the rest of our lists (and won't even compile with the type signature above). We need to keep going, and since we're generating a list, this is normally done with the prepend operator :
combine (c:cs) (x:xs) = x ++ [c] : combine cs xs
However, this is such a common pattern that there is a helper function called zipWith that handles the edge cases for us. It's type signature is
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
It walks down both input lists simultaneously, passing the corresponding elements into the provided function. Since the function we want to apply is \c x -> x ++ [c] (turned into a lambda function), we can drop it in to zipWith as
combine cs xs = zipWith (\c x -> x ++ [c]) cs xs
But Haskell will let us drop arguments when possible, so we can eta reduce this to
combine :: [Char] -> [String] -> [String]
combine = zipWith (\c x -> x ++ [c])
And that's it!
When you want to combine lists element by element, it is usually a zip you are looking at. In this case, you know exactly how you want to combine the elements – that makes it a zipWith.
zipWith takes a "combining function" and then creates a function that combines two lists using said combining function. Let's call your "combining" function append, because it adds a characters to the end of a string. You can define it like this:
append char string = string ++ [char]
Do you see how this works? For example,
append 'e' "nic" = "nice"
or
append '!' "Hello" = "Hello!"
Now that we have that, recall that zipWith takes a "combining function" and then creates a function that combines two lists using that function. So your function is then easily implemented as
combine = zipWith append
and it will do append on each of the elements in order in the lists you supply, like so:
combine ", !" ["Hello", "", "..."] = ["Hello,", " ", "...!"]
You are close. There are a couple issues with what you have.
y has type Char, and x has type String which is an alias for [Char]. This means that you can add y to the top of a list with y : x, but you can't add y to the end of a list using the same : operator. Instead, you make y into a list and join the lists.
x ++ [y]
There must also be a base case, or this recursion will continue until it has no elements in either list and crash. In this case, we likely don't have anything we want to add.
combine [] [] = []
Finally, once we create the element y ++ [x] we want to add it to the top of the rest of the items we have computed. So we use : to cons it to our list.
combine :: String -> [String] -> [String]
combine [] [] = []
combine (x : xs) (y : ys) = (y ++ [x]) : (combine xs ys)
One note about this code, if there is ever a point where the number of characters in your string is different from the number of strings in you list, then this will crash. You can handle that case in a number of ways, bheklilr's answer addresses this.
kqr's answer also works perfectly and is probably the best one to use in practice.

Replacing strings with strings from a list

I am trying to write a function that takes a list of searchwords, a list of replacementwords and a string on which those will be used.
listReplace :: [String] -> [String] -> String -> String
The tricky part is that if the fitting searchword is the n'th, then the n'th replacementword should be used. Also, when a replacementword has been used it should not be replaced by a different replacementword if it is actually a searchword itself. I've already written these kind of functions for
replace :: String -> String -> String -> String:
replace x y [] = []
replace x y (z:zs) = if isPrefixOf x (z:zs) then y ++ replace x y (drop (length x) (z:zs)) else z: (replace x y zs)
and
replace' :: String -> [String] -> String -> String:
replace' x y [] = []
replace' x [] (z:zs) = []
replace' x y (z:zs) = if isPrefixOf x (z:zs) then concat (take 1 y) ++ replace' x (drop 1 y) (drop (length x) (z:zs)) else z: (replace' x y zs)
I just dont know how to begin with this replaceList function tho, the only thing that might actually be useful that I've found so far is a function that replaces the n'th element in a list. But I cant seem to figure out how to put it to use in this case:
replace :: Int -> a -> [a] -> [a]
replace n a [] = []
replace 0 a (x:xs) = a : xs
replace n a (x:xs) = x : replace (n-1) a xs
well hopefully one of you can help me out! Thanks in advance :)
I would suggest a different type than
listReplace :: [String] -> [String] -> String -> String
What would happen if one called
listReplace ["one", "two"] ["een"] "I have two problems"
the substring "two" would be found, but there's no replacement for it provided.
Rather use
listReplace :: [(String, String)] -> String -> String
so that you are guaranteed that there are always exactly as many replacement strings as patterns you search for.
A simple implementation could then use
find :: (a -> Bool) -> [a] -> Maybe a
from Data.List to check if any of the provided patterns is a prefix of the remaining input,
listReplace _ "" = ""
listReplace replacements string#(c:cs)
= case find ((`isPrefixOf` string) . fst) replacements of
Just (pat,rep) -> rep ++ listReplace replacements (drop (length pat) string)
Nothing -> c : listReplace replacements cs
This easy solution is not very efficient - that would require a more complicated algorithm - and it doesn't detect if one of the patterns to be replaced is a prefix of another, so that if the shorter pattern comes before the longer in the list, the longer pattern would never be used, even if it should be. That can be dealt with by sorting the list of replacements, for example descending by lexicographical order, before calling the replace function.
My suggestion would be to use a somewhat different intermediate datastructure when processing the string that you want edited. Here is a solution that uses tries.
Preliminaries
import Data.Map (Map)
import qualified Data.Map as M
Tries
Here is a simple datatype of tries:
data Trie = Trie (Maybe String) (Map Char Trie)
Tries are constructed from the empty trie and a function for inserting key/value bindings into an existing trie:
empty :: Trie
empty = Trie Nothing M.empty
insert :: String -> String -> Trie -> Trie
insert [] val (Trie _ tries) = Trie (Just val) tries
insert (c : cs) val (Trie mbVal tries) = case M.lookup c tries of
Nothing -> Trie mbVal (M.insert c (insert cs val empty) tries)
Just trie -> Trie mbVal (M.insert c (insert cs val trie) tries)
Matching
With tries, matching reduces to recursing over the input string while traversing the trie. When a match is found, the corresponding replacement value is returned together with the remaining part of the input string (so that it can be subjected to further replacements):
match :: Trie -> String -> Maybe (String, String)
match (Trie (Just val) _ ) s = Just (val, s)
match (Trie Nothing _ ) [] = Nothing
match (Trie Nothing tries) (c : cs) = case M.lookup c tries of
Nothing -> Nothing
Just trie -> match trie cs
Note that this function is greedy in the sense that it gives preference to the shortest match if multiple matches are possible. Adapting it so that it picks the longest match instead (and, hence, implements the "maximal-munch" principle) is not too hard.
Replacement
Replacing occurrences of search words by their corresponding replacements can be implemented by looking for a match in the input string: if a match is found, the replacement is put into the output string and we continue processing with the unmatched part of the string. If no match is found, we keep the head of the input string and proceed with the tail.
replace :: Trie -> String -> String
replace trie = go
where
go [] = []
go s#(c : cs) = case match trie s of
Nothing -> c : go cs
Just (s', s'') -> s' ++ go s''
Bringing it all together
Your required function listReplace is now almost trivial:
listReplace :: [String] -> [String] -> String -> String
listReplace keys vals = replace trie
where
trie = foldr ($) empty (zipWith insert keys vals)
As you see, the part that you refer to as "tricky" is easily realised by "zipping" the two list arguments.
Example
Here is a simple example (inspired by L. Peter Deutsch):
> let s = "to err is human; to forgive, divine"
> listReplace ["err", "forgive"] ["iterate", "recurse"] s
"to iterate is human; to recurse, divine"

Swap characters between strings Haskell

if i say i have two strings or character lists,
list1 = ["c","a","t"]
list2 = ["d","o","g"]
and if i read a string using Input Output "ct" and pass it to the function,the function should return "dg".
Please give me any idea about such a function.
I would consider taking those two lists, zipping them together, use Data.Map.fromList to create a lookup Map, then map over the input String and use the Map to work out what to replace them with.
I'll first assume list1 and list2 have type [Char] (i.e. String), since that's what your text seems to indicate (your code has them as [String]s -- if you really want this, see the generalized version in the addendum).
If you zip the two lists, you end up with a list of pairs indicating how to translate characters. In your example, zip list1 list2 = [('c','d'), ('a','o'), ('t','g')]. We'll call this our lookup list. Now consider the function lookup:
lookup :: Eq a => a -> [(a, b)] -> Maybe b
In our case, we can specialize this to
lookup :: Char -> [(Char, Char)] -> Maybe Char
so we have something that takes a character and a lookup list and returns a substituted character if the input character is in the lookup list (otherwise a Nothing). Now we just need to glue the things we've found together: We essentially need to map \c -> lookup c lookupList (more elegantly written as flip lookup) over the input string while throwing out any characters not found in the lookup list. Well, enter mapMaybe:
mapMaybe :: (a -> Maybe b) -> [a] -> [b]
It does exactly what we want. Now your function can be written as
replace :: String -> String -> String -> String
replace list1 list2 = mapMaybe ((flip lookup) (zip list1 list2))
You'll need to import Data.Maybe.
Addendum, for when you understand the above: Observe how what we did above had nothing to do with the fact that we were working with lists of characters. We could do everything above with lists of any type for which equality makes sense, i.e. for (lists of) any type which is an instance of the Eq typeclass (cf the signature of lookup above). Moreover, we don't have to translate from that type to itself -- for example, each character above could be sent to say, an integer! So really, we can write
replace :: (Eq a) => [a] -> [b] -> [a] -> [b]
replace list1 list2 = mapMaybe ((flip lookup) (zip list1 list2))
and now our function works as long as list1 is a list of something for which equality makes sense. Replacement of characters just becomes a special case.
A quick example:
> replace "cat" "dog" "ct"
"dg"
> replace "cat" [1,2,3] "ct"
[1,3]
For two string you may do as follows:
patt :: String -> String -> String -> String
patt (x : xs) (y : ys) p'#(p : ps)
| p == x = y : patt xs ys ps
| otherwise = patt xs ys p'
patt _ _ [] = []
main :: IO ()
main = do
putStrLn $ patt "cat" "dog" "ct"

Resources