我刚刚在学习Haskell,有点卡住了。我想比较列表元素,并测量它们之间的差异,然后返回最高的一个。不幸的是,我不知道如何处理这个问题。通常,我只会迭代列表并比较邻居,但这似乎不是Haskell的做法。我已经尝试过使用map
,但正如我所说,我真的不知道如何解决这个问题。我会感谢你的每一种建议!
向致以最良好的祝愿
编辑:我的想法是首先压缩所有像这样的双pairs a = zip a (tail a)
。然后我想得到所有的差异(也许是map
?),然后选择最高的一个。我就是无法处理Haskell语法。
我不知道你所说的"测量列表元素之间的差异"是什么意思,但如果你想计算列表中的"最大"元素,你可以使用内置的maximum
函数:
maximum :: Ord a => [a] -> a
这个函数获取一个可以排序的值列表,包括所有的数字、字符和字符串。
如果你想得到最大值和最小值之间的差,你可以使用类似的函数minimum
,然后减去两者。当然,可能有一个稍微快一点的解决方案,即只遍历列表一次,或者对列表进行排序,然后获取第一个和最后一个元素,但在大多数情况下,执行diff xs = maximum xs - minimum xs
足够快,对其他人来说最有意义。
所以你要做的是计算连续元素之间的差,而不是计算每个元素的最小值和最大值。您不需要直接索引,而是使用一个名为zipWith
的方便函数。它采用一个二进制操作和两个列表,并使用该二进制操作将它们"压缩"在一起。所以类似的东西
zipWith (+) [1, 2, 3] [4, 5, 6] = [1 + 4, 2 + 5, 3 + 6] = [5, 7, 9]
它相当方便,因为如果其中一个列表提前用完,它就会停在那里。所以你可以做一些类似的事情
diff xs = zipWith (-) xs ???
但是我们如何将列表偏移1呢?好吧,简单(安全)的方法是使用drop 1
。你可以使用tail
,但如果xs
是一个空列表,它会抛出一个错误并使你的程序崩溃,但drop
不会
diff xs = zipWith (-) xs $ drop 1 xs
就是一个例子
diff [1, 2, 3, 4] = zipWith (-) [1, 2, 3, 4] $ drop 1 [1, 2, 3, 4]
= zipWith (-) [1, 2, 3, 4] [2, 3, 4]
= [1 - 2, 2 - 3, 3 - 4]
= [-1, -1, -1]
这个函数将返回正值和负值,我们只对幅度感兴趣,所以我们可以使用abs
函数:
maxDiff xs = ??? $ map abs $ diff xs
然后使用我上面强调的功能:
maxDiff xs = maximum $ map abs $ diff xs
你完了!如果你想成为一个幻想,你甚至可以用无点表示法写这个作为
maxDiff = maximum . map abs . diff
现在,这实际上会在一个空列表中引发一个错误,因为maximum []
抛出了一个错误。但我会让您找到解决这个问题的方法。
正如bhekliler所提到的,maximum
是一个快速而简单的解决方案。
如果你想要一些背景,这里有一些。我们试图做的是获取一个值列表,并将其简化为一个值。这被称为折叠,并且可以使用(除其他外)具有签名foldl :: (a -> b -> a) -> a -> [b] -> a
的foldl
函数。
foldl
的(a -> b -> a)
部分是一个取两个值并返回第一种类型之一的函数。在我们的情况下,这应该是我们的比较函数:
myMax :: Ord a => a -> a -> a
myMax x y | x > y = x
| otherwise = y
(请注意,Ord a
是必需的,以便我们可以比较我们的值)。
所以,我们可以说
-- This doesn't work!
myMaximum :: Ord a => [a] -> a
myMaximum list = foldl myMax _ list
但什么是_?为这个函数指定一个起始值是没有意义的,所以我们转而使用foldl1
,它不需要起始值(而是从列表中获取前两个值)。这使得我们的最大功能
myMaximum :: Ord a => [a] -> a
myMaximum list = foldl1 myMax list
或者,以无点格式,
myMaximum :: Ord a => [a] -> a
myMaximum = foldl1 myMax
如果你看看Data.List
中最大值的实际定义,你会发现它使用了同样的方法。
map
将函数映射到列表上。它将列表中的每个CCD_ 23转换为一个CCD_。
你想要的是找到两个邻居之间最大的差异,这是仅靠map
无法做到的。我假设你现在只看数字,因为这更容易。
diffs :: (Num a) => [a] -> [a]
diffs [] = []
diffs [x] = []
diffs (x1:x2:xs) = abs(x1-x2) : (diffs$x2:xs)
mnd :: (Num a, Ord a) => [a] -> a
mnd [] = 0
mnd [x] = 0
mnd xs = maximum$diffs xs
因此,diffs
一次取一个列表项,得到它和它的邻居之间的绝对差,然后把它放在它创建的列表的前面(:
操作符把一个单独的元素放在列表的前面)。
mnd
只是maximum$diffs xs
的一个包装器,用于阻止抛出异常。