什么是无限数据结构的引人注目的用例?

时间:2011-03-12 18:15:40

标签: data-structures haskell clojure scheme lazy-sequences

有些语言(Haskell,Clojure,Scheme等)有懒惰的评价。懒惰评估的“卖点”之一是无限数据结构。这有什么好处的?能够处理无限数据结构的情况的一些例子显然是有利的吗?

6 个答案:

答案 0 :(得分:20)

以下是两个例子,一个大一小:

国际象棋比赛中,约翰·休斯(John Hughes)有一个很好的例子。{p> Why Functional Programming Matters。国际象棋游戏的移动树实际上并不是无限的,但它足够大以至于它可能是无限的(称之为“近无限”)。在严格的语言中,您实际上不能将其视为树,因为没有足够的空间来存储整棵树。但是在惰性语言中,您只需定义树,然后定义“nextMove”函数,以便在必要时遍历它。懒惰的评估机制负责细节。

小例子只是将索引号与列表中的每个项相关联,因此[“foo”,“bar”,“baz”]变为[(1,“foo”),(2,“bar”) ),(3,“baz”)]。在严格的语言中,您需要一个循环来跟踪最后一个索引,并检查您是否在最后。在Haskell中你只需说:

zip [1..] items

zip的第一个参数是无限列表。您不需要计算出提前需要多长时间。

答案 1 :(得分:14)

我能想到的一些优点:

  • 清洁代码 - 值得注意的是,生成无限序列的代码通常比在有界数据上运行的代码更简单,更清晰。这是因为这样的代码通常更接近基础数学定义。
  • 灵活性 - 而不是提前决定您的数据需要多大,如果您使用懒惰的无限结构编写它,您根本不需要担心。它将“正常工作”
  • 性能 - 如果您正确使用懒惰,您可以通过仅在需要时计算数据值来使用它来提高性能 - 而且可能根本不会。因此,您可以避免大量不必要的计算。
  • 无限进程的抽象 - 有趣地使用无限延迟序列作为事件流的抽象,例如随时间从网络连接读取输入。这可以创建一些非常优雅和有趣的方法来创建处理事件流的代码。例如请参阅Clojure中的stream-utils库。
  • 更好的算法 - 有些算法可以通过无限的数据结构更容易表达 - 这个想法是你懒洋洋地“拉入”你需要的解决方案的部分而留下其余的如果使用这种方法可以减少算法的时间复杂度(例如从O(n^2)O(n log n)),那么这可能是一个很大的胜利。

答案 2 :(得分:7)

有规范的纯记忆策略:

fib = (map fib' [0..] !!)
    where
    fib' 0 = 0
    fib' 1 = 1
    fib' n = fib (n-1) + fib (n-2)

我们将fib'函数映射到无限列表,以构建所有fib的表。瞧!便宜,易记忆。

当然,这在参数中具有线性查找时间。您可以用无限trie替换它以获得对数查找时间。比照data-inttrie

答案 3 :(得分:6)

我打算对@ knivil的计划发表评论。相反,我会把它作为另一个答案。

懒惰的数据结构不是唯一的方式来完成大多数任务。这可能会激怒Pythonistas。但我相信程序员可以选择使用哪种技术。懒惰的技术是强大而优雅的。

Knivil提到使用Scheme的iota。看看编写完整方法(包含所有3个参数)是多么容易,依赖于懒惰:

iota count begin step = let xs = begin:map (+step) xs
                        in take count xs

-- or, alternately
iota count begin step = take count $ map ((+begin).(*step)) [0..]

我还可以通过滥用懒惰来为非空列表编写length

len = fst . last . zip [1..]

-- or even handling empty lists
len = fst . last . zip [0..] . (undefined:)

考虑Prelude中定义的强大而优雅的iterate函数。

iterate f x = x : iterate f (f x)

它创建无限列表[x, f x, f (f x), f (f (f x)), ...]。我可以根据iota

撰写iterate
iota count begin step = take count $ iterate (+step) begin

懒惰的方法是一种优雅的编程方式。这不是唯一的方式,习惯于C或Java的人肯定会喊出“但我不会需要懒惰,我只能_”,而且他们是正确的。如果您的语言是Turing-complete,则可以完成。但懒惰可以如此优雅。

答案 4 :(得分:2)

好吧,上个月我有一个很好的用例。复制对象时,我需要一个用于唯一名称的生成器。这意味着,生成器采用原始名称X,并为副本生成新名称。它通过附加像

这样的文本来做到这一点
X - copy
X - copy (2)
X - copy (3)
...

只要在同一组内的对象集中未使用该名称。使用“无限数据结构”(无限数组的字符串)代替简单循环有一个好处:如果名称已在使用中,您可以将名称生成部分完全与测试分开。因此,我可以将生成器函数重用于不同类型的对象,其中每个对象类型的使用中测试略有不同。

答案 5 :(得分:2)

无限数据结构提供(可计算的)实数的优雅表示。例如,像

这样的无限列表
[(0, 4), (1, 3.5), (3.1, 3.2), ...]

可以代表pi。由于内置的​​懒惰,操作这种表示变得容易。