12.2.2 无穷序列

在前一章,我们简单演示过使用延迟值,实现延迟列表。这种数据结构可以用来创建无穷数据结构,比如,从零开始的整数列表。这之所以可能,是因为每个元素的计算被推迟了:只在访问元素时,才计算它的值,并且,每次只关注一个元素的计算。

使用seq<‘a> 表示序列是相似的。该接口有一个方法MoveNext,计算出下一个的元素。序列可能是无穷的,即,MoveNext 方法始终能够计算出下一个元素,并永远不会返回false (表示序列结束)。无穷序列听起来可能有点奇怪,但我们将看到,它可能很有价值,把算法划分成不同部分,使代码更具可读性。

在第四章,我们讨论过图表的绘制,使用随机颜色填充每一个部分,但这并不总是有最好的结果。我们可以用无穷序列来表示图的颜色。在清单12.5 中,我们首先生成随机颜色的序列,很快就会看到有其他的方法。

清单12.5 在C# 和F# 中生成随机颜色的无穷序列

// C# version using loops

IEnumerable<Color> RandomColors(){

varrnd = new Random();

while(true){     [1]

intr = rnd.Next(256), g = rnd.Next(256), b = rnd.Next(256);

yieldreturn Color.FromArgb(r, g, b);     [2]

}

}

// F# version using recursion

let rnd = new Random()

let rec randomColors = seq {     [3]

letr, g, b = rnd.Next(256), rnd.Next(256), rnd.Next(256)

yieldColor.FromArgb(r, g, b)     [4]

yield!randomColors }    [5]

两个实现都包含生成颜色的无限循环。在C# 中,循环使用while(true) 实现[1];函数式方法创建无限循环是使用递归[5]。在无限循环的主体中,我们产生一个随机生成的颜色值。在F# 中,我们使用yield 结构[4],在C# 中,我们使用yield return[2]。

如果编译F# 版本的代码,在递归调用的这一行会有警告[5],说递归引用将在运行时检查。我们在第八章讨论过这种警告,是通知我们,正在引用的值在自己的定义之内。在这里,代码是正确的,因为递归调用将在序列完全初始化之后进行。

清单12.5 在把F# 代码括在seq 块中的时候,使用了不同的缩进样式[3]。没有从新行开始,缩进整个主体,而是把seq 标识符,和左大括号放在行尾。在本书中的一些(程序)清单中,我们会使用这种方法,使代码更紧凑。在实践中,这两种方法均正确,可以自由选择一种,以使更具可读性为准。

现在,我们有了颜色的无穷序列,就可以使用了。清单12.6 表明,无穷序列能够更好地分隔相信部分。这里只有F# 代码,C# 版本(非常类似)在本书的网站可以找到。

清单12.6 使用颜色序列绘制图表(F#)

open System.Drawing

open System.Windows.Forms

let dataSource = [ 490; 485; 450; 425; 365;340; 290; 230; 130; 90; 70; ]

let coloredSequence = Seq.zip dataSource randomColors    [1]

let coloredData = coloredSequence |> List.ofSeq    [2]

let frm = new Form(ClientSize = Size(500, 350))

frm.Paint.Add(fun e –>

e.Graphics.FillRectangle(Brushes.White,0, 0, 500, 350)

coloredData|> Seq.iteri(fun i (num, clr) –>     [3]

usebr = new SolidBrush(clr)

e.Graphics.FillRectangle(br,0, i * 32, num, 28) )  <-- 用索引确定色块的位置

)

frm.Show()

为了提供简短而完整的示例,我们只手工定义了几个数字数据,使用Seq.zip 函数,把随机生成的颜色组合起来[1]。这个函数取的参数为两个序列,返回一个元组序列:每个元组的第一个元素来自第一个序列,第二个元素来自第二个序列。这样,在我们的示例中,每个元组包含的数字分别来自数据源和随机生成的颜色。所返回序列的长度,是两个给定序列中较短序列的长度,所以,它将为每个数值生成一个随机颜色,然后停止。就是说,我们只用到有限数量的颜色。那么,如果我们可以生成了一百种颜色,但是,有人给了我们101 个数字,怎么办呢?无穷序列优雅地解决了这个问题,而无需担心长度。

在使用序列之前, 我们要把它转换为列表[2]。这样做是因为,随机颜色序列不纯(pure。这是什么意思?),每次重新计算时,返回不同颜色。因此,如果我们在使用之前,没有把它转换成列表,在每次重绘窗体期间,会得到不同的颜色。如果我们有了颜色数据的列表,只需要遍历其中的元素,并绘制色块,就可以了。我们将使用Seq.iteri 函数[3],它为每个元素调用指定的函数,把元素在序列中的索引和元素本身传递给函数。我们使用元组模式,立即把元素分解成数字值(色块的宽度),并生成颜色。

本示例之所以有意义,是因为我们可以很方便地使用另一种方式生成颜色。如果我们na?vely (又出现这个字了。是正常的意思吗?)实现它,颜色在绘图函数中计算[3],这可能会使改变使用的颜色,相对困难。但是,清单12.6 中的解决方案把颜色生成代码,与绘图代码中完全分离开来,这样,只需要提供不同的颜色序列,我们就能改变图表绘制的方式了。清单12.7 显示另一种配色方案。

清单12.7 生成渐变颜色序列(C# and F#)

// C# version using loops

IEnumerable<Color> GreenBlackColors(){

while(true){     [1]

for(intg = 0; g < 255; g += 25)

yield return Color.FromArgb(g / 2, g, g / 3);

}

}

// F# version using loop and recursion

let rec greenBlackColors = seq {

forg in 0 .. 25 .. 255 do     [2]

yieldColor.FromArgb(g / 2, g, g / 3)

yield!greenBlackColors }    [3]

在12.7 中的代码再次包含了无限循环,使用while 循环[1]和递归[3]实现。在循环体中,我们生成了渐变颜色,包含10 种不同颜色。我们使用for 循环生成的绿色部分,并计算出蓝色和红色部分(三原色吧)。本示例还展示了,生成具有指定步长的、数字序列的F# 语法[2]。g 的值从0 开始,每次迭代增加25,直到值大于250 为止。图12.1 显示了最终结果。

图12.1 使用渐变颜色作为生成的颜色序列,绘制图表

可以发现,无穷序列在实际编程中非常有用,因为它能很容易地把可能要在以后更改的代码部分,从中分解出来。从理论角度来看,无穷序列也很有意义;在Haskell 中,它经常用来表示数值计算。

到此,我们已经讨论了创建序列的绝大部分内容,现在,要看一下如何处理序列了。

Haskell 中的无穷列表和 F# 的缓存序列

我们在第十一章提到,Haskell 完全使用延迟计算。我们知道F# 中的Lazy<‘a> 可以在需要时模拟延迟计算值,序列能够以同样的方式,模拟一些其他的Haskell 结构。我们来看一下稍微晦涩的例子,只是为了找到一种感觉而已。在Haskell 中,我们可以写下面的代码:

let nums = 1 : [ n + 1 | n <- nums ]

只要我们把它译成F#,你就能理解了。标准的Haskell 函数式列表是延迟的(因为完全是),: 运算符对应于F# 的:: 运算符,中括号中的表达式返回列表中的所有数字,每次加1。在F# 中,我们可以使用序列表达式,写出同样的代码:

let rec nums =

seq{ yield 1

for n in nums do yield n + 1 };;

代码构造的序列从1 开始,并递归地从序列中取所有数字,每次递增1。这样,返回的序列将包含数字1,2,3,等等。F# 版本效率太差,因为每次递归调用,都从第一元素开始构建新的序列。要计算出长度为3 的序列,就会创建三个nums 的实例,一个长度为3,一个长度2,一个长度为1。

在F# 中,代码的惯用版本可能看起来不一样。就像在清单12.4 中生成阶乘一样,我们可以实现一个工具函数,生成给定大小的序列;然后,递归地调用,使用优化的基本操作yield!。而在Haskell 中,情况就不同了,因为计算的值被缓存了,这样,就不必从头开始重新计算序列。我们要在F# 中得到类似的效果,可以使用Seq.cache 函数:

let rec nums =

seq{ yield 1

for n in nums do yield n + 1 } |> Seq.cache;;

Seq.cache 函数返回的序列,缓存了已经计算过的值,所以,此版本的代码执行会更为聪明。访问第1000 元素,有缓存的版本要比原始的速度快大约100 倍。组合缓存和序列表达式,能够与面向数学的Haskell 具有相同的表现力。然而,寻找F# 惯用的解决方案,通常是更好的主意,比如,这里使用的yield!。

时间: 2024-11-11 00:32:41

12.2.2 无穷序列的相关文章

Tyvj-TOM的无穷序列

背景 蛟川书院模拟试题 描述 TOM有一个无穷序列中如下:110100100010000100000.....请你帮助TOM找出在这个无穷序列中指定位置上的数字 输入格式 第一行一个正整数N,表示询问的次数:接下来的N行一个正整数Ai,Ai表示在序列中的位置. 输出格式 N行,每一行为0或1,表示序列第Ai位上的数字. 测试样例1 输入 4 3 14 7 6 输出 0 0 1 0 备注 对于100%的数据有N<=1500000,Ai<=10^9提示:差值为1的等差数列   Sn(前n项和)=n

Oracle基础 12 对象 objects 同义词/序列/试图/索引

--创建同义词create public synonym employees for hr.employees;  --公共同义词需要 create public synonym 权限 表的所有用户授予公共权限  grant select on employees to public; create synonym t1_s for t1;   --私有同义词如果想在不同的环境中通过不同的别名引用同一个表时,要创建私有同义词.  编译同义词 alter synonym t1_s compile;

12.3.3.1 序列表达式中的平面映射

假设我们有一个关于城市的元组列表,每个元组包含城市的名字和它所在的国家,另外,我们还有一个列表,是用户所选的城市.因此,我们可以这样表示样本数据: let cities = [ ("New York","USA"); ("London", "UK"); ("Cambridge","UK"); ("Cambridge", "USA") ] let e

有用函数编程

<序> 感谢 关于本书 关于封面 第一部分 学习函数式思维 第一章 不同的思维 1.1 什么是函数式编程? 1.2 通往有用函数编程之路 1.3 用函数式编程提高生产力 1.3.1 函数范式 1.3.2 声明式编程风格 1.3.3 了解程序的执行 1.3.4 设计并发友好的应用程序 1.3.5 函数风格怎样形成代码 1.4 函数式编程演示样例 1.4.1 用声明式风格表达意图 1.4.1.1 用 LINQ 处理数据 1.4.1.2 用 XAML 描写叙述用户界面 1.4.1.3 声明式函数动画

12.2.1 递归的序列表达式

函数式编程中主要的控制流结构是递归.我们已经在很多例子中,写的普通函数就使用过递归,它能够解决命令式编程中的循环问题,而不需依赖可变状态.当我们想写一个简单的递归函数时,要使用 let rec 关键字,这样,就能函数以递归方式调用自身. 用于组合序列的 yield! 结构,也可以在序列表达式中执行递归调用,所以,我们同样可以使用函数编程的方法,生成序列.清单 12.4 生成所有的小于 1 百万的阶乘数,与清单 12.1 的 C# 示例一样. 清单 12.4 使用序列表达式生成序列数 (F# In

python核心编程第五章练习-5.17-随机序列

5-17  生成一个有 N 个元素的由随机数 n 组成的列表, 其中 N 和 n 的取值范围分别为: (1 <N <= 100), (0 <= n <= 2**31 -1).然后再随机从这个列表中取 N (1 <= N <= 100)个随机数出来, 对它们排序,然后显示这个子集. #!/usr/bin/python import random N = random.randint(2, 100)  #随机获取一个指定范围内的整数 randlist = random.sa

《University Calculus》-chaper8-无穷序列和无穷级数-等比级数

前言:其实无穷序列和无穷级数和数列{an}以及我们接触微积分就给出的极限概念lim有着紧密的联系,它对于我们在具体的问题当中进行建模和数据分析有着非常重要的作用. 无穷序列: 最简单的一种说法,就是一个有无限项的数列{an}.由于其项数无限,我们就可以去分析随着数列下标n的增加,an的敛散性,这就和极限联系了起来.包括极限的运算法则夹层定理,在这里都有用武之地. 无穷级数: 一个很简单的说法,就是将无穷序列加和,然后分析其敛散性. 在不同的实际工程问题中我们将会面临不同的无穷级数,因此在这之前数

第十二章 序列表达式和可选工作流

第十二章序列表达式和可选工作流 本章介绍 ■生成和处理序列值 ■处理 F# 序列表达式 ■理解单子和 LINQ 表达式 ■实现 F# 计算表达式 在我们开始讨论序列表达式之前,必须知道什么是序列(sequence),这也是数学的F# 术语.序列是有序的列表,可能包含无穷的元素.这一切听上去有点抽象,但也不用担心,我们已经熟悉这种类型了,在.NET 中表达同样概念的是:IEnumerable<T>. 在.NET 框架中有IEnumerable<T> 类型的主要原因,是它提供一种统一的

12.3.3.2 直接使用平面映射

首先,我们要看看平面映射到底是什么样子.通常,理解函数如何运行的第一步,就是研究类型签名.图 12.2 比较了 Seq.map(普通映射)和 Seq.collect(平面映射)的类型签名. 图 12.2 对于每个输入元素,普通映射返回一个元素,而平面映射,可以返回元素的任意集合. 提醒一下,类型签名中的 # 号,描述映射函数,传递给 collect,表示函数的返回类型不必一定是 seq <'b> 类型.在前一章,我们讨论过使用 # 号的类型声明,#seq<'b> 位置上可以用实现了