如果您坚持使用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