地图功能应用于列表的特定元素



我有一个函数:

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来决定是否将操纵器函数应用于另一个元组元素。

请注意,zipmap的组合总是等效于单次传递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]

类似的解决方案:

如果我们试着再应用笛卡尔还原论的一个步骤来解决这个问题,我们可以注意到,论证xsmarkIndexedElements函数中只起很小的作用。一种可能性是完全消除它,取而代之的是仅返回无限布尔值列表的函数:

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-ordlistunion是左偏的事实,即它从第一个参数列表中选择元素,而不是从第二个参数列表中挑选元素,在碰撞上。

相关内容

  • 没有找到相关文章

最新更新