11.4.1 无穷列表

728 篇文章 1 订阅
30 篇文章 0 订阅
11.4.1 无穷列表



    这一节的标题可能听起来有点奇怪(或疯了),所以,我们会提供一个字面解释。我们用得很多的数据结构之一是函数式列表。我们也可能想要表示逻辑上无穷列表,例如,所有质数的列表。事实上,我们不会使用所有的数字,但可能会处理这样的数据结构,而不考虑长度。如果列表是无穷的,我们就能够访问我们需要尽可能多的数字。

    除了数学上的挑战,同样的概念在许多主流编程中也是有用的。当我们在第 4 章画饼图时,用的是随机颜色,但是,如果使用以无限列表方式生成颜色,使得图表外观清晰。在下一章中,我们将会看到所有这些例子,但现在,我们来向你展示如何将这种想法表示成使用延迟值。

    在内存中存储无穷数字列表,看起来是一个棘手的问题。很明显,我们不能完整地存储这种数据结构,所以,我们需要存储它的一部分,其余部分表示为一个延迟计算。如我们已经看到的,延迟值是一个好方法,表示延迟计算的部分。

    我们可以表示简单的无穷列表,其方式类似于普通列表。它是一个单元格,它包含一个值和列表中的其余部分。唯一不同的是,列表的其余部分将延迟计算,当我们执行它的时候,它给出另一个单元格。在 F# 中,我们可以使用差别联合来表示这种列表,如清单 11.19 所示。



Listing 11.19 Infinite list of integers (F#)



type InfiniteInts =
  | LazyCell of int *
                        Lazy<InfiniteInts>



    这个差别联合只有一个识别器,这意味着,它是类似于记录。我们可以用记录来写这段代码,但对于这个示例,用差别联合更方便,因为,我们可以用优美的模式匹配的语法,来提取其携带的值。这个唯一的识别器被称为延迟单元(LazyCell),它在单元格存储当前值,以及对"尾"的引用。尾是一个延迟值,所以,它将在需求时进行计算。用这种方式,当我们计算单元格时,可以逐个列表单元格进行,结果会被缓存。



F# 和 Haskell 中的延迟列表



    如前所述,Haskell 到处使用延迟计算。这意味着,在 Haskell 中,标准的列表类型自动就是延迟的。尾直到这个值在代码中访问时才计算。

    在 F# 中,延迟列表的使用并不非常频繁。在下一章,我们将会看到一种更优雅的写无穷集合,用 F#,也用 C# 2.0。F# 提供了延迟列表的一种实现,相似于我们在这一节实现的。你可以在 LazyList FSharp.PowerPack.dll 库中找到 LazyList<'a>。



    现在,我们已经有了自己的类型,让我们用它来创建一个简单的无穷列表,存储整数0、 1、 2、 3 … …。清单 11.20 也演示如何从这个列表中访问值。



Listing 11.20 Creating a list containing 0, 1, 2, 3, 4, … (F# Interactive)



> let rec numbers(num) =
     LazyCell(num, lazy numbers(num + 1));;
val numbers : int –> InfiniteInts

> numbers(0);;
val nums : InfiniteInts = LazyCell(0, Value is not created.)

> let next(LazyCell(hd, tl)) =
     tl.Value;;
val next : InfiniteInts –> InfiniteInts

> numbers(0) |> next |> next |> next |> next |> next;;
val nums : InfiniteInts = LazyCell(5, Value is not created.)



    我们首先写一个递归函数 numbers,它返回整数的无穷列表,从作为参数值给定的数字开始,直到无穷。返回的单元格包含第一个值和一个尾。这个尾是延迟值,在以递归方式调用 numbers 时,得到下一个单元格。

    如果我们以 0 作为参数值,调用这个函数,得到从 0 开始的无穷列表,F# Interactive 的输出并不具特别可读性,但你可以发现,第一个值是 0,尾是 <InfiniteInts> 类型的延迟值。随后的命令声明函数 next,给出列表中下一个单元格。我们用声明中的模式匹配来分解唯一的参数值。这看起来有点怪,因为你通常不使用只有一个识别器的差别联合,但其原理与分解元组的组件是相同。在函数体中,我们读取 Value 属性,计算下一个单元格。最后一行使用 next 函数几次,从列表中读第六次值。

    我们可以用延迟列表做更多的事情,但是,在这里我们不会更深入进去,在下一章,我们会看到更多 F# 常用的技术。有些情况下,LazyList<'T> 类型是很有用的。虽然我们没有直接使用 F# 类型库,使用它也不会有问题,现在你理解了原理。

    在这一节介绍无穷数据结构中,我们一直专注于更多的函数风格,而没有展示 C# 示例。现在,它有可能在 C# 中写相同的类型,我们知道如何在 C# 中写延迟值,但在下一章中,我们将会看到更自然的方式在 C# 中表示无穷结构,或者流的值。

    在此示例中,延迟列表有一个非常有趣的地方。一旦我们计算列表到一些点,计算的值在内存中仍可用,我们不必要每次重新计算。正如我们在下一节中将要看到,延迟值的这方面可以用于很简单而优雅的缓存机制。



写处理无穷列表的函数



    当使用标准的列表类型时,我们可以使用的函数,如 List.map 和 List.filter。我们也可以为无穷列表实现相同的函数,但是,当然,并非所有的都可以。例如,List.fold 和 List.sort 需要读所有元素,对于延迟列表,这是不可能的。这里的一个例子,说明什么是可能的,它实现了 map 函数:



let rec map f (LazyCell(hd, tl)) =
  LazyCell(f(hd), lazy map f tl.Value)



    其结构类似于正常的 map 函数。它把给定的函数应用到单元格中的第一个值,然后,以递归方式处理列表中的其余部分。尾的处理由于使用 lazy 关键字而延迟。其他常见的列表处理函数也类似。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值