我想在每n个位置对整数列表执行算术运算(例如,将值加倍)。
例如,给定列表[1,2,3,4,5,6,7]
,我想每三个位置加倍值。在这种情况下,我们会[1,2,6,4,5,12,7]
。
我该怎么办?
答案 0 :(得分:13)
applyEvery :: Int -> (a -> a) -> [a] -> [a]
applyEvery n f = zipWith ($) (cycle (replicate (n-1) id ++ [f]))
cycle
子表达式使用正确数量的元素构建函数列表[id,id,...,id,f]
并重复使用,而zipWith ($)
将该函数列表应用于参数列表。< / p>
既然你要求它,更详细!随意请求更多解释。
最主要的想法可能最好用ASCII图片解释(这不会阻止我写一千个很多ASCII字!):
functions : [ id, id, f , id, id, f , id, id, f, ...
input list: [ 1, 2, 3, 4, 5, 6, 7 ]
-----------------------------------------------------
result : [ 1, 2, f 3, 4, 5, f 6, 7 ]
就像没有理由硬编码你想要加倍列表中每第三个元素的事实一样,f
(在你的例子中加倍)并没有什么特别之处,除了它应该具有相同的结果输入什么都不做。所以我把这些参数作为我的功能。操作数字列表甚至不重要,因此该函数适用于a
列表,只要它给出“间隔”和操作。这为我们提供了类型签名applyEvery :: Int -> (a -> a) -> [a] -> [a]
。我把输入列表放在最后,因为像doubleEveryThird = applyEvery 3 (*2)
这样的部分应用程序会返回一个新列表,即所谓的组合器。我基本上随机选择了其他两个参数的顺序:-)
要构建函数列表,我们首先组装基本构建块,包括n-1 id
s,然后是f
,如下所示:replicate (n-1) id ++ [f]
。 replicate m x
制作一个包含m
参数重复x
的列表,例如replicate 5 'a' = "aaaaa"
,但它也适用于函数。我们必须将f
包装在自己的列表中,而不是使用:
,因为您只能在前面添加单个元素 - Haskell的列表是单链接的。
接下来,我们继续使用cycle
重复基本构建块(不是repeat
,因为我第一次错误)。 cycle
的类型为[a] -> [a]
,因此结果是“相同级别的嵌套”列表。示例cycle [1,2,3]
的计算结果为[1,2,3,1,2,3,1,2,3,...]
[旁注:我们没有使用的唯一的重复y函数是repeat
本身:它形成一个由其参数组成的无限列表]
除此之外,稍微棘手的zipWith ($)
部分。您可能已经知道了普通的zip
函数,它接受两个列表并将元素放在结果中元组的同一位置,当任一列表用完元素时终止。图示地:
xs : [ a , b , c , d, e]
ys: [ x, y , z ]
------------------------------
zip xs ys: [(a,x),(b,y),(c,z)]
这看起来非常像第一张照片,对吗?唯一的事情是我们不想将单个元素放在一个元组中,而是将第一个元素(这是一个函数)应用到第二个元素。使用zipWith
进行自定义组合功能的压缩。另一张照片(最后一张,我保证!):
xs : [ a , b , c , d, e]
ys: [ x, y, z ]
----------------------------------------
zipWith f xs ys: [ f a x, f b y, f c z ]
现在,我们应该选择zipWith
做什么?好吧,我们想要将第一个参数应用到第二个参数,因此(\f x -> f x)
应该可以解决问题。如果lambdas让你感到不舒服,你也可以定义一个顶级函数apply f x = f x
并使用它。但是,这已经是Prelude中的标准运算符,即$
!由于您不能将中缀运算符用作独立函数,因此我们必须使用语法糖($)
(这实际上只意味着(\f x -> f $ x)
)
将上述所有内容放在一起,我们得到:
applyEvery :: Int -> (a -> a) -> [a] -> [a]
applyEvery n f xs = zipWith ($) (cycle (replicate (n-1) id ++ [f])) xs
但我们最终可以摆脱xs
,导致我给出的定义。
答案 1 :(得分:7)
获取列表中值的索引的常用方法是将zip
列表转换为(value, index)
的元组。
ghci > let zipped = zip [1,2,3,4,5,6,7] [1..]
ghci > zipped
[(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7)]
然后你需要map
超过该列表并返回一个新列表。如果index可以被3整除(index `rem` 3 == 0
),我们会将值加倍,否则我们将返回相同的值:
ghci > map (\(value, index) -> if index `rem` 3 == 0 then value*2 else value) zipped
[1,2,6,4,5,12,7]
告诉我这一切是否有意义 - 如果您不熟悉zip
和map
等,我可以添加更多详细信息。
您可以通过查看Haddocks找到zip
上的文档,其中说:“zip需要两个列表并返回相应对的列表。” (文档托管在多个地方,但我去了https://www.stackage.org并搜索了zip
)。
map
函数将函数应用于列表中的每个项目,为每个元素生成新值。
Lambdas只是没有特定名称的函数。我们在map
的第一个参数中使用了一个来说明我们应该对列表中的每个元素做什么。您可能已经在其他语言中看到过这些,如Python,Ruby或Swift。
这是lambdas的语法:
(\arg1, arg2 -> functionBodyHere)
我们也可以在没有lambda的情况下编写它:
ghci > let myCalculation (value, index) = if index `rem` 3 == 0 then value*2 else value
ghci > map myCalculation zipped
[1,2,6,4,5,12,7]
答案 2 :(得分:2)
mapIf :: (Int -> Bool) -> (a -> a) -> [a] -> [a]
mapIf pred f l = map (\(value,index) -> if (pred index) then f value else value) $ zip l [1..]
mapEveryN :: Int -> (a -> a) -> [a] -> [a]
mapEveryN n = mapIf (\x -> x `mod` n == 0)
答案 3 :(得分:2)
注意:此代码尚未经过测试。
在lens
土地上,这称为Traversal
。 Control.Lens
为您提供以下内容:
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
type Traversal s t a b =
forall f . Applicative f => (a -> f b) -> s -> f t
type Traversal' s a = Traversal s s a a
我们可以使用lens
中的itraverse
Control.Lens.Indexed
:
-- everyNth :: (TraversableWithIndex i t, Integral i)
=> i -> Traversal' (t a) a
everyNth :: (TraversableWithIndex i t, Integral i, Applicative f)
=> i -> (a -> f a) -> t a -> f (t a)
everyNth n f = itraverse f where
g i x | i `rem` n == n - 1 = f x
| otherwise = pure x
这可以专门针对您的特定目的:
import Data.Profunctor.Unsafe
import Data.Functor.Identity
everyNthPureList :: Int -> (a -> a) -> [a] -> [a]
everyNthPureList n f = runIdentity #. everyNth n (Identity #. f)
答案 4 :(得分:1)
一种简单的递归方法:
(select (rec1).*)
基本上,igo从everyNth n f xs = igo n xs where
igo 1 (y:ys) = f y : igo n ys
igo m (y:ys) = y : igo (m-1) ys
igo _ [] = []
doubleEveryThird = everyNth 3 (*2)
开始,向下计数直到达到n
,然后它将应用该功能,然后返回1
。部分应用了n
:doubleEveryThird
需要三个参数,但我们只给了它两个,所以everyNth
会期望最终的参数。