我最近制作了一个小算法,从一段代码中删除函数参数,只保留最外面的函数。
我发现这个算法很容易以命令的方式设计
但是,我对函数式编程非常感兴趣,我想知道如何以功能的方式完成同样的事情。
如果您能告诉我这样的算法是如何工作的,那将对我非常有帮助,因此我可以更好地了解函数式编程的工作原理。此外,我想知道在设计算法时你的思考过程是什么。
我在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:]
答案 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循环)。也就是说,重要的一点是,如果你真的想要,你仍然可以编写算法的原始,命令式版本。