所以我正在努力将模式与列表匹配,例如:
match "abba" "redbluebluered" -> True
或
match "abba" "redblueblue" -> False
等等。我编写了一个有效的算法,我认为这是合理可行的,但我不确定在没有显式递归的情况下是否有更好的方法可以做到这一点。
import Data.HashMap.Strict as M
match :: (Eq a, Eq k, Hashable k) => [k] -> [a] -> HashMap k [a] -> Bool
match [] [] _ = True
match [] _ _ = False
match _ [] _ = False
match (p:ps) s m =
case M.lookup p m of
Just v ->
case stripPrefix v s of
Just post -> match ps post m
Nothing -> False
Nothing -> any f . tail . splits $ s
where f (pre, post) = match ps post $ M.insert p pre m
splits xs = zip (inits xs) (tails xs)
我会称之为match "abba" "redbluebluered" empty
。实际的算法很简单。地图包含已匹配的模式。最后它是[a - >; “红色”,b - > “蓝色”]。如果下一个模式是我们之前看到过的模式,那么只需尝试匹配它,如果可以的话,可以向下递减。否则失败并返回false。
如果下一个模式是新模式,只需尝试将新模式映射到字符串中的每个前缀并递归。
答案 0 :(得分:6)
这与解析问题非常相似,所以让我们从解析器monad中提取一些提示:
match
应该返回解析的所有可能延续的列表为了了解我们的目标,让我们假设我们有这个神奇的单子。试图匹配" abba"对一个字符串看起来像:
matchAbba = do
var 'a'
var 'b'
var 'b'
var 'a'
return () -- or whatever you want to return
test = runMatch matchAbba "redbluebluered"
事实证明,这个monad是List monad上的State monad。 List monad提供回溯,State monad包含当前的赋值和输入。
以下是代码:
import Data.List
import Control.Monad
import Control.Monad.State
import Control.Monad.Trans
import Data.Maybe
import qualified Data.Map as M
import Data.Monoid
type Assigns = M.Map Char String
splits xs = tail $ zip (inits xs) (tails xs)
var p = do
(assigns,input) <- get
guard $ (not . null) input
case M.lookup p assigns of
Nothing -> do (a,b) <- lift $ splits input
let assigns' = M.insert p a assigns
put (assigns', b)
return a
Just t -> do guard $ isPrefixOf t input
let inp' = drop (length t) input
put (assigns, inp')
return t
matchAbba :: StateT (Assigns, String) [] Assigns
matchAbba = do
var 'a'
var 'b'
var 'b'
var 'a'
(assigns,_) <- get
return assigns
test1 = evalStateT matchAbba (M.empty, "xyyx")
test2 = evalStateT matchAbba (M.empty, "xyy")
test3 = evalStateT matchAbba (M.empty, "redbluebluered")
matches :: String -> String -> [Assigns]
matches pattern input = evalStateT monad (M.empty,input)
where monad :: StateT (Assigns, String) [] Assigns
monad = do sequence $ map var pattern
(assigns,_) <- get
return assigns
尝试,例如:
matches "ab" "xyz"
-- [fromList [('a',"x"),('b',"y")],fromList [('a',"x"),('b',"yz")],fromList [('a',"xy"),('b',"z")]]
要指出的另一件事是转换字符串的代码,例如&#34; abba&#34; monadic值do var'a'; var'b'; var 'b'; var 'a'
就是:
sequence $ map var "abba"
更新:正如@Sassa NF指出的那样,为了匹配你想要定义的输入结束:
matchEnd :: StateT (Assigns,String) [] ()
matchEnd = do
(assigns,input) <- get
guard $ null input
然后将其插入monad:
monad = do sequence $ map var pattern
matchEnd
(assigns,_) <- get
return assigns
答案 1 :(得分:1)
我想修改您的签名并返回Bool
以上的内容。然后你的解决方案变成:
match :: (Eq a, Ord k) => [k] -> [a] -> Maybe (M.Map k [a])
match = m M.empty where
m kvs (k:ks) vs@(v:_) = let splits xs = zip (inits xs) (tails xs)
f (pre, post) t =
case m (M.insert k pre kvs) ks post of
Nothing -> t
x -> x
in case M.lookup k kvs of
Nothing -> foldr f Nothing . tail . splits $ vs
Just p -> stripPrefix p vs >>= m kvs ks
m kvs [] [] = Just kvs
m _ _ _ = Nothing
使用已知的折叠技巧来产生一个函数,我们可以得到:
match ks vs = foldr f end ks M.empty vs where
end m [] = Just m
end _ _ = Nothing
splits xs = zip (inits xs) (tails xs)
f k g kvs vs = let h (pre, post) = (g (M.insert k pre kvs) post <|>)
in case M.lookup k kvs of
Nothing -> foldr h Nothing $ tail $ splits vs
Just p -> stripPrefix p vs >>= g kvs
这里match
是折叠所有键的函数,用于生成一个带有Map
和a
字符串的函数,该函数返回Map
个匹配项的匹配项子。完整匹配a
字符串的条件由foldr
- end
应用的最后一个函数进行跟踪。如果end
附带地图和空字符串a
,则匹配成功。
使用函数f
折叠键列表,函数g
有四个参数:当前键,与键列表的其余部分匹配的函数f
(即end
折叠,或a
),已匹配的键映射,以及g
字符串的其余部分。如果已在地图中找到该密钥,则只需删除前缀并将地图和剩余部分提供给a
。否则,尝试为不同的拆分组合提供修改后的地图和剩余的g
s。只要Nothing
在h
中生成{{1}},就会懒惰地尝试这些组合。
答案 2 :(得分:0)
这是另一个解决方案,我认为更具可读性,并且与其他解决方案一样效率低下:
import Data.Either
import Data.List
import Data.Maybe
import Data.Functor
splits xs = zip (inits xs) (tails xs)
subst :: Char -> String -> Either Char String -> Either Char String
subst p xs (Left q) | p == q = Right xs
subst p xs q = q
match' :: [Either Char String] -> String -> Bool
match' [] [] = True
match' (Left p : ps) xs = or [ match' (map (subst p ixs) ps) txs
| (ixs, txs) <- tail $ splits xs]
match' (Right s : ps) xs = fromMaybe False $ match' ps <$> stripPrefix s xs
match' _ _ = False
match = match' . map Left
main = mapM_ (print . uncurry match)
[ ("abba" , "redbluebluered" ) -- True
, ("abba" , "redblueblue" ) -- False
, ("abb" , "redblueblue" ) -- True
, ("aab" , "redblueblue" ) -- False
, ("cbccadbd", "greenredgreengreenwhiteblueredblue") -- True
]
这个想法很简单:不是拥有Map
,而是在列表中存储模式和匹配的子串。因此,当我们遇到一个模式(Left p
)时,我们用子字符串替换所有出现的这个模式,并以条带化的子字符串递归调用match'
,并对属于{的每个子字符串重复此操作。已处理字符串的{1}}。如果我们遇到已经匹配的子字符串(inits
),那么我们只是尝试去除这个子字符串,并在连续尝试时递归调用Right s
,否则返回match'
。