【问题标题】:Map function applied to specific elements of the list映射函数应用于列表的特定元素
【发布时间】:2020-03-23 05:18:11
【问题描述】:

我有一个函数:

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

我该怎么做?

【问题讨论】:

    标签: function haskell recursion map-function


    【解决方案1】:

    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..]) &lt;*&gt; ZipList xs 有一些针对_at_or 的临时定义。

    这利用了data-ordlistunion 是左偏这一事实,即在发生冲突时,它会从第一个参数列表中选择元素而不是第二个参数列表中的元素。

    【讨论】:

    • 就像getZipList $ (f `_at` is) `_or` (id `_at` [0..]) &lt;*&gt; ZipList xs 有一些对_at_or 的临时定义。
    • 太棒了!我不得不承认一个令人印象深刻。我不知道可以像这样使用unionBy。并且仍在处理getZipList 的事情......顺便说一句,在行人代码方面,我添加了基于zipWith 的解决方案。
    • ZipList 只是我们在常规列表上的一个标签,以传达 zipping 应用程序的意图:ZipList fs &lt;*&gt; ZipList xs == ZipList (zipWith ($) fs xs)(在通常的嵌套循环应用程序中,@987654339 @)。标签仍然在结果中,所以我们需要删除它:getZipList (ZipList xs) == xs。像往常一样,在 Haskell 中,命名是混乱的:我们没有得到 ZipList,我们得到的是常规列表 inside。我所说的标记通常被称为包装。 wrap--unrwap 是惯用的。
    • 顺便说一句,我在this answer 中“水平”排列的两个列表中使用了相同的“从上方看”的想法。 (或垂直,从侧面,无论感觉如何)。 :)
    【解决方案2】:

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

    【讨论】:

    • 好主意,拉伸索引列表使其与 xs 匹配。
    【解决方案3】:

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

    然后,在你map的函数中,你只需要对注解元组进行模式匹配,并使用if来决定是否将操纵函数应用于另一个元组元素。

    请注意,zipmap 的组合始终等同于单次传递 zipWith,因此您最好使用它。

    【讨论】:

    • @osobennyj 是的,或者使用上面提到的map ... (zip ...),也就是变相的zipWith
    • 或使用列表理解,[ foo x | (i,x) &lt;- zip [0..] xs, elem i is]。这引起了有趣的关注,因为它不必要地是二次的,所以问题变成了如何使它成为线性(在索引中)。一种方法是使用stateful foldr,将索引作为状态。
    • 事实证明,压缩和左偏联合是要走的路。 :)
    猜你喜欢
    • 2011-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-25
    • 2011-10-04
    • 2021-01-22
    相关资源
    最近更新 更多