简体   繁体   English

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

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

So the problem I'm working on matching a pattern to a list, such like this: match "abba" "redbluebluered" -> True or match "abba" "redblueblue" -> False , etc. I wrote up an algorithm that works, and I think it's reasonable understandable, but I'm not sure if there's a better way to do this without explicit recursion. 所以我正在努力将模式与列表匹配,例如: 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)

I would call this like match "abba" "redbluebluered" empty . 我会称之为match "abba" "redbluebluered" empty The actual algorithm is simple. 实际的算法很简单。 The map contains the patterns already matched. 地图包含已匹配的模式。 At the end it is [a - > "red", b -> "blue"]. 最后是[a - >“red”,b - >“blue”]。 If the next pattern is one we've seen before, just try matching it and recurse down if we can. 如果下一个模式是我们之前看到过的模式,那么只需尝试匹配它,如果可以的话,可以向下递减。 Otherwise fail and return false. 否则失败并返回false。

If the next pattern is new, just try mapping the new pattern to every single prefix in the string and recursing down. 如果下一个模式是新的,只需尝试将新模式映射到字符串中的每个前缀并递归。

This is very similar to a parsing problem, so let's take a hint from the parser monad: 这与解析问题非常相似,所以让我们从解析器monad中获取提示:

  • match should return a list of all of the possible continuations of the parse match应返回解析的所有可能延续的列表
  • if matching fails it should return the empty list 如果匹配失败,则应返回空列表
  • the current set of assignments will be state that has to carried through the computation 当前的分配集将是必须通过计算进行的状态

To see where we are headed, let's suppose we have this magic monad. 为了了解我们的目标,让我们假设我们有这个神奇的单子。 Attempting to match "abba" against a string will look like: 尝试将“abba”与字符串匹配将如下所示:

matchAbba = do
  var 'a'
  var 'b'
  var 'b'
  var 'a'
  return ()  -- or whatever you want to return

test = runMatch matchAbba "redbluebluered"

It turns out this monad is the State monad over the List monad. 事实证明,这个monad是List Monad上的State monad。 The List monad provides for backtracking and the State monad carries the current assignments and input around. List monad提供回溯,State monad包含当前的赋值和输入。

Here's the code: 这是代码:

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

Try, for instance: 试试,例如:

matches "ab" "xyz"
-- [fromList [('a',"x"),('b',"y")],fromList [('a',"x"),('b',"yz")],fromList [('a',"xy"),('b',"z")]]

Another thing to point out is that code which transforms a string like "abba" to the monadic value do var'a'; var'b'; var 'b'; var 'a' 另一点需要指出的是,将像“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' is simply: do var'a'; var'b'; var 'b'; var 'a'简单地说:

sequence $ map var "abba"

Update: As @Sassa NF points out, to match the end of input you'll want to define: 更新:正如@Sassa NF指出的那样,要匹配您要定义的输入结束:

matchEnd :: StateT (Assigns,String) [] ()
matchEnd = do
  (assigns,input) <- get
  guard $ null input

and then insert it into the monad: 然后将其插入monad:

        monad = do sequence $ map var pattern
                   matchEnd
                   (assigns,_) <- get
                   return assigns

I would like to modify your signature and return more than Bool . 我想修改你的签名并返回超过Bool Your solution then becomes: 您的解决方案将成为:

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

Using the known trick of folding to produce a function we can obtain: 使用已知的折叠技巧来生成函数,我们可以获得:

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

Here match is the function folding all keys to produce a function taking a Map and a string of a , which returns a Map of matches of the keys to substrings. 在这里match是一种折叠所有密钥产生函数取功能Map和串a ,它返回一个Map的钥匙串的匹配。 The condition for matching the string of a in its entirety is tracked by the last function applied by foldr - end . 为的字符串匹配的条件a以其全文通过由施加的最后功能跟踪foldr - end If end is supplied with a map and an empty string of a , then the match is successful. 如果end与地图和一个空字符串供给a ,则匹配成功。

The list of keys is folded using function f , which is given four arguments: the current key, the function g matching the remainder of the list of keys (ie either f folded, or end ), the map of keys already matched, and the remainder of the string of a . 使用函数f折叠键列表,函数f有四个参数:当前键,与键列表的其余部分匹配的函数g (即f折叠或end ),已匹配的键映射,以及的字符串的其余a If the key is already found in the map, then just strip the prefix and feed the map and the remainder to g . 如果已在地图中找到该键,则只需删除前缀并将地图和剩余部分提供给g Otherwise, try to feed the modified map and remainder of a s for different split combinations. 否则,尝试进料的改性地图和其余a S代表不同分裂组合。 The combinations are tried lazily as long as g produces Nothing in h . 只要gh产生Nothing ,就可以懒惰地尝试这些组合。

Here is another solution, more readable, I think, and as inefficient as other solutions: 这是另一个解决方案,我认为更具可读性,并且与其他解决方案一样效率低下:

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
    ]

The idea is simple: instead of having a Map , store both patterns and matched substrings in a list. 这个想法很简单:不是使用Map ,而是将模式和匹配的子串存储在列表中。 So when we encounter a pattern ( Left p ), then we substitute all occurrences of this pattern with a substring and call match' recursively with this substring being striped, and repeat this for each substring, that belongs to inits of a processed string. 所以,当我们遇到一个模式( Left p ),那么我们就代替这个模式所有出现的子串并调用match'这一子被条纹递归,并重复这一过程,每个子,属于inits一个处理字符串。 If we encounter already matched substring ( Right s ), then we just try to strip this substring, and call match' recursively on a successive attempt or return False otherwise. 如果我们遇到已匹配的子字符串( Right s ),那么我们只是尝试去除这个子字符串,并在连续尝试时递归调用match'否则返回False

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

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