4

我最近做了一个小算法,从一段代码中去掉函数参数,只保留最外层的函数。
我发现这个算法很容易以命令式的方式设计。
但是,我对函数式编程真的很感兴趣,我想知道你将如何以函数式的方式完成同样的事情。

如果你能告诉我这样的算法是如何工作的,那对我很有帮助,所以我可能会更好地了解函数式编程的工作原理。另外我想知道你在设计算法时的思考过程是什么。

我在 Python 中制作了命令式版本,但您的答案不一定是在 Python 中;haskell 或任何其他语言也可以。

这是它的作用(将字符串作为输入并返回一个字符串):

"foo(a.d, b.e.fi()).go(sd, ds())"     -- returns -->  "foo().go()"
"foo(a, b).bar().fuu"                 -- returns -->  "foo().bar().fuu"
"foo.bar"                             -- returns -->  "foo.bar"

这是我的命令式代码:

def get_rid_of_arguments(text):
    i, start, end = 0, 0, 0
    result = ""
    for j, c in enumerate(text):
        if c == '(':
            if i == 0:
                start = j
                result += text[end:start]
            i += 1
        elif c == ')':
            i -= 1
            if i == 0:
                end = j + 1
                result += '()'
    return result + text[end:]
4

5 回答 5

7

这是我的版本:

import Control.Monad
import Control.Monad.State

-- filter `str` with the "stateful" monadic predicate function `handleChar`, 
-- with an initial state of 0
getRidOfArguments :: String -> String
getRidOfArguments str = filterM handleChar str `evalState` 0

handleChar :: Char -> State Int Bool
handleChar '(' = modify (+1) >> gets (<= 1)
handleChar ')' = modify (max 0 . subtract 1) >> gets (== 0)
handleChar _   = gets (== 0)

我的思考过程是:我们正在过滤一个列表,所以我想到了filter;然而,我们是否保留或删除一个字符取决于某些状态(我们打开/关闭括号的计数)。所以单子过滤器功能filterM是合适的,我们可以使用State单子来抽象我们的打开/关闭计数的管道。

如果您想了解有关上述工作原理的更多详细信息,请告诉我。

于 2013-10-04T15:03:01.643 回答
3

好吧,我更喜欢 jberryman 的解决方案,但如果你想避免使用 monad,试试这个

stateFilter :: (s -> a -> (s, Bool)) -> [a] -> s -> [a]
stateFilter f as state = snd $ foldr stepper (state, []) as
  where stepper (state, filtered) a =
          let (state', b) = f state a in
             if b then (state', a:filtered) else (state', filtered)

这使状态通过我们的过滤函数运行,我们只返回当前值是否为真以及我们的新状态。那么你的代码就是

-- # Converted from jberrymans lovely answer
handleChar :: Int -> Char -> (Int, Bool)
handleChar s '(' = (max 0 (s - 1), s <= 1)
handleChar s ')' = (s +1, s <= 0)
handleChar s _   = (s, s == 0)

现在状态是明确的(而不是那么漂亮),但可能更容易理解。

clean str = stateFilter handleChar str 0

现在这很好而且实用,整个事情归结为折叠字符串。有一些管道正在跟踪状态,但是一旦你开始更多地了解 Haskell,这就会很好地消失。

于 2013-10-04T16:16:48.183 回答
2

已经有很多答案了,但只是为了添加到列表中,这里有一个非常简单的功能风格。

它使用一个接受嵌套计数的辅助函数。因此,0 表示不在括号内,1 表示在 1 对内等。如果 n > 0,则我们删除字符。如果我们相应地达到一个括号递增/递减 n 。

辅助函数基本上是对该算法的逐案描述。如果真的使用它,你会把它挂在“where”子句上。

skipBrackets :: String -> String
skipBrackets s = skipper s 0

skipper :: String -> Int -> String

skipper [] _ = []
skipper ('(':xs) 0 = '(' : skipper xs 1
skipper (')':xs) 1 = ')' : skipper xs 0

skipper ('(':xs) n = skipper xs (n + 1)
skipper (')':xs) n = skipper xs (n - 1)

skipper (x:xs) 0 = x : skipper xs 0
skipper (x:xs) n = skipper xs n
于 2013-10-05T07:57:16.143 回答
1

一种方法是将迭代样式转换为递归样式。换句话说,不是使用for循环多次执行某些代码,而是通过让函数调用自身来实现相同的目的。

Haskell 中的一个例子:

get_rid_of_arguments [] = []
get_rid_of_arguments ('(':xs) = "()" ++ (get_rid_of_arguments $ dropper xs)
get_rid_of_arguments (x:xs) = x : get_rid_of_arguments xs

dropper [] = []
dropper (')':xs) = xs
dropper ('(':xs) = dropper $ dropper xs
dropper (_:xs) = dropper xs

main = do
    print $ get_rid_of_arguments "foo(a.d, b.e.fi()).go(sd, ds())" == "foo().go()"
    print $ get_rid_of_arguments "foo(a, b).bar().fuu" == "foo().bar().fuu"
    print $ get_rid_of_arguments "foo.bar" == "foo.bar"

PS你的原始python代码和这个Haskell代码都不是“从代码片段中去除函数参数”的正确方法,我只是在回答“我如何翻译这个代码”的问题。

于 2013-10-04T16:06:38.733 回答
0

在进行这种转换时,我喜欢的一个技巧是将尾调用视为一种 goto+variable 赋值:

sumn n = addn n 0

addn i acc =
   if i == 0 then
      acc
   else
      addn (i-1) (acc + i)
def sumn(n):
  #lets pretend Python has gotos for now...
  i, acc = n, 0
 acc:
  if i == 0:
     return acc
  else:
     i, acc = (i-1), (acc + i)
     goto acc

在你的情况下,这会翻译有点像

--Haskell pseudocode w/ string slicing
get_rid xs = go 0 0 0 0 ""
  where
    -- "go" is a common name for these tail-recursive loop functions.
    go i j start end result =
      if j < length xs then
         case xs !! j of
           '('  -> 
              if i == 0 then
                go (i+1) (j+1) j end (result ++ (text[end:start]))
              else
                go (i+1) (j+1) start end result
           ')' -> 
              if i == 1 then
                go (i-1) (j+1) start (j+1) (result ++ "()")
              else
                go (i-1) (j+1) start end result
           _ ->
              go i (j+1) start end result
      else
         result ++ text[end:]

最终结果非常难看,因为这仍然是一个基本的命令式算法,需要进行大量的变量赋值。此外,功能版本明确表明您的所有变量都在可用的最大范围内(“go”循环):我想您应该能够通过使用内部循环来摆脱“start”和“end”来查找下一个 ")" 而不是在主循环中执行所有操作(这对原始 Python 程序也有效)。

还有一些小的样式问题,比如仍然在链表上使用索引和切片(它在 Haskell 中的 O(N) 而不是 O(1))以及使用尾递归循环 (gotos) 而不是更结构化的折叠(for 循环)。也就是说,重要的一点是,如果您真的愿意,您仍然可以编写算法的原始、命令式版本。

于 2013-10-04T15:59:56.900 回答