对于Haskell还是很新的。我正在尝试使用递归编写排序函数,而这是我到目前为止所拥有的:
sort' :: [Int] -> [Int]
sort' [] = []
sort' [x] = [x]
sort' (x:y:xs) =
if x > y
then y:sort' (x:xs)
else x:sort' (y:xs)
根据我的判断,当要排序的元素少于2个时,它可以正常工作。但是,如果我输入[3,2,1],则会得到[2,1,3]。我尝试手动按照输入的路径进行操作,但无法弄清楚如何对两个以上的初始元素进行排序。我已经读了一些书,并且正在考虑涉及iterate
并使用列表长度(length
)的问题,但是我不确定如果它可以工作怎么实现。
答案 0 :(得分:2)
您的sort'
实现了冒泡排序的迭代,因此,一遍又一遍地应用它会得到一个完全排序的列表。有many ways of doing that,其中一个是使用iterate
重复应用,然后使用!!
选择第N个应用:
completeSort list = iterate sort' list !! length list
答案 1 :(得分:0)
正如其他人指出的那样,您可以使用iterate
将sort'
应用于列表n
次,这是在Haskell中使用高级组合器的绝妙示例(与原始递归相反)。另一个有用的组合器是until
:until
将使用谓词:: a -> Bool
,函数:: a -> a
和值:: a
,并将该函数应用于该值,直到谓词保持为真。因此,当您遇到类似情况但又不知道要在输入中进行多少次传递时,可以执行类似until isSorted sort' xs
的操作,
Haskell中还有许多其他的一遍排序算法也很不错,包括自上而下和(特别是自下而上)合并排序。
冒着把您带入理论深处的风险,我想提一下另一种相当不错的高层组合方式,用于考虑n
次的迭代。这基于“递归方案”的思想,其中的基本思想是递归数据类型的结构告知有用的模式以进行处理/转换。碰巧的是,Haskell足够强大,可以表达很多这样的想法。递归方案最简单的例子之一是基于自然数:
data Nat = Zero | Succ Nat
这是一种递归数据类型,因为第二个数据构造函数本身返回了Nat
的引用。有一种非常自然的处理Nat
的方式,也就是说:当我拥有Zero
时,我应该返回一些值;否则,我应该递归处理Nat
,然后使用这些值得出一个新值。碰巧的是,我们可以用另一种方式写Nat
,这使得通常很容易做到这一点:
data NatF a = Zero | Succ a
type Nat = Mu NatF
现在,NatF
数据类型可以在其“递归”组件中保存任何类型的值,而不仅仅是另一个Nat
。第二行中的Mu
类型构造函数基本上类似于fix
函数,但是对于类型:它实例化NatF (NatF (NatF (NatF ...)))
以便Nat = NatF Nat
,这与我们之前的定义相同。
但是,由于通用性的提高,我们现在可以写下一种类型,该类型描述如上所述处理Nat
的函数:NatF a -> a
。当给定Zero
时,此类型的函数仅返回一些a
;给定Succ
时,它可以访问在“内部” a
上递归调用时返回的Nat
。事实证明,可以概括执行此操作所需的基础架构,以便它可以在以上述样式编写的任何类型上工作(即作为在递归组件上概括的类型的固定点)。这意味着您可以根据递归调用的结果为函数编写方程式,然后在此数据结构上调用cata
,而不必费心写下重复的无聊递归调用!
这还意味着将您的“应用排序”(长度xs)时间写入xs的另一种方法是:
sort xs = cata phi (toNat $ length xs)
where phi Zero = xs
phi (Succ xs) = sort' xs