首页 > 解决方案 > 比较 Haskell 列表中的日期

问题描述

所以我创建了这个函数,它需要 2 个项目并比较它们并返回最大的日期。

biggerDate :: (Ord a1,Ord a2,Ord a3) => (a3,a2,a1)->(a3,a2,a1)->(a3,a2,a1)
biggerDate (x, x1, x2) (y, y1, y2) = 
  if x2 > y2
  then (x, x1, x2) 
  else if x1 > y1 then (x, x1, x2) else (y, y1, y2)

现在我正在尝试列出日期并比较所有日期以找到最大的日期。

我只有两个远的是

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)

我一直试图从列表中取出 2 个元素并进行比较。

标签: listdatehaskellcompare

解决方案


让我们从功能上思考,让类型引导我们完成这个过程。

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate xs = ???

我们列出一份清单,然后……做点什么。那么,第一个问题是:如果列表为空,我们希望发生什么?有几种方法可以处理这个问题,但出于我们的目的,我们只会说这是一个错误并发出错误信号

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate [] = error "Empty list of dates!"
maxDate (x : xs) = ???

好的,下一个最简单的情况是单元素列表。在那种情况下,最大的日期显然是唯一的。毕竟,我们只有一个选择。

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate [] = error "Empty list of dates!"
maxDate [x] = x
maxDate (x : xs) = ???

现在我们只剩下一般情况了:我们的列表中有几个元素。我使用模式匹配x : xs来提取第一个。我们现在要做的是找出哪个更大:第一个元素或列表的其余部分。我们可以通过递归找出列表其余部分中最大的元素是什么:maxDate xs. 然后我们需要弄清楚那个东西是大于还是小于第一个元素x。您已经编写了一个函数来执行此操作,所以让我们使用它。

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate [] = error "Empty list of dates!"
maxDate [x] = x
maxDate (x : xs) = biggerDate x (maxDate xs)

现在我们有了一个有效的实现。但我们不要止步于此。让我们变得更好。

首先,请注意我们可以对 上的任何二进制函数执行此操作(a3, a2, a1),而不仅仅是biggerDate. 让我们稍微抽象一下我们的函数并接受一个额外的参数

foldDate :: (Ord a1, Ord a2, Ord a3) => ((a3, a2, a1) -> (a3, a2, a1) -> (a3, a2, a1)) -> [(a3, a2, a1)] -> (a3, a2, a1)
foldDate _ [] = error "Empty list!"
foldDate _ [x] = x
foldDate f (x : xs) = f x (foldDate f xs)

我添加了一个额外的参数以使我们的函数在一般情况下更有用。现在,maxDate只是一个特例foldDate

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate = foldDate biggerDate

但我们可以走得更远,事实证明,foldDate根本不需要它的论点是日期。它适用于任何类型。

fold :: (a -> a -> a) -> [a] -> a
fold _ [] = error "Empty list!"
fold _ [x] = x
fold f (x : xs) = f x (fold f xs)

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate = fold biggerDate

现在,Haskell 中有一个非常有用的工具,叫做Hoogle。您可以将类型签名插入其中,它会告诉您与该签名匹配的内容是内置的还是在知名的 Haskell 库中。让我们插入我们抽象fold函数的类型:(a -> a -> a) -> [a] -> a. 第一个结果是一个名为 的函数foldr1。虽然源代码比我们在这里写的要复杂一些,但事实证明foldr1它确实是我们想要的确切功能。我们甚至不需要自己编写fold函数;它已经内置了。

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate = foldr1 biggerDate

伟大的!maxDate现在只是两个字。这很时髦。但我们也可以继续努力biggerDate。见,事实证明,Ord有一个三元组的实例,它自动从第一个元素开始排序,然后是第二个,然后是第三个。因此,如果您愿意以 YMD 顺序(而不是您现在正在做的 DMY)来定位您的日期,您biggerDate也可以变得更简单。

biggerDate :: (Ord a1,Ord a2,Ord a3) => (a3, a2, a1) -> (a3, a2, a1) -> (a3, a2, a1)
biggerDate x y = if x > y then x else y

如果您仍然希望以 DMY 格式打印日期,您始终可以data Date = Date Int Int Int使用自己的Show实例定义自定义数据类型 ( ),但现在我们将只使用 YMD,因为它使一些事情变得更简单。

好的,但再一次,让我们抽象一下。该函数对元组没有任何作用,所以它应该适用于任何Ord事情,对吧?

bigger :: Ord a => a -> a -> a
bigger x y = if x > y then x else y

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate = foldr1 bigger

好的,这个类型签名现在看起来很抽象。让我们再次跳到 Hoogle 。瞧,这个bigger函数也是内置的:它被称为max. 所以我们甚至不需要bigger.

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate = foldr1 max

让我们再次抽象一下。这个版本maxDate也没有真正使用元组,所以它的类型签名可能和Ord a => [a] -> a. 通过 Hoogle 的另一次旅行告诉我们,即使是该功能也可以在 Haskell中使用。所以我们最终的定义是

maxDate :: (Ord a1, Ord a2, Ord a3) => [(a3, a2, a1)] -> (a3, a2, a1)
maxDate = maximum

如果您知道如何使用您可以随意使用的抽象技术,那么该函数maxDate实际上内置于 Haskell 中的。

我如此详细地介绍所有这些的原因正是如此。准备好自己编写这些递归定义是很有价值的,尤其是在开始时,我建议你这样做。事实上,Data.List在学习 Haskell 时,仅仅通过标准库(尤其是一个金矿)并手动实现您在那里看到的功能是一个很好的练习。

但同样重要的是要注意标准库很好地捕获了许多常见的设计模式。您的“我有一个二进制函数,我想折叠一个列表”的模式被foldr1(及其折叠函数系列)捕获,更一般地说,您的“我想要这个列表中最大的东西,对于某些定义'最大'”被maximum函数捕获。这就是人们使用 Haskell 的原因。它的多态功能做了很多工作,并提供了一个相对较小的标准库(例如,与 Java 相比),基于它的大小,它比乍一看更有用,并且捕获了很多更高的-order 编程模式非常优雅。


推荐阅读