将递归函数重写为管道函数组合

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

现在我尝试使用takeWhileiterate 编写版本

我的第二次尝试:

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 应用 Collat​​z 猜想并写成 fun2 _ = 0 怎么样 @LukaRahne 它不是 0。该函数将给定数字的 Collat​​z 序列中的所有偶数相加。该猜想表明它是有限的,因此它必须以 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&lt;$guard α &lt;|&gt; b&lt;$guard β &lt;|&gt; ... 的链,就像您用程序语言编写的if α then return a; if β then return b... 一样。 — 它的工作原理如下:如果 ψ 为真,guard ψJust (),如果为假,Nothing&lt;$() 替换为其左侧的值,或将 Nothing 保留原样。 &lt;|&gt; 最终选择它找到的第一个 Just a,跳过任何 Nothings。 感谢您的解释,我想我现在明白了。 @groovy: a &lt;$ b 只是 const a &lt;$&gt; bfmap (const a) b。将其视为 &lt;$&gt; 的特殊版本,它忽略右侧的值——这就是为什么符号就像 &lt;$&gt; 而没有右侧的 &gt; 此代码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 编写嵌套的ifs:

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 相同的功能,将sumfilter 融合到其中:

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 显示给定输入数字的整个 Collat​​z 序列,反转。

【讨论】:

请描述 (1:) , ((,)0) , ((,)[]) 句子。我以前从未见过它。 @СергейКузминский 那是“运算符部分”,即部分应用的运算符。 (1:) == (:) 1 == (\y -&gt; 1:y)(,)x == (x ,) == (\y -&gt; (x, y))。另见***.com/a/13477198/849891。 @СергейКузминский 恭喜达到 15 名! :) 你现在有权投票。 ;) ;)(而且你总是有权接受答案)。

以上是关于将递归函数重写为管道函数组合的主要内容,如果未能解决你的问题,请参考以下文章

在不使用递归的情况下重写递归函数

编写一个打印所有组合的通用函数。没有递归[重复]

函数参数,返回值,递归函数

递归函数二分查找面相对象初识类空间,对象空间组合继承

设计生成所有 n 位数字组合的递归函数的最佳方法是啥?

用递归函数计算从n个人中选择k个人组成一个委员会的不同组合数