确定Haskell中的匹配括号

时间:2012-04-20 09:09:09

标签: haskell parentheses

假设我有一个字符串,如:

abc(def(gh)il(mn(01))afg)lmno(sdfg*)

如何确定第一个的匹配括号? (意思是(def(gh)il(mn(01))afg)

我试图通过计算开放括号的数量直到第一个')'来创建一个between函数,但它不适用于像这样的字符串。

2 个答案:

答案 0 :(得分:7)

您可以使用一个简单地遍历字符串的函数,同时跟踪一组开括号的索引。无论何时遇到右括号,您都知道它与堆栈顶部的索引匹配。

例如:

parenPairs :: String -> [(Int, Int)]
parenPairs = go 0 []
  where
    go _ _        []         = []
    go j acc      ('(' : cs) =          go (j + 1) (j : acc) cs
    go j []       (')' : cs) =          go (j + 1) []        cs -- unbalanced parentheses!
    go j (i : is) (')' : cs) = (i, j) : go (j + 1) is        cs
    go j acc      (c   : cs) =          go (j + 1) acc       cs

此函数返回属于匹配括号对的所有索引对的列表。

将该函数应用于示例字符串会给出:

> parenPairs "abc(def(gh)il(mn(01))afg)lmno(sdfg*)"
[(7,10),(16,19),(13,20),(3,24),(29,35)]

您感兴趣的左括号出现在索引3处。返回的列表显示匹配的右括号将在索引24处找到。

以下函数为您提供字符串的所有正确括号段:

parenSegs :: String -> [String]
parenSegs s = map (f s) (parenPairs s)
  where
    f s (i, j) = take (j - i + 1) (drop i s)

例如:

> parenSegs "abc(def(gh)il(mn(01))afg)lmno(sdfg*)"
["(gh)","(01)","(mn(01))","(def(gh)il(mn(01))afg)","(sdfg*)"]

根据Frerich Raabe的建议,我们现在也可以编写一个只返回最左边段的函数:

firstParenSeg :: String -> String
firstParenSeg s = f s (minimum (parenPairs s))
  where
    f s (i, j) = take (j - i + 1) (drop i s)

例如:

> firstParenSeg "abc(def(gh)il(mn(01))afg)lmno(sdfg*)"
"(def(gh)il(mn(01))afg)"

请注意,如果输入字符串不包含至少一对匹配的括号,firstParenSeg将失败。

最后,parenPairs函数的一个小的改编让它在不平衡的括号上失败:

parenPairs' :: String -> [(Int, Int)]
parenPairs' = go 0 []
  where
    go _ []        []         = []
    go _ (_ : _ )  []         = error "unbalanced parentheses!"
    go j acc       ('(' : cs) =          go (j + 1) (j : acc) cs
    go j []        (')' : cs) = error "unbalanced parentheses!"
    go j (i : is)  (')' : cs) = (i, j) : go (j + 1) is        cs
    go j acc       (c   : cs) =          go (j + 1) acc       cs

答案 1 :(得分:2)

使用帮助器go功能的简单新手解决方案。

brackets :: String -> String
brackets string = go string 0 False
  where go (s:ss) 0 False | s /= '(' = go ss 0 False
        go ('(':ss) 0 False = '(' : go ss 1 True
        go (')':_) 1 True = ")"
        go (')':ss) n True = ')' : go ss (n-1) True
        go ('(':ss) n True = '(' : go ss (n+1) True
        go (s:ss) n flag = s : go ss n flag 
        go "" _ _ = ""

我们的想法是记住每个Char的一些开括号的计数器。当该计数器等于1且Char等于)时 - 是时候返回所需的字符串了。

> brackets "abc(def(gh)il(mn(01))afg)lmno(sdfg*)"
"(def(gh)il(mn(01))afg)"

注意,此函数将为非平衡字符串返回带有未闭合括号的字符串,如下所示:

> brackets "a(a(a"
"(a(a"

可以通过另一种模式匹配条件来避免。


<强> UPD

更具可读性的解决方案是balancedSubstring函数:: String -> Maybe String,如果括号是平衡的,则返回Just需要子字符串,而在其他情况下需要Nothing

brackets :: String -> String
brackets string = go string 0 False
  where go (s:ss) 0 False | s /= '(' = go ss 0 False
        go ('(':ss) 0 False = '(' : go ss 1 True
        go (')':_) 1 True = ")"
        go (')':ss) n True = ')' : go ss (n-1) True
        go ('(':ss) n True = '(' : go ss (n+1) True
        go (s:ss) n flag = s : go ss n flag
        go "" _  _ = ""

isBalanced :: String -> Bool
isBalanced string = go string 0
  where go ('(':ss) n = go ss (n+1)
        go (')':ss) n | n > 0 = go ss (n-1)
        go (')':_ ) n | n < 1 = False
        go (_:ss) n = go ss n
        go "" 0 = True
        go "" _ = False

balancedSubstring :: String -> Maybe String
balancedSubstring string | isBalanced string = Just $ brackets string
balancedSubstring string | otherwise         = Nothing

所以现在balancedSubstring函数的结果更容易理解:

> balancedSubstring "abc(def(gh)il(mn(01))afg)lmno(sdfg*)"
Just "(def(gh)il(mn(01))afg)"

> balancedSubstring "a(a(a"
Nothing