我有一个函数:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal f is xs = helper 0 is xs
where
helper _ [] xs = xs
helper c (i:is) (x:xs)
| c < i = x : helper (c+1) (i:is) xs
| otherwise = f x : helper (c+1) is xs
它的工作原理是这样的:
mapAtOriginal (*2) [0,3] [1,2,3,4] -- == [2,2,3,8]
所以我想重写它,但使用map
函数。我知道map
适用于列表的每个元素,但是,我只需要将其应用于特定索引。
我该怎么做?
map
不知道它在"列表中的位置"。因此,您首先需要将这些信息编码到元素本身中。这可以用zip [0..]
来完成,它基本上用它发生的位置注释每个元素。
然后,在你map
的函数中,你只需要在注解元组上进行模式匹配,并使用if
来决定是否将操纵器函数应用于另一个元组元素。
请注意,zip
和map
的组合总是等效于单次传递zipWith
,所以这是你应该最好使用的。
如果你坚持使用map
来解决这个问题,一个可能的途径是使用类型[(a, Bool)]
的辅助列表,其中只有索引元素与True
布尔值配对。
生成此辅助列表的函数可以像这样声明:
markIndexedElements :: [Int] -> [a] -> [(a, Bool)]
如果我们有这样的函数,我们问题的其余部分变得容易:
λ> auxList = markIndexedElements [0,3] [1,2,3,4]
λ> auxList
[(1,True),(2,False),(3,False),(4,True)]
λ>
λ> map ((x, b) -> if b then (2*x) else x) auxList
[2,2,3,8]
λ>
markIndexedElements
函数从第一个列表创建第二个列表,同时维护一些状态信息。因此,如果我们更喜欢现成的递归方案,它似乎是 scanl :: (
b -> a -> b( -> b -> [a] -> [b] 函数的工作。要维护的状态主要由原始列表中的当前位置以及迄今为止未使用的索引列表组成。如果当前位置等于下一个索引,我们删除该索引并输出一个 (x, True( 对。这将给出以下代码:
-- only indexed elements to get paired with a True value
markIndexedElements :: [Int] -> [a] -> [(a, Bool)]
markIndexedElements indices xs =
let sfn ((_,p),(pos,ind)) y = -- stepping function for scanl
if (null ind)
then ((y, False), (pos+1,[]))
else ((y, pos==head ind),
(pos+1, if (pos==head ind) then tail ind else ind))
scanRes = scanl sfn ((undefined,False), (0,indices)) xs
in map fst $ drop 1 scanRes
其余代码不难编写:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let pairList = markIndexedElements indices xs -- auxiliary list
pfn (x,b) = if b then (fn x) else x -- auxiliary function
in map pfn pairList
main = do
let xs = [1,2,3,4]
indices = [0,3]
result = mapAtOriginal (*2) indices xs
putStrLn $ show (markIndexedElements indices xs)
putStrLn $ show result
程序输出:
[(1,True),(2,False),(3,False),(4,True)]
[2,2,3,8]
类似的解决方案:
如果我们试着再应用笛卡尔还原论的一个步骤来解决这个问题,我们可以注意到,论证xs
在markIndexedElements
函数中只起很小的作用。一种可能性是完全消除它,取而代之的是仅返回无限布尔值列表的函数:
booleansFromIndices :: [Int] -> [Bool]
booleansFromIndices indices =
let sfn (pos,ind) = Just $ -- stepping function for unfoldr
if (null ind)
then ( False, (pos+1, []) )
else ( pos==head ind,
(pos+1, if (pos==head ind) then tail ind else ind))
in unfoldr sfn (0,indices)
结果列表以最后一个索引之后的无限False
值序列结束:
λ> take 10 $ booleansFromIndices [0,3]
[True,False,False,True,False,False,False,False,False,False]
λ>
然后可以使用booleansFromIndices
重写目标mapAtOriginal
函数:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let pairList = zip xs $ booleansFromIndices indices
pfn (x, b) = if b then (fn x) else x
in map pfn pairList
最后但并非最不重要的一点是,正如其他回答者/评论者已经指出的那样,map+zip 方案通常可以用基于 zipWith 函数的方案代替。 像这样,在我们的例子中:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let booleans = booleansFromIndices indices
in zipWith (x b -> if b then (fn x) else x) xs booleans
从 jpmarinier 的答案开始,填充索引列表中的空白空间; 使用包data-ordlist
,
{-# LANGUAGE TupleSections #-}
import qualified Data.List.Ordered as O
import Data.Ord (comparing)
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal f is xs =
zipWith (flip ($)) -- apply zippily
xs
(map fst $ -- recover the functions, `f` or `id`
O.unionBy (comparing snd) -- `is` is assumed subset of [0..]
(map (f ,) is ) -- tag with
(map (id ,) [0..]) ) -- indices
这就像getZipList $ (f `_at` is) `_or` (id `_at` [0..]) <*> ZipList xs
对_at
和_or
有一些临时定义。
这利用了data-ordlist
的union
是左偏的事实,即它从第一个参数列表中选择元素,而不是从第二个参数列表中挑选元素,在碰撞上。