繁体   English   中英

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

[英]Is there any way to not use explicit recursion in this algorithm?

所以我正在努力将模式与列表匹配,例如: 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 - >“red”,b - >“blue”]。 如果下一个模式是我们之前看到过的模式,那么只需尝试匹配它,如果可以的话,可以向下递减。 否则失败并返回false。

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

这与解析问题非常相似,所以让我们从解析器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")]]

另一点需要指出的是,将像“abba”这样的字符串转换为do var'a'; var'b'; var 'b'; var 'a'值的代码do var'a'; var'b'; var 'b'; var 'a' do var'a'; var'b'; var 'b'; var 'a' 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

我想修改你的签名并返回超过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折叠键列表,函数f有四个参数:当前键,与键列表的其余部分匹配的函数g (即f折叠或end ),已匹配的键映射,以及的字符串的其余a 如果已在地图中找到该键,则只需删除前缀并将地图和剩余部分提供给g 否则,尝试进料的改性地图和其余a S代表不同分裂组合。 只要gh产生Nothing ,就可以懒惰地尝试这些组合。

这是另一个解决方案,我认为更具可读性,并且与其他解决方案一样效率低下:

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'这一子被条纹递归,并重复这一过程,每个子,属于inits一个处理字符串。 如果我们遇到已匹配的子字符串( Right s ),那么我们只是尝试去除这个子字符串,并在连续尝试时递归调用match'否则返回False

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM