【问题标题】:Map over list, except for last list element映射列表,最后一个列表元素除外
【发布时间】:2014-11-09 06:14:53
【问题描述】:

如何最好地映射列表的所有元素,除了最后一个列表元素?

假设我们有一个列表 let l = [1,2,3,4] 并想要获得 [2,3,4,4]

我确实有一个解决方案,但感觉不像是“功能性”的方法(在 ghci 中):

let l = [1,2,3,4]
let len = toIntegral $ length l -- to avoid a type mismatch Integer <-> Int
let l1 = zip l [1..]
let l2 = map (\(x,y) -> if y < len then (x + 1,y) else (x,y)) l1
let l3 = map (fst) l2

不是很好...我希望有更好的方法!由于我是函数式编程的新手,但我不知道从哪里开始寻找它。

【问题讨论】:

    标签: list haskell functional-programming map-function


    【解决方案1】:

    只需重写map,但在只有一个元素时做一个特殊情况:

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

    这现在甚至适用于无限列表,它比计算长度要快得多,并且更具可读性。请注意,这确实将您的函数限制为 a -&gt; a 类型,而不是 a -&gt; b

    或者,你可以这样做

    mapBut1 f (x:y:xs) = f x : mapBut1 f (y:xs)
    mapBut1 f other = other
    

    它们是等价的定义,但后者使用的模式匹配少了 1 个。不过,我更喜欢前者,因为处理的案件更明显。

    【讨论】:

    • 非常好!一件小事:代码最后一行中的xs 不应该是x,因为我们匹配的是长度为1 的列表吗?
    • @mort 首先,Haskell 不在乎我们如何命名变量,因此将 xs 重命名为 x 不会改变代码的含义。其次,该行匹配与(x:xs) 模式不匹配的任何内容,可能是[_][][_] 表示一个元素的列表)。所以它的意思是除了模式(x:xs) 之外的任何东西都会匹配,而不是单个元素会匹配。但是,您确实让我发现了这段代码中的一个小错误。
    【解决方案2】:

    像往常一样,这是一个假装超态的工作:

    import Data.List (tails)
    
    mapButLast :: (a -> a) -> [a] -> [a]
    mapButLast f = foldr g [] . tails where
      g (x:_:_) r = f x : r
      g xs      _ = xs
    

    或者用正确的para 我们就写

    mapButLast f = para g [] where
      g x [] r = [x]
      g x _  r = f x : r
    

    在哪里

    para f z (x:xs) = f x xs (para f z xs)
    para f z []     = z
    

    【讨论】:

    • 这是一个不错的模式!
    • @J.Abrahamson 我已经编辑了更多内容,带有链接。 ;)
    • para 是“显而易见”的方式,但我喜欢使用 tails 来获得假装的想法。虽然,他们并排在一起,我不太确定它实际上比para 本身更易懂。
    • @J.Abrahamson 最终它们是相同的——访问tailspara 所做的(最多跳过一个节拍)。使用tails,它甚至可以重用结构。有时非标准的para f z [] = z; para f z xs@(x:t) = f x xs (para f z t) 可能更可取,而这正是foldr ... tails 的变体。
    【解决方案3】:

    你也可以使用标准库中的init and last(没有特殊导入)如果你不关心性能

    你可以写

    map f (init list) ++ [last list]
    

    在哪里

    • f : 你要映射的函数
    • list : 你想映射的列表

    【讨论】:

    • 这并不是糟糕的性能,因为init 是懒惰的,last list expr。在评估之前处于 WHNF 中。我敢打赌,它与 O(n) 遍历到可能被 ghc 优化的最后一个元素所接受的答案相提并论。
    • @DavidUnric,这具有非常糟糕的性能特征。特别是,它不是一个好的流媒体:它将整个输入列表保存在内存中,直到最后一个元素被评估。
    • 我喜欢这个解决方案,因为即使是初学者也可以阅读;)
    【解决方案4】:

    是什么让最后一个元素如此特别?

    我知道它不能回答你的问题,但我会考虑像

    这样的类型
    data Foo a = Foo a [a]
    

    和一个合适的 Functor。

    【讨论】:

      【解决方案5】:

      我只想添加一个使用列表理解的替代方法并尝试比较解决方案(我第一次尝试,如果我做得不对,请告诉我)

      reverse $ last l : [f x | x <- tail (reverse l)]
      

      它比大多数其他解决方案都慢(请参阅下面的报告链接)。

      这是一个示例程序 (allbutlast.hs)。它具有上述所有解决方案,一个简单的quickcheck 测试和一个使用criterion 的基准测试。

      运行的先决条件:QuickCheck 和标准

      cabal update
      cabal install QuickCheck
      cabal install -j --disable-tests criterion
      

      执行

      ghc -O2 --make allbutlast.hs
      ./allbutlast --output allbutlast.html
      

      样本报告:Criterion report for allbutlast

      【讨论】:

        猜你喜欢
        • 2018-03-10
        • 1970-01-01
        • 1970-01-01
        • 2011-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-13
        相关资源
        最近更新 更多