8

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

假设我们有一个列表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

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

4

5 回答 5

14

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

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

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

或者,你可以做

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

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

于 2014-09-15T16:41:57.147 回答
10

像往常一样,这是假装变形的工作:

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
于 2014-09-15T17:22:25.200 回答
4

如果你不关心性能initlast你也可以使用标准库(没有特殊导入)。

你可以写

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

在哪里

  • f: 你要映射的函数
  • list:您要映射的列表
于 2014-09-15T17:19:39.477 回答
1

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

我知道它不能回答你的问题,但我会考虑这样的类型

data Foo a = Foo a [a]

和一个合适的仿函数。

于 2014-09-15T19:23:54.907 回答
0

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

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

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

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

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

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

执行

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

示例报告:allbutlast 的标准报告

于 2014-09-17T11:34:24.157 回答