TDA 452型
DIT迪特142
2016年HT

2016年功能编程
第2周的练习

第2周练习:递归和数据类型

以下是一些旨在帮助你练习写作和推理的练习关于递归定义和数据类型。

其他有用的练习可以请参阅本书第3章和第4章。

如果你没有时间做所有这些练习,不用担心。这些练习旨在提供足够的工作来保持经验丰富的学生很忙。如果你做了所有标有(*)的练习可能理解了本周的内容。

祝你好运!

1.(*)最大值函数

完成以下函数定义:

--maxi x y返回x和y的最大值

(我们称此函数为maxi,因为有一个标准函数max做同样的事情。当然,您不应该使用它。)

首先为maxi写一个类型签名,左边写一个“等于undefined”。在编写定义之前,请考虑至少一个属性您将使用测试代码。

您需要考虑两种情况:它们是什么?写下左手边,并确保Hugs接受它们。

完成您的定义并使用QuickCheck进行测试。

2.平方和。

定义一个函数,用于计算从1到n的数字的平方和。

--sumsq n返回1*1+2*2+…+n*n个

提示:使用递归从sumsq(n-1)计算sumsq n;不要使用一个公式(例如下面的公式)来计算这个值,而不需要递归。

有人说sumsq n等于n(n+1)(2n+1)/6。使用QuickCheck调查这是否成立。

3.(*)河内塔

河内塔是一个古老的谜,由一系列戒指组成不同尺寸的,以及安装在底座上的三个支柱。一开始戒指如图所示位于最左边的柱子上,目标是将它们全部移动到最右边的柱子,通过一次将一个环从一个柱子移动到另一个柱子。但是,任何时候都不能把大戒指放在小戒指的上面!

[河内塔:一个钉有8个环,两个空钉]

你能找到一种基于递归的解谜策略吗?那就是,如果你已经知道如何将n-1个环从一个帖子移动到另一个,你能找到吗移动n个环的方法?

如果你尝试一下你的策略,你会很快发现比如说,用五个圆环来解决这个难题需要很多步。你能定义Haskell函数

河内
它计算将n个环从一个柱移动到另一个使用你的策略?解决这个难题需要多少步十个戒指?传说原来的谜题有32个就在此时,一座修道院的布达迪斯僧侣正在解决这个问题。当拼图完成时,世界将终结。

难度更大(只有当你想要挑战时才这样做):现在假设我们在谜题中添加第四个帖子。你能想出一个策略吗哪一个利用了第四个帖子?定义Haskell函数以计算你的新策略需要多少步才能解决这个难题。有多少步现在需要用十个环来解决这个难题吗?离结束还有多远如果僧侣们在他们的谜题中添加了第四个帖子,这将是世界的奇迹吗?

你能解释一下这种行为吗?提示:你的递归是什么类型的使用两种策略?

4.斐波那契数

这个斐波那契数由定义

F类0=1
F类1=1
F类n个+2=F类n个+1+F类n个

因此,值的顺序从1、1、2、3、5、8……开始。。。

编写递归函数

--fib n计算第n个斐波那契数
基于上述数学定义。用它来计算第10、15、,第20、25和30斐波那契数。你注意到了什么?你能解释一下吗行为?

更困难:有一种更快的方法来计算斐波那契数。假设我们定义了一个函数fibAux,它满足

fibAux i(fib n)(fib(n+1))==fib(n+i)

请注意,这是一个定义,它是一个属性!

来自此属性你能看看我们怎么做吗重新定义fib依据使用fibAux?(提示:尝试在属性中将n设置为0)。

有可能得到由此递归定义fibAux仅使用代数的属性:我们想要的两种情况是

fibAux 0 a b=。。。fibAux i a b | i>0=。。。

看看你是否可以用这个属性来计算右手边是什么应该是。

使用QuickCheck测试fib的新定义(以fibAux表示)满足上述属性。

使用新版fib计算第10、15、20、25和30斐波那契数。你注意到了什么?

用手计算fibAux 4 1 1。如果您以前编程过,请观察i、a和b的值在连续递归调用中的变化方式。真的吗让你想起了什么?

5.因素。

素数p只有两个因子,即1和p本身。复合数字具有不止两个因素。定义函数

最小因子n

它返回n中大于1的最小因子。例如,

最小因子14==2
最小因子15==3

在编程smallestFactor之前,至少编写两个QuickCheck它应该满足的属性。您需要整数的函数除法和余数:研究标准函数div公司国防部为此目的。

提示:使用辅助函数nextFactor k n写入smallestFactor,该函数返回n的最小因子大于k。您可以使用定义smallestFactornextFactor和nextFactor。写入的QuickCheck属性nextFactor,然后再定义它。

现在定义

num系数n

它计算范围1..n内n的因子数。

对于以下练习,您可以使用内置的Haskell列表,或者(如果你想的话)我们在讲座中使用的列表类型。这些类型是基本相同,只是语法略有不同。了解更多关于内置Haskell列表的语法,请阅读这本书。

6.(*)乘法列表元素

定义函数

乘法::数字a=>[a]->a
将列表中的所有元素相乘。(想想:它应该是什么空列表的值为?)。例如
乘以[1,2,3,4,5]120

(这实际上是一个标准函数,称为product)。

您可以对我们在讲座中看到的List数据类型执行以下操作:

数据列表a=空|添加a(列表a)
因此,您的函数具有以下类型:
乘法::List Integer->Integer

7.避免重复

在许多情况下,列表不应包含重复元素。对于例如,一组卡片不应包含同一张卡片两次。定义一个功能

重复项::等式a=>[a]->Bool

它返回真的如果其参数包含重复的元素。

重复[1,2,3,4,5]>False(错误)重复[1,2,3,2]真的

提示:标准函数元素,用于测试元素是否出现在列表中,在这里很有帮助。

一种方法确保不包含重复项的列表以列出可能包含重复元素的列表,然后删除它们。定义函数

删除重复项::等式a=>[a]->[a]
它返回一个列表,其中包含与其参数相同的元素,但没有重复项。使用以下属性进行测试:
prop_duplicatesRemoved::[Integer]->布尔prop_duplicatesRemoved xs=not(重复项(removeDuplicates xs))

该物业是否保证删除重复项行为正确吗?如果没有,缺少什么?

(删除重复项实际上是一个标准函数,称为节点).

8.测试

采用以下定义rankBeats公司从演讲中,破坏它更改其中一个False(错误)结果到真的,反之亦然。rankBeats公司应该揭示错误。您的任务是定义一个或多个的属性rankBeats公司,对于正确的定义来说是正确的,但使您能够使用快速检查。

(注:因为QuickCheck随机选择测试数据,所以它总是可能在前100个测试用例中没有发现错误。如果您希望如果没有发现错误,只需继续测试相同的属性即可。如果真的是这样False,则QuickCheck最终应找到失败的测试用例)。

9.(*)定义类型

定义数据类型月份表示月份和函数

天月份::月份->整数->整数
它计算一个月内的天数,同时给定年。(你可以忽略闰世纪之类的事情:只要假设第四年是闰年)。

定义数据类型日期,包含年、月和日,以及一个函数

有效日期::日期->Bool
如果日期中的日期介于1和一个月中的天数。