将简单的命令式算法转换为功能式

时间:2013-10-04 12:56:49

标签: python haskell functional-programming imperative-programming

我最近制作了一个小算法,从一段代码中删除函数参数,只保留最外面的函数。
我发现这个算法很容易以命令的方式设计 但是,我对函数式编程非常感兴趣,我想知道如何以功能的方式完成同样的事情。

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

我在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:]

5 个答案:

答案 0 :(得分: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会浮现在脑海中;但是,我们是否保留或删除一个角色取决于某些状态(我们的开放/封闭的数量)。因此monadic过滤器函数filterM是合适的,我们可以使用State monad来抽象我们的打开/关闭计数的管道。

如果您想了解有关上述工作方式的详细信息,请与我们联系。

答案 1 :(得分: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,这就会很好地消失。

答案 2 :(得分:2)

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

它使用一个带有嵌套计数的辅助函数。因此,0表示不在括号内,1表示在1对内等。如果n> 0然后我们丢弃字符。如果我们相应地按下括号增量/减量n。

辅助函数基本上是该算法的逐个案例描述。如果将它用于真实的话,你可以把它从#34;其中&#34;子句。

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

答案 3 :(得分: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"

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

答案 4 :(得分:0)

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

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”循环):我猜你应该能够通过使用内部循环来摆脱“开始”和“结束”下一个“)”而不是在主循环中执行所有操作(这对原始Python程序也有效)。

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