映射功能应用于列表的特定元素

时间:2019-11-27 15:42:35

标签: function haskell recursion map-function

我有一个功能:

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 适用于列表中的每个元素,但是,我只需要将其应用于特定索引。

我该怎么办?

3 个答案:

答案 0 :(得分:5)

map不知道它在“列表中的位置”。因此,您首先需要将该信息编码为元素本身。可以使用zip [0..]来完成,该操作基本上用每个元素的出现位置注释它。

然后,在您的函数map中,您只需要在注释元组上进行模式匹配,并使用if来决定是否将操纵器函数应用于其他元组元素。

请注意,zipmap的组合始终等同于单次通过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)

jpmarinieranswer的想法开始,就是填补索引列表中的空白;使用软件包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}}是左偏的事实,即在发生冲突时,它从其第一个参数列表中选择了元素,而从其第二个参数列表中选择了元素。