My suggestion would be to use a slightly different intermediate data structure when processing the string you want to edit. Here is the solution that uses trying .
Qualifiers
import Data.Map (Map) import qualified Data.Map as M
Is trying
Here is a simple type of attempt:
data Trie = Trie (Maybe String) (Map Char Trie)
Traffic jams are built from an empty trie and a function to insert 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)
Conformity
When trying to match, it comes down to recursion along the input string when passing a trie. When a match is found, the corresponding return value is returned along with the rest of the input string (so that it can be further replaced):
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 selects the longest match (and therefore implements the maximal-munch principle) is not too difficult.
Replacement
Replacing the appearance of search words with their corresponding substitutions can be implemented by searching for a match in the input line: if a match is found, the replacement is placed in the output line, and we continue processing with the unsurpassed part of the line. If no match is found, we save the initial input line and start from 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''
Putting it all together
Your required listReplace function listReplace now almost trivial:
listReplace :: [String] -> [String] -> String -> String listReplace keys vals = replace trie where trie = foldr ($) empty (zipWith insert keys vals)
As you can see, the part that you call โcomplexโ is easily implemented by โzippingโ 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"