有没有办法在这个算法中不使用显式递归?

时间:2014-11-15 21:44:49

标签: haskell recursion coding-style fold

所以我正在努力将模式与列表匹配,例如: match "abba" "redbluebluered" -> Truematch "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。

如果下一个模式是新模式,只需尝试将新模式映射到字符串中的每个前缀并递归。

3 个答案:

答案 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是折叠所有键的函数,用于生成一个带有Mapa字符串的函数,该函数返回Map个匹配项的匹配项子。完整匹配a字符串的条件由foldr - end应用的最后一个函数进行跟踪。如果end附带地图和空字符串a,则匹配成功。

使用函数f折叠键列表,函数g有四个参数:当前键,与键列表的其余部分匹配的函数f(即end折叠,或a),已匹配的键映射,以及g字符串的其余部分。如果已在地图中找到该密钥,则只需删除前缀并将地图和剩余部分提供给a。否则,尝试为不同的拆分组合提供修改后的地图和剩余的g s。只要Nothingh中生成{{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'