我想在列表中的链式元素对上映射函数。考虑清单:
a = [1,2,3,4,5]
通过使用例如函数f a1 a2 = a1 < a2
,我想得到:
[True,True,True,True]
以下似乎总体上起作用:
zipWith f a (tail a)
然而,我发现它有点像黑客。有没有更合适的方式,也许使用折叠?
答案 0 :(得分:6)
如果我告诉你在Haskell中这是最惯用的方式,我想我可以代表Haskell社区发言。你可以使用折叠来一起破解某些东西,但它显然不像第一个变体那样可读:
f p xs = snd $ foldl (\acc x -> (x, snd acc ++ [p (fst acc) x])) (head xs, []) (tail xs)
感谢@WillNess提供上述功能的更具可读性的版本,尽管它仍然没有简洁明了地打败zipWith
。
f p = foldr g [] . tails where g (x:y:_) r = p x y:r; g _ _ = []
这真是个hackish。在这种情况下使用折叠的问题在于,通常元素将被单独处理,并且它们不“知道”它们的邻居。他们知道的唯一值是累加器和它们自己的值。
在此示例中,执行f (<) [1,2,3,4,5]
将检查对中的左侧元素是否小于之前的元素,但方式的可读性低于仅执行此操作:
f p xs = zipWith p xs (tail xs)
用法与上述相同。
请注意,我不是Haskell语言的专业人士,所以可能这种使用折叠的方法可能是以更好的方式编写的,但是它永远不会超越zipWith
方法的优雅。
答案 1 :(得分:3)
您可以在State仿函数上使用Traversable实例来实现预期的结果,如下所示:
traverseSt :: Traversable t => (a -> a -> b) -> a -> t a -> t b
traverseSt f x ta = runState (traverse (\a -> get >>= \a' -> f a a'<$put a) ta) x
然后,您可以将此函数专门用于处理非空列表(因为您需要知道至少一个元素以提供第一个种子值),如下所示:
zipSelf :: (a -> a -> b) -> [a] -> [b]
zipSelf f (a:as) = traverseSt f a as
zipSelf f [] = error "Impossible use of zipSelf on empty structure"
以这种方式拆分代码,您也可以在其他结构上定义zipSelf
,例如Tree
或Maybe
s,或者第一个元素易于提取的任何类型。
Cheerio,
答案 2 :(得分:2)
你可以写一个像
这样的函数movingApply :: (a -> a -> b) -> [a] -> [b]
movingApply f (x:y:xs) = f x y : movingApply f (y:xs)
movingApply f _ = []
(随意选择一个更好的名字,这个很糟糕),但是当你展开它时,这在计算上与zipWith f x (drop 1 x)
相同。请注意,我使用的是drop 1
而不是tail
,这是因为tail
是部分函数,而tail []
导致运行时错误{{1} }返回drop 1 []
。由于这与[]
种类几乎完全相同,我更喜欢zipWith
,因为它是单行的,我不必手动进行递归
答案 3 :(得分:1)
您可以使用mapAccumL
import Data.List
a = [1,2,3,4,5]
compareAdjacent = snd $ mapAccumL (\acc x -> (x,(x > acc))) 0 a
这会产生输出[True,True,True,True,True]。如果您希望它忽略第一个列表项,您可以创建如下函数:
compareAdjacent' (x:xs) = snd $ mapAccumL (\acc x -> (x,(x > acc))) x xs
compareAdjacent' [] = []