用列表中的字符串替换字符串
Posted
技术标签:
【中文标题】用列表中的字符串替换字符串【英文标题】:Replacing strings with strings from a list 【发布时间】:2013-05-01 05:13:42 【问题描述】:我正在尝试编写一个函数,该函数接受一个搜索词列表、一个替换词列表和一个将使用它们的字符串。
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)
我只是不知道如何从这个 replaceList 函数开始,到目前为止,我发现的唯一可能真正有用的是替换列表中第 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
希望你们中的一个可以帮助我!在此先感谢:)
【问题讨论】:
【参考方案1】:我会建议一种不同的类型
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
这个简单的解决方案效率不高 - 这需要更复杂的算法 - 它不会检测要替换的模式之一是否是另一个模式的前缀,因此如果较短的模式出现在较长的模式之前列表中,永远不会使用更长的模式,即使它应该使用。这可以通过在调用替换函数之前对替换列表进行排序来处理,例如按字典顺序降序。
【讨论】:
我认为你放错了 as-pattern @Daniel Fischer 首先:感谢您的回答。我在 3 周前才开始编程,所以我可能需要多一点时间才能完全理解你写的所有内容。当尝试使用你的函数时,我在“Nothing -> c”tho 上得到一个解析错误,因为我没有使用任何这样的语法,但我不知道如何解决它。 “(drop(length pat))字符串”中似乎还缺少一个“)”。无论如何,感谢您的帮助。我要开始研究 Maybe-Expressions 以进一步了解您的代码:) @user2299050 是的,缺少右括号。现在添加它。这应该可以解决解析错误。【参考方案2】:我的建议是在处理要编辑的字符串时使用稍微不同的中间数据结构。这是一个使用tries的解决方案。
预赛
import Data.Map (Map)
import qualified Data.Map as M
尝试
这是一个简单的数据类型的尝试:
data Trie = Trie (Maybe String) (Map Char Trie)
Tries 由空 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)
匹配
在遍历 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"
【讨论】:
感谢您的回答。我以前从未听说过这种可能性。我现在就试试看!以上是关于用列表中的字符串替换字符串的主要内容,如果未能解决你的问题,请参考以下文章