繁体   English   中英

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

[英]Translating a simple imperative algorithm to functional style

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

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

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

这是我的版本:

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过滤器函数filterM是合适的,我们可以使用State monad来抽象我们的打开/关闭计数的管道。

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

好吧,我更喜欢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多一点就会很好地消失。

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

它使用一个辅助函数来获取嵌套计数。 所以,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

一种方法是从迭代转换为递归样式。 换句话说,不是使用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代码是“从代码片段中删除函数参数”的正确方法,我只是回答“我如何翻译这段代码”的问题。

在进行这种转换时我喜欢的一个技巧是将尾调用视为一种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循环) )。 也就是说,重要的一点是,如果你真的想要,你仍然可以编写算法的原始,命令式版本。

暂无
暂无

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

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