将递归函数重写为管道函数组合
Posted
技术标签:
【中文标题】将递归函数重写为管道函数组合【英文标题】:Rewrite recursion function as a pipeline functions composition 【发布时间】:2013-04-21 16:40:11 【问题描述】:我正在写作业(CIS194 Haskell 课程)。
我必须将下面的递归函数重写为管道函数(没有明显的递归)。
fun2 :: Integer -> Integer
fun2 1 = 0
fun2 n
| even n = n + fun2 ( n ‘div‘ 2 )
| otherwise = fun2 (3 * n + 1)
我的第一次尝试在这里:
fun2''' = sum
. (filter (even))
. unfoldr (\x -> if (even x)
then Just (x, x `div` 2)
else if (x==1) then Nothing
else Just (x, x * 3 + 1))
这是一个正常的解决方案还是很奇怪?
我怎样才能更好地重写fun2
?
现在我尝试使用takeWhile
和iterate
编写版本
我的第二次尝试:
fun2'' :: Integer -> Integer
fun2'' = sum
. (filter even)
. takeWhile (/=1)
. iterate (\x -> if even x
then x `div` 2
else x * 3 + 1 )
我现在对until
版本没什么问题。
【问题讨论】:
一些不必要的括号,但还算不错。我很乐观,您的takeWhile
+ iterate
方法也会取得不错的效果。
对于奇怪的情况,您也可以返回Just (0, x * 3 + 1)
,而不是返回x
,然后避免filter
。
应用 Collatz 猜想并写成 fun2 _ = 0 怎么样
@LukaRahne 它不是 0。该函数将给定数字的 Collatz 序列中的所有偶数相加。该猜想表明它是有限的,因此它必须以 2 的幂序列结束,即偶数(直到最后的 1)。
【参考方案1】:
看起来不错,这里唯一在 Haskell 中有点危险的是else if
。在这种情况下,它可以很好地改写为 applicative 风格:
-# LANGUAGE TupleSections #-
import Control.Applicative
import Control.Monad (guard)
fun2''' = sum
. filter even
. unfoldr ( \x -> fmap (x,) $
x`div`2 <$ guard(even x)
<|> x*3 + 1 <$ guard( x/=1 )
)
【讨论】:
您可以读取a<$guard α <|> b<$guard β <|> ...
的链,就像您用程序语言编写的if α then return a; if β then return b...
一样。 — 它的工作原理如下:如果 ψ 为真,guard ψ
为 Just ()
,如果为假,Nothing
。 <$
将 ()
替换为其左侧的值,或将 Nothing
保留原样。 <|>
最终选择它找到的第一个 Just a
,跳过任何 Nothing
s。
感谢您的解释,我想我现在明白了。
@groovy: a <$ b
只是 const a <$> b
或 fmap (const a) b
。将其视为 <$>
的特殊版本,它忽略右侧的值——这就是为什么符号就像 <$>
而没有右侧的 >
。
此代码fails with a type mismatch error on Ideone。 Sticking some runKleisli
and Kleisli
in there 成功了。但这几乎不是一个直观或易于阅读的代码(至少对我而言)。 :) 有没有更简单的方法来修改这段代码?
@WillNess:对,Arrow 的东西有点太多了,而且使用所需的 kleisli 包装器真的不再好用了。我回到plain applicative version。【参考方案2】:
现在可以使用multi-way IF 编写嵌套的if
s:
g :: Integer -> Integer
g = sum .
unfoldr (\x->
if | even x -> Just (x, x `div` 2)
| x==1 -> Nothing
| otherwise -> Just (0, x * 3 + 1))
或者你可以定义自己的if操作符,
(??) t (c,a) | t = c | otherwise = a
g = sum . unfoldr (\x-> even x ?? (Just (x, x `div` 2) ,
(x==1) ?? (Nothing, Just (0, x * 3 + 1))))
与until
相同的功能,将sum
和filter
融合到其中:
g = fst . until ((==1).snd)
(\(s,n) -> if even n then (s+n,n`div`2) else (s,3*n+1))
. ((,)0)
或
g = sum . filter even . f
f :: Integer -> [Integer]
f = (1:) . fst . until ((==1).snd)
(\(s,n) -> if even n then (n:s,n`div`2) else (n:s,3*n+1))
. ((,)[])
最后一个函数 f
显示给定输入数字的整个 Collatz 序列,反转。
【讨论】:
请描述 (1:) , ((,)0) , ((,)[]) 句子。我以前从未见过它。 @СергейКузминский 那是“运算符部分”,即部分应用的运算符。(1:) == (:) 1 == (\y -> 1:y)
。 (,)x == (x ,) == (\y -> (x, y))
。另见***.com/a/13477198/849891。
@СергейКузминский 恭喜达到 15 名! :) 你现在有权投票。 ;) ;)(而且你总是有权接受答案)。以上是关于将递归函数重写为管道函数组合的主要内容,如果未能解决你的问题,请参考以下文章