Map function applied to specific elements of the list

生来就可爱ヽ(ⅴ<●) 提交于 2020-01-25 03:10:09

问题


I have a 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

It works like this:

mapAtOriginal (*2) [0,3] [1,2,3,4]   -- == [2,2,3,8]

So I want to rewrite it but using a map function. I understand that map applies to every element of the list, however, I need it applied only for specific indices.

How can I do it?


回答1:


map doesn't know “where in the list” it is. So you first need to encode that information into the elements themselves. This can be done with zip [0..], which basically annotates each element with position at which it occurs.

Then, in the function you map, you just need to pattern-match on the annotation-tuple, and use an if to decide whether or not to apply the manipulator function to the other tuple element.

Note that the combination of zip and map is always equivalent to a single pass zipWith, so that's what you should preferrably use.




回答2:


If you insist on using map to solve this problem, a possible route consists in making use of an auxiliary list of type [(a, Bool)], where only the indexed elements are paired with a True boolean value.

The function producing this auxiliary list could be declared like this:

markIndexedElements :: [Int] -> [a] -> [(a, Bool)]

If we have such a function, the rest of our problem becomes easy:

 λ> 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]
 λ> 

The markIndexedElements function makes a second list out of the first list, while maintaining some state information. So if we prefer a ready-made recursion scheme, it seems to be a job for the scanl :: (b -> a -> b) -> b -> [a] -> [b] function.

The state to be maintained consists mostly of the current position in the original list plus the list of so far unused indices. If the current position is equal to the next index, we drop that index and output an (x, True) pair. This gives the following code:

-- 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

The rest of the code is not difficult to write:

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

Program output:

[(1,True),(2,False),(3,False),(4,True)]
[2,2,3,8]

Similar solutions:

If we try to apply just one more step of Cartesian reductionism to this problem, we can note that argument xs plays only a minor role in the markIndexedElements function. A possibility is to eliminate it completely, and have instead a function that returns just an unlimited list of boolean values:

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)

The resulting list ends with an unlimited sequence of False values after the last index:

 λ> take 10 $ booleansFromIndices [0,3]
 [True,False,False,True,False,False,False,False,False,False]
 λ> 

The target mapAtOriginal function can then be rewritten using booleansFromIndices:

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

Last but not least, as already noted by other answerers/commenters, the map+zip scheme can generally be replaced by one based on the zipWith function. Like this, in our case:

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



回答3:


Starting from the idea from jpmarinier's answer of filling up the empty spaces in the indices list; using package 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

It's like getZipList $ (f `_at` is) `_or` (id `_at` [0..]) <*> ZipList xs with some ad-hoc definitions for _at and _or.

This uses the fact that data-ordlist's union is left-biased, i.e. it picks the elements from its first argument list over those from its second, on collisions.



来源:https://stackoverflow.com/questions/59073781/map-function-applied-to-specific-elements-of-the-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!