2014-12-19 2 views
1

Мне нужно иметь возможность применить функцию к n-му элементу списка. Например:Применить функцию к 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), есть намного лучший способ сделать это.

+1

Взгляните на линзы. – jamshidh

+3

Взгляд на линзы может быть не лучшей идеей для кого-то нового для Haskell. Я думаю, что у вас здесь все хорошо. Вы ищете что-то более эффективное? Более идиоматично? Однострочный? –

+0

@jamshidh Я быстро посмотрел на линзы, но я предпочел бы сделать это без каких-либо пакетов. – b3036667

ответ

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] 
+0

Не должно быть 'if p == i, тогда f v else v'? Кроме того, это то, что я искал. – b3036667

+0

Да, это должно, моя ошибка. Отредактировано, спасибо. – utdemir

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 
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

Вы можете сделать это с какой-то ручной рекурсии довольно легко, и он будет работать лучше, чем версия 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.

Смежные вопросы