簡體   English   中英

在Haskell中使用什么而不是顯式遞歸?

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

編寫一個函數, 將右邊第二個數字開頭的其他數字翻倍:

例:

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

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

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

以上對顯式遞歸的使用通常被認為是差的Haskell樣式(例如,在可能的情況下使用fold *,scan等)。

質詢

  1. Haskell庫函數涵蓋了上述情況?

  2. 什么是更簡潔/慣用的Haskell解決方案仍然是O(n)?

  3. 是否有上述類型的遞歸的名稱(我們使用更深層遞歸的值來做出下一級別的決定)?

您可以使用foldr從右側執行此類遞歸:

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

使用標准庫函數定義此函數的另一種方法:

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

或者以無點的風格

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

這里有用的答案很多,但還沒有人提到的罕見功能mapAccumRData.List ,其適合這一特定使用案例幾乎是完美的:

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

對於問題1和2,使用lens您可以以聲明方式定義函數:

import Control.Lens

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

在操作上,這涉及反轉列表,然后修改,然后再次反轉,但當然它仍然是O(N)具有任何恆定數量的反轉。

另一種方法是使用鏡頭包裝。

這允許您避免顯式遞歸,並且對可以操作的數據結構保持非常靈活。

您可以使用元素遍歷 它需要一個Int -> Bool函數來決定要采取什么指標。

雙偶數指數或奇數指數。

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

或者每三個元素加倍:

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


不只是列表

此技術適用於具有Traversable實例的任何數據類型。

例如,獲取容器的標准樹數據類型。

> 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

你的問題。

  1. 鏡頭包具有許多功能,有助於處理遞歸而不顯式。

  2. 鏡頭很簡潔,雖然有些人還不認為它是慣用的。 我還沒有測試過上述函數的bigO。 我的理解是它將取決於您正在使用的數據類型的可遍歷實例的bigO。

    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. 我不確定你在這里要求什么。

你也可以使用地圖:

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]

我使用相互遞歸的解決方案

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 [] = []                                                               

為了完整起見,正如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