简体   繁体   English

在Haskell中使用什么而不是显式递归?

[英]What to use instead of explicit recursion in Haskell?

Write a function that doubles other number beginning with the 2nd number from the right : 编写一个函数, 将右边第二个数字开头的其他数字翻倍:

Example: 例:

doubleEveryOther   [8,7,6,5]
=> [16,7,12,5]

doubleEveryOther     [1,2,3]
=> [1,4,3]

O(n) solution: O(n)解决方案:

doubleEveryOther :: Num a => [a] -> [a]
doubleEveryOther xs0 =
    let (_,r)   = deo xs0
        deo xs1 = case xs1 of
            []     -> (False, [])
            (x:xs) -> let (b, xs') = deo xs in ((not b), (if b then 2*x else x) : xs')
    in r

The use above of explicit recursion is generally considered poor Haskell style (eg, use fold*, scan, etc where possible). 以上对显式递归的使用通常被认为是差的Haskell样式(例如,在可能的情况下使用fold *,scan等)。

QUESTIONS 质询

  1. what Haskell library functions cover the above case? Haskell库函数涵盖了上述情况?

  2. what would be a more concise/idiomatic Haskell solution that is still O(n)? 什么是更简洁/惯用的Haskell解决方案仍然是O(n)?

  3. is there a name for the above type of recursion (where we use the value from a deeper recursion to make a decision the next level up)? 是否有上述类型的递归的名称(我们使用更深层递归的值来做出下一级别的决定)?

You can use foldr to do this kind of recursion from the right: 您可以使用foldr从右侧执行此类递归:

doubleEveryOther = snd . foldr go (False, [])
    where go x (b, xs) = (not b, (if b then 2*x else x) : xs)

Another way to define this function by using standard library functions: 使用标准库函数定义此函数的另一种方法:

doubleEveryOther ls = reverse $ zipWith (*) (cycle [1,2]) (reverse ls)

Or in pointfree style 或者以无点的风格

doubleEveryOther = reverse . zipWith (*) (cycle [1,2]) . reverse

Lots of useful answers here, but no one yet mentioned the rarely seen function mapAccumR from Data.List which fits this particular use case almost perfectly: 这里有用的答案很多,但还没有人提到的罕见功能mapAccumRData.List ,其适合这一特定使用案例几乎是完美的:

doubleEveryOther :: Num a => [a] -> [a]
doubleEveryOther = snd . mapAccumR step False
  where
    step False x = (True, x)
    step True  x = (False, 2*x)

As to question 1 and 2, with lens you can define the function in a declarative manner: 对于问题1和2,使用lens您可以以声明方式定义函数:

import Control.Lens

doubleEveryOther :: Num a => [a] -> [a]
doubleEveryOther = reversed . traversed . indices odd *~ 2 

Operationally, this involves reversing the list, then modifying, then reversing again, but of course it's still O(N) with any constant number of reversals. 在操作上,这涉及反转列表,然后修改,然后再次反转,但当然它仍然是O(N)具有任何恒定数量的反转。

An alternative is to use the lens package. 另一种方法是使用镜头包装。

This allows you to avoid explicit recursion and remain very flexible on what data structures you can operate on. 这允许您避免显式递归,并且对可以操作的数据结构保持非常灵活。

You can use the elements traversal . 您可以使用元素遍历 It takes a Int -> Bool function to decide what indices to act on. 它需要一个Int -> Bool函数来决定要采取什么指标。

Double even indices or odd indices. 双偶数指数或奇数指数。

> over (elements even) (*2) [8,7,6,5]
[16,7,12,5]
> over (elements odd) (*2) [8,7,6,5]
[8,14,6,10]

Or double every third element: 或者每三个元素加倍:

> over (elements (\n -> mod n 3 == 0)) (*2) [8,7,6,5]
[16,7,6,10]


Not just lists 不只是列表

This technique will work for any datatype that has a Traversable instance. 此技术适用于具有Traversable实例的任何数据类型。

For example take the standard tree datatype for the containers . 例如,获取容器的标准树数据类型。

> import Data.Tree
> let tree = Node 1 [Node 2 [Node 3 [], Node 4 []], Node 5 [Node 6 []]]
> let prettyTree = putStrLn . drawTree . fmap show
> prettyTree tree
1
|
+- 2
|  |
|  +- 3
|  |
|  `- 4
|
`- 5
   |
   `- 6
> prettyTree $ over (elements even) (*2) tree
2         --   1
|         --   |
+- 2      --   +- 2
|  |      --   |  |
|  +- 6   --   |  +- 3
|  |      --   |  |
|  `- 4   --   |  `- 4
|         --   |
`- 10     --   `- 5
   |      --      |
   `- 6   --      `- 6

Your questions. 你的问题。

  1. The lens package has a number of functions that help with handling recursion with out being explicit. 镜头包具有许多功能,有助于处理递归而不显式。

  2. The lens is concise, though some do not yet considered it idiomatic. 镜头很简洁,虽然有些人还不认为它是惯用的。 I have not tested the bigO of the above functions. 我还没有测试过上述函数的bigO。 My understanding is that it will depend on the bigO of the traversable instance for the datatype you are using. 我的理解是它将取决于您正在使用的数据类型的可遍历实例的bigO。

    The list instance in the Traversable module looks straightforward and should meet your expectations.: Traversable模块中的列表实例看起来很简单,应该符合您的期望:

     instance Traversable [] where {-# INLINE traverse #-} -- so that traverse can fuse traverse f = Prelude.foldr cons_f (pure []) where cons_f x ys = (:) <$> fx <*> ys 
  3. I am not sure what you are asking for here. 我不确定你在这里要求什么。

You can use map as well: 你也可以使用地图:

Prelude> let f ns = map (\(a,b) -> if (even (length ns) && even b) || (odd (length ns) && odd b) then a else a * 2) $ zip ns [1..]

Prelude> f [8,7,6,5]
[16,7,12,5]

Prelude> f [8,7,6]
[8,14,6]

My solution using mutual recursions 我使用相互递归的解决方案

doubleEveryOther :: [Integer] -> [Integer]                                      
doubleEveryOther xs                                                             
    | even n =  doubleOdd xs                                                    
    | otherwise = doubleEven xs                                                 
  where n = length xs     

-- | use mutual recursion
doubleEven :: Num a => [a] -> [a]
doubleEven (x:xs) = x : doubleOdd xs     
doubleEven [] = []                                                              

doubleOdd :: Num a => [a] -> [a]
doubleOdd (x:xs) = (2*x) : doubleEven xs 
doubleOdd [] = []                                                               

For the sake of completeness, here is your solution encoded as a recursion-schemes zygomorphism, as anticipated by András Kovács's remark : 为了完整起见,正如AndrásKovács的评论所预期的那样,这里的解决方案被编码为递归方案 zygomorphism:

{-# LANGUAGE LambdaCase #-}

import Data.Functor.Foldable

doubleEveryOther :: Num a => [a] -> [a]
doubleEveryOther = zygo flagAlg emitAlg
    where
    flagAlg = \case
        Nil -> False
        Cons _ b -> not b
    emitAlg = \case
        Nil -> []
        Cons x (b, xs) -> (if b then 2*x else x) : xs

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

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