我需要能够将函数应用于列表的第n个元素。例如:
> doSomething (+5) 2 [1,2,3,4,5]
应该返回[1,7,3,4,5]
我有一个可以做到这一点的功能:
doSomething :: (a -> a) -> Int -> [a] -> [a]
doSomething f n xs = ys ++ [f x] ++ zs
where (ys, x:zs) = splitAt (n - 1) xs
但我是Haskell的新手,所以我确信(与Haskell中的许多简单函数一样)有更好的方法。
答案 0 :(得分:3)
由于jamshidh表示lens包使得完成此类任务变得简单。
> over (element 2) (+5) [1..5]
[1,2,8,4,5]
这种操作适用于任何可遍历的,例如树:
> import Data.Tree
> let tree = Node 1 [Node 2 [], Node 3 []]
> putStr . drawTree . fmap show $ tree
1
|
+- 2
|
`- 3
> putStr . drawTree . fmap show $ over (element 2) (+5) tree
1
|
+- 2
|
`- 8
答案 1 :(得分:3)
如果您需要随机访问序列的元素,您可能根本不想使用列表。例如,您可以改为使用Data.Vector
:
import Data.Vector (Vector)
import qualified Data.Vector as V
modifyNth :: Int -> (a -> a) -> Vector a -> Vector a
modifyNth n f = V.imap f'
where f' i a | i == n = f a
| otherwise = a
使用示例:
>>> modifyNth 2 (+5) (V.fromList [1,2,3,4,5])
fromList [1,2,8,4,5]
答案 2 :(得分:2)
如果您不想深入了解镜头,而更喜欢简单的解决方案,您可以使用列表推导;它在线性时间运行,列表连接会降低大型列表的性能:
Prelude> [if i == 2 then v + 5 else v | (i, v) <- zip [1..] l]
[1,7,3,4,5]
所以,doSomething
将是:
Prelude> let doSomething f i l = [if p == i then f v else v | (p, v) <- zip [1..] l]
Prelude> doSomething (+5) 2 [1,2,3,4,5]
[1,7,3,4,5]
答案 3 :(得分:2)
您可以非常轻松地使用一些手动递归来执行此操作,并且它将比splitAt
版本执行得更好,并且分配的临时对象少于列表推导。
doSomething :: (a -> a) -> Int -> [a] -> [a]
doSomething _f _ [] = []
doSomething f 0 (x:xs) = f x : xs
doSomething f n (x:xs) = x : doSomething f (n - 1) xs
案例非常明显:如果列表为空,则无法执行任何操作,因此请将其返回。如果n为0,则只需在其上调用f并将其添加到列表的其余部分。否则,你可以 将当前的x放在前面,然后用较小的n递归。