我有一个功能:
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
适用于列表中的每个元素,但是,我只需要将其应用于特定索引。
我该怎么办?
答案 0 :(得分:5)
map
不知道它在“列表中的位置”。因此,您首先需要将该信息编码为元素本身。可以使用zip [0..]
来完成,该操作基本上用每个元素的出现位置注释它。
然后,在您的函数map
中,您只需要在注释元组上进行模式匹配,并使用if
来决定是否将操纵器函数应用于其他元组元素。
请注意,zip
和map
的组合始终等同于单次通过zipWith
,因此,您最好使用它。
答案 1 :(得分:2)
如果您坚持使用map
来解决此问题,则可能的途径是使用类型为[(a, Bool)]
的辅助列表,其中仅将索引元素与{{1}配对}布尔值。
产生此辅助列表的函数可以这样声明:
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]
λ>
函数在保持一些 state 信息的同时,从第一个列表中列出第二个列表。因此,如果我们更喜欢现成的递归方案,那么scanl :: (b -> a -> b) -> b -> [a] -> [b]函数似乎是一项工作。
要保留的状态主要由原始列表中的当前位置以及迄今为止未使用的索引列表组成。如果当前位置等于下一个索引,则删除该索引并输出(x,True)对。这给出了以下代码:
markIndexedElements
其余代码并不难编写:
-- 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]
λ>
重写目标mapAtOriginal
函数:
booleansFromIndices
最后但并非最不重要的是,正如其他答复者/评论者已经指出的那样,通常可以使用基于zipWith函数的map + zip方案替换map + zip方案。 在我们的例子中是这样的:
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
答案 2 :(得分:2)
从jpmarinier的answer的想法开始,就是填补索引列表中的空白;使用软件包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
的{{1}}是左偏的事实,即在发生冲突时,它从其第一个参数列表中选择了元素,而从其第二个参数列表中选择了元素。