编写一个功能,将右边第二个数字开头的其他数字加倍:
示例:
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等)。
问题
Haskell库函数涵盖了上述情况?
什么是更简洁/惯用的Haskell解决方案仍然是O(n)?
是否有上述递归类型的名称(我们使用更深层次递归的值来做出下一级别的决定)?
答案 0 :(得分:9)
您可以使用foldr
从右侧执行此类递归:
doubleEveryOther = snd . foldr go (False, [])
where go x (b, xs) = (not b, (if b then 2*x else x) : xs)
答案 1 :(得分:6)
使用标准库函数定义此函数的另一种方法:
doubleEveryOther ls = reverse $ zipWith (*) (cycle [1,2]) (reverse ls)
或以无点样式
doubleEveryOther = reverse . zipWith (*) (cycle [1,2]) . reverse
答案 2 :(得分:4)
这里有很多有用的答案,但还没有人提到mapAccumR
中很少见的函数Data.List
几乎完全适合这个特定的用例:
doubleEveryOther :: Num a => [a] -> [a]
doubleEveryOther = snd . mapAccumR step False
where
step False x = (True, x)
step True x = (False, 2*x)
答案 3 :(得分:3)
关于问题1和2,使用lens
,您可以以声明方式定义函数:
import Control.Lens
doubleEveryOther :: Num a => [a] -> [a]
doubleEveryOther = reversed . traversed . indices odd *~ 2
在操作上,这涉及反转列表,然后修改,然后再次反转,但当然它仍然是O(N),具有任意常数的反转。
答案 4 :(得分:2)
另一种方法是使用 lens包。
这使您可以避免显式递归,并且可以对可以操作的数据结构保持非常灵活。
您可以使用elements traversal。需要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实例的任何数据类型。
例如,采用标准树数据类型 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
你的问题。
lens包中有许多函数可以帮助处理递归而不是显式的。
lens很简洁,但有些人还不认为它是惯用的。我还没有测试过上述函数的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 = (:) <$> f x <*> ys
我不确定你在这里要求的是什么。
答案 5 :(得分:1)
您也可以使用地图:
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]
答案 6 :(得分:0)
我使用相互递归的解决方案
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 [] = []
答案 7 :(得分:0)
为了完整起见,这是您的解决方案编码为递归方案同型,如András Kovács's remark所预期:
{-# 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