【问题标题】:Apply a function to every second element in a list将函数应用于列表中的每个第二个元素
【发布时间】:2015-01-31 21:40:34
【问题描述】:

我想对列表中的每个第二个元素应用一个函数:

> mapToEverySecond (*2) [1..10]
[1,4,3,8,5,12,7,16,9,20] 

我写了以下函数:

mapToEverySecond :: (a -> a) -> [a] -> [a]
mapToEverySecond f l = map (\(i,x) -> if odd i then f x else x) $ zip [0..] l

这可行,但我想知道是否有更惯用的方式来做这样的事情。

【问题讨论】:

  • 当然还有其他方法。但我投票结束的理由是,在这种情况下,什么是“更好”的东西似乎非常主观。如果您可以描述您在实施中遇到的具体问题,那么我们就可以着手解决该问题。
  • 我支持@DanielWagner,但这是一个标准的有趣答案:map2nd f = zipWith ($) (cycle [id, f])
  • @J.Abrahamson,从基础 4.8 开始,有时会比以前的版本慢(以前的版本是阴暗的)。你可以改用map2nd f xs = zipWith (flip ($)) xs (cycle [id, f])
  • 你说得对,“更好”是主观的。但是当我刚刚开始学习 Haskell 时,我很想看到有经验的 Haskell 的解决方案。当我找到这个解决方案时,我只是想知道“就是这样”......来自 for-loop-languages 这感觉非常不同。谢谢@J.Abrahamson 的回答。
  • @BenjaminHodgson,这与编译器无关,也与部分应用程序无关。 zipWith 长期以来一直试图与它的任何一个列表参数融合。不幸的是,与 right 列表参数融合实际上在语义上是错误的,并且可能会产生底部,当使用优化编译时不应该出现底部,否则不会。所以我推动将其删除,所以zipWith 只会与它的 left 列表参数融合。

标签: list haskell


【解决方案1】:

Haskell 我写的不多,但首先想到的是:

func :: (a -> a) -> [a] -> [a]
func f [] = []
func f [x] = [x]
func f (x:s:xs) = x:(f s):(func f xs)

这有点不合时宜,因为您不仅要处理空列表,还要处理包含一个元素的列表。这也不能很好地扩展(如果你想要每三分之一,或者

可以像@Landei 指出的那样写

func :: (a -> a) -> [a] -> [a]
func f (x:s:xs) = x:(f s):(func f xs)
func f xs = xs

不过,为了摆脱对 [][x] 的丑陋检查,恕我直言,这使它更难阅读(至少是第一次)。

【讨论】:

  • 非常合理。但是,您可能应该删除最后一行中多余的括号:func f (x:s:xs)=x : f s : func f xs
  • 接受,这是最直接的解决方案(对我来说)。谢谢你。我想对于其他基于索引的标准(每三分之一,第四,只有素数等)我会坚持我的解决方案。
  • 您可以删除前两种情况,并将func f xs = xs 添加为最后一个
【解决方案2】:

我会这样做:

mapOnlyOddNumbered f []      = []
mapOnlyOddNumbered f (x:xs)  = f x : mapOnlyEvenNumbered f xs

mapOnlyEvenNumbered f []     = []
mapOnlyEvenNumbered f (x:xs) = x : mapOnlyOddNumbered f xs

这是否是“惯用的”是一个见仁见智的问题(如果它适合的话,我会把它作为评论给出),但看看许多不同的方法可能会很有用。您的解决方案与我的解决方案或 cmets 中的解决方案一样有效,并且更容易更改为 mapOnlyEvery13ndmapOnlyPrimeNumbered

【讨论】:

    【解决方案3】:
    mapToEverySecond = zipWith ($) (cycle [id, (*2)])
    

    是我能想到的最小的,在我看来也很清楚。它还有点随着每nth扩展。

    编辑:哦,人们已经在 cmets 中提出了建议。我不想偷它,但我真的认为这就是答案。

    【讨论】:

    • 为了与其他答案保持一致,我认为最好是mapToEverySecond f = zipWith ($) (cycle [id, f])
    【解决方案4】:

    我可能会这样做:

    mapToEverySecond f xs = foldr go (`seq` []) xs False
      where
        go x cont !mapThisTime =
          (if mapThisTime then f x else x) : cont (not mapThisTime)
    

    但如果我正在编写库代码,我可能会将其封装在 build 表单中。

    编辑

    是的,这也可以使用mapAccumLtraverse 来完成。

    import Control.Applicative
    import Control.Monad.Trans.State.Strict
    import Data.Traversable (Traversable (traverse), mapAccumL)
    
    mapToEverySecond :: Traversable t => (a -> a) -> t a -> t a
    -- Either
    mapToEverySecond f = snd . flip mapAccumL False
     (\mapThisTime x ->
         if mapThisTime
         then (False, f x)
         else (True, x))
    
    -- or
    mapToEverySecond f xs = evalState (traverse step xs) False
      where
        step x = do
          mapThisTime <- get
          put (not mapThisTime)
          if mapThisTime then return (f x) else return x
    

    或者您可以使用scanl 来完成,我将留给您解决。

    【讨论】:

    • 谢谢,这看起来很有趣(我最初想使用折叠/扫描)。我需要查找 seq 和!不过,这是我第一次见到他们。
    • @Nausika,不幸的是,Haskell 中的扫描有点损坏(grr ...),但如果您愿意,无论如何都可以使用它们。另一个不同的选项是mapAccumL。比traverse 更干净。
    • @Nausika,我添加了一些细节。
    • 感谢您提供详细信息。函数mapAccumL 正是我想要的!为什么说scans和mapAccum坏了?
    • @Nausika, mapAccumL 本身并没有真正损坏,但是它返回累加器的最终值以及列表的事实使它无法正常工作,否则它可以使用列表融合优化。 scanl 已损坏,因为它以初始累加器值开始列表。最大的问题可能是不能为一般的Traversable 类型定义它。我记得,这也让其他一些事情有点尴尬。
    【解决方案5】:

    这更像是对@MartinHaTh 答案的评论。我会稍微优化他的解决方案

    func :: (a -> a) -> [a] -> [a]
    func f = loop
      where
        loop []  = []
        loop [x] = [x]
        loop (x:s:xs) = x : f s : loop xs
    

    【讨论】:

    • 这实际上是一种优化,还是只是节省字符?您不再将 f 显式传递给递归调用,但我认为它仍然被闭包创建机制隐式传递。
    • @amalloy 这是一个优化。 MartinHaTh 的函数是递归的,这意味着它不会被 GHC 内联。但是这个func 不是(只有loop 是),所以func f 将被内联,这也可以实现进一步的潜在优化。另请参阅Can GHC really never inline map, scanl, foldr, etc.?
    【解决方案6】:

    不是很优雅,但这是我的看法:

    mapToEverySecond f = reverse . fst . foldl' cmb ([], False) where
        cmb (xs, b) x = ((if b then f else id) x : xs, not b)
    

    或改进 MartinHaTh 的答案:

    mapToEverySecond f (x : x' : xs) = x : f x' : mapToEverySecond f xs
    mapToEverySecond _ xs = xs
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-18
      相关资源
      最近更新 更多