我正在尝试编写一个函数,该函数包含一个搜索词列表,一个替换词列表和一个将使用这些词的字符串。
listReplace :: [String] -> [String] -> String -> String
棘手的部分是如果拟合搜索词是第n个,那么应该使用第n个替换词。此外,当使用替换词时,如果它本身实际上是一个搜索词,则不应该用不同的替换词替换它。我已经为
编写了这些函数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)
和
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)
我只是不知道如何从这个替换列表函数开始,到目前为止我发现的唯一可能实际有用的是一个替换列表中第n个元素的函数。但我似乎无法弄清楚如何在这种情况下使用它:
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
希望你们其中一个可以帮助我!在此先感谢:)
答案 0 :(得分:7)
我建议采用与
不同的类型listReplace :: [String] -> [String] -> String -> String
如果打电话
会发生什么listReplace ["one", "two"] ["een"] "I have two problems"
可以找到子字符串“two”,但是它没有替代它。
而是使用
listReplace :: [(String, String)] -> String -> String
这样可以确保您搜索的模式总是与替换字符串完全相同。
然后可以使用简单的实现
find :: (a -> Bool) -> [a] -> Maybe a
从Data.List
检查是否有任何提供的模式是剩余输入的前缀
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
这个简单的解决方案效率不高 - 需要更复杂的算法 - 并且它不会检测其中一个要替换的模式是否是另一个模式的前缀,因此如果更短的模式出现在更长的模式之前列表,永远不会使用更长的模式,即使它应该是。这可以通过在调用替换函数之前对替换列表进行排序来处理,例如按字典顺序降序。
答案 1 :(得分:4)
我的建议是在处理您想要编辑的字符串时使用稍微不同的中间数据结构。这是一个使用tries的解决方案。
import Data.Map (Map)
import qualified Data.Map as M
这是一个简单的尝试数据类型:
data Trie = Trie (Maybe String) (Map Char Trie)
尝试是从空的trie和用于将键/值绑定插入现有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)
使用try,匹配会减少在遍历trie时递归输入字符串。找到匹配项后,将返回相应的替换值以及输入字符串的剩余部分(以便可以进一步替换):
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
请注意,此功能是贪婪的,因为如果可能有多个匹配,它会优先选择最短匹配。改编它以便它选择最长的匹配(并因此实现"maximal-munch"原则)并不太难。
通过查找输入字符串中的匹配项,可以实现替换匹配替换的搜索词的出现次数:如果找到匹配项,则将替换项放入输出字符串中,然后继续使用字符串中不匹配的部分进行处理。如果找不到匹配项,我们保留输入字符串的头部并继续尾部。
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''
您所需的功能listReplace
现在几乎是微不足道的:
listReplace :: [String] -> [String] -> String -> String
listReplace keys vals = replace trie
where
trie = foldr ($) empty (zipWith insert keys vals)
如您所见,您称之为“棘手”的部分很容易通过“压缩”两个列表参数来实现。
这是一个简单的例子(灵感来自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"