lazylist 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +22 -2
- data/Rakefile +30 -30
- data/VERSION +1 -1
- data/examples/examples.rb +15 -1
- data/examples/pi.rb +1 -1
- data/install.rb +5 -2
- data/lib/lazylist.rb +413 -231
- data/lib/lazylist/enumerable.rb +112 -0
- data/lib/lazylist/list_builder.rb +137 -0
- data/lib/lazylist/version.rb +8 -0
- data/make_doc.rb +1 -1
- data/tests/runner.rb +1 -6
- data/tests/test.rb +61 -0
- metadata +54 -37
- data/TODO +0 -2
data/CHANGES
CHANGED
@@ -1,7 +1,27 @@
|
|
1
|
+
2007-04-03 * 0.3.0 * Major work on the implementation:
|
2
|
+
- Supports list comprehensions for infinite list like
|
3
|
+
Haskell does by offering a simple DSL.
|
4
|
+
- Added cartesian products to LazyList.
|
5
|
+
Many thanks to Reginald Braithwaite <reg@braythwayt.com>
|
6
|
+
for his interesting ideas & email discussion about this
|
7
|
+
topic. Be sure to check out his blog under
|
8
|
+
http://weblog.raganwald.com/, where he writes about all
|
9
|
+
kinds of other interesting stuff.
|
10
|
+
* A nice side effect of the changes necessary for the
|
11
|
+
new implementation is that the first value of a list is
|
12
|
+
not forced until requested. This also causes a bit of
|
13
|
+
space&time waste, but hardware is getting better and
|
14
|
+
cheaper, isn't it?
|
15
|
+
* Now there is more than one Empty object, be careful not to
|
16
|
+
use equal? to compare them. It's better to use LazyList#empty?
|
17
|
+
for this.
|
18
|
+
* Switched from the continuation based ReadQueue to a Thread
|
19
|
+
based one, that should be a bit faster.
|
20
|
+
* Better organization of the file structure.
|
21
|
+
* Added version information.
|
1
22
|
2005-12-23 * 0.2.2 * Added append and + (the binary append case) to append lazy
|
2
23
|
lists to each other.
|
3
|
-
2005-12-05 * 0.2.1 * Fixed a documentation bug, reported by Gary Wright
|
4
|
-
<at2002@mac.com>.
|
24
|
+
2005-12-05 * 0.2.1 * Fixed a documentation bug, reported by Gary Wright <at2002@mac.com>.
|
5
25
|
* enable/disable index reference cache added.
|
6
26
|
* LazyList#sublist added to generate sublists that
|
7
27
|
share elements with their "super" lists.
|
data/Rakefile
CHANGED
@@ -5,15 +5,16 @@ include Config
|
|
5
5
|
|
6
6
|
PKG_NAME = 'lazylist'
|
7
7
|
PKG_VERSION = File.read('VERSION').chomp
|
8
|
-
PKG_FILES = FileList['**/*'].exclude(/^doc
|
9
|
-
|
10
|
-
task :default => :test
|
8
|
+
PKG_FILES = FileList['**/*'].exclude(/^(doc|CVS|pkg|coverage)/)
|
11
9
|
|
12
10
|
desc "Run unit tests"
|
13
11
|
task :test do
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
ruby %{-Ilib tests/runner.rb}
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Testing library with coverage"
|
16
|
+
task :coverage do
|
17
|
+
sh 'rcov -x tests -Ilib tests/runner.rb'
|
17
18
|
end
|
18
19
|
|
19
20
|
desc "Installing library"
|
@@ -33,45 +34,25 @@ task :clean do
|
|
33
34
|
end
|
34
35
|
|
35
36
|
spec = Gem::Specification.new do |s|
|
36
|
-
#### Basic information.
|
37
|
-
|
38
37
|
s.name = 'lazylist'
|
39
38
|
s.version = PKG_VERSION
|
40
39
|
s.summary = "Implementation of lazy lists for Ruby"
|
41
40
|
s.description = ""
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
#s.add_dependency('log4r', '> 1.0.4')
|
46
|
-
#s.requirements << ""
|
42
|
+
s.add_dependency('dslkit', '>= 0.2.2')
|
47
43
|
|
48
44
|
s.files = PKG_FILES
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
#s.extensions << "ext/extconf.rb"
|
53
|
-
|
54
|
-
#### Load-time details: library and application (you will need one or both).
|
55
|
-
|
56
|
-
s.require_path = 'lib' # Use these for libraries.
|
57
|
-
s.autorequire = 'lazylist'
|
58
|
-
|
59
|
-
#s.bindir = "bin" # Use these for applications.
|
60
|
-
#s.executables = ["bla.rb"]
|
61
|
-
#s.default_executable = "bla.rb"
|
62
|
-
|
63
|
-
#### Documentation and testing.
|
46
|
+
s.require_path = 'lib'
|
64
47
|
|
65
48
|
s.has_rdoc = true
|
66
|
-
s.extra_rdoc_files = [
|
49
|
+
s.extra_rdoc_files = Dir['lib/**/*.rb']
|
67
50
|
s.rdoc_options <<
|
68
51
|
'--title' << 'LazyList -- Infinite lists in Ruby' <<
|
69
52
|
'--main' << 'LazyList' <<
|
70
53
|
'--line-numbers'
|
71
54
|
s.test_files << 'tests/test.rb'
|
72
55
|
|
73
|
-
#### Author and project details.
|
74
|
-
|
75
56
|
s.author = "Florian Frank"
|
76
57
|
s.email = "flori@ping.de"
|
77
58
|
s.homepage = "http://lazylist.rubyforge.org"
|
@@ -83,5 +64,24 @@ Rake::GemPackageTask.new(spec) do |pkg|
|
|
83
64
|
pkg.package_files += PKG_FILES
|
84
65
|
end
|
85
66
|
|
86
|
-
|
67
|
+
desc m = "Writing version information for #{PKG_VERSION}"
|
68
|
+
task :version do
|
69
|
+
puts m
|
70
|
+
File.open(File.join('lib', 'lazylist', 'version.rb'), 'w') do |v|
|
71
|
+
v.puts <<EOT
|
72
|
+
class LazyList
|
73
|
+
# LazyList version
|
74
|
+
VERSION = '#{PKG_VERSION}'
|
75
|
+
VERSION_ARRAY = VERSION.split(/\\./).map { |x| x.to_i } # :nodoc:
|
76
|
+
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
77
|
+
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
78
|
+
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
|
79
|
+
end
|
80
|
+
EOT
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
task :default => [ :version, :test ]
|
85
|
+
|
86
|
+
task :release => [ :version, :clean, :package ]
|
87
87
|
# vim: set et sw=2 ts=2:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/examples/examples.rb
CHANGED
@@ -45,8 +45,22 @@ fib.each(10) { |x| print x, " " } ; puts
|
|
45
45
|
p fib[100]
|
46
46
|
puts
|
47
47
|
|
48
|
+
fib = nil
|
49
|
+
puts "Fibonacci lazy list with zip"
|
50
|
+
fib = list(1, 1) { fib.zip(fib.drop) { |a, b| a + b } }
|
51
|
+
fib.each(10) { |x| print x, " " } ; puts
|
52
|
+
p fib[100]
|
53
|
+
puts
|
54
|
+
|
55
|
+
fib = nil
|
56
|
+
puts "Fibonacci lazy list with a list and a build call"
|
57
|
+
fib = list(1, 1) { build { a + b }.where(:a => fib, :b => fib.drop) }
|
58
|
+
fib.each(10) { |x| print x, " " } ; puts
|
59
|
+
p fib[100]
|
60
|
+
puts
|
61
|
+
|
48
62
|
puts "Sum up odd numbers lazylist to get a squares stream"
|
49
|
-
odd = LazyList.
|
63
|
+
odd = LazyList[1..Infinity].select { |x| x % 2 == 1 }
|
50
64
|
puts odd
|
51
65
|
squares = LazyList.tabulate(0) do |x|
|
52
66
|
(0..x).inject(0) { |s, i| s + odd[i] }
|
data/examples/pi.rb
CHANGED
@@ -25,7 +25,7 @@ if $0 == __FILE__
|
|
25
25
|
puts "Sum of the first 1000 digitis of pi: #{sum}"
|
26
26
|
puts "500th digit using memoized computation: #{PI[499]}"
|
27
27
|
|
28
|
-
puts "Printing #{max ? "the first #{max}" : "all the "} digits of pi:"
|
28
|
+
puts "Printing #{max ? "the first #{max}" : "all the "} digits of pi:" # vim-uff: "
|
29
29
|
PI.each!(max) do |x|
|
30
30
|
STDOUT.print x
|
31
31
|
STDOUT.flush
|
data/install.rb
CHANGED
@@ -6,6 +6,9 @@ require 'fileutils'
|
|
6
6
|
include FileUtils::Verbose
|
7
7
|
|
8
8
|
libdir = CONFIG["sitelibdir"]
|
9
|
-
|
10
|
-
|
9
|
+
install("lib/lazylist.rb", libdir)
|
10
|
+
mkdir_p subdir = File.join(libdir, 'lazylist')
|
11
|
+
for f in Dir['lib/lazylist/*.rb']
|
12
|
+
install(f, subdir)
|
13
|
+
end
|
11
14
|
# vim: set et sw=2 ts=2:
|
data/lib/lazylist.rb
CHANGED
@@ -35,6 +35,10 @@
|
|
35
35
|
#
|
36
36
|
# sq = LazyList.tabulate(1) { |x| x * x }
|
37
37
|
#
|
38
|
+
# or in the much nicer list builder syntax:
|
39
|
+
#
|
40
|
+
# sq = list { x * x }.where :x => 1..Infinity
|
41
|
+
#
|
38
42
|
# Now it's possible to get the first 10 square numbers by calling
|
39
43
|
# LazyList#take
|
40
44
|
#
|
@@ -77,6 +81,14 @@
|
|
77
81
|
# to compute this result. That's a very transparent way to get memoization for
|
78
82
|
# sequences that require heavy computation.
|
79
83
|
#
|
84
|
+
# You can also use the zip method to create fib:
|
85
|
+
#
|
86
|
+
# fib = list(1, 1) { fib.zip(fib.drop) { |a, b| a + b } }
|
87
|
+
#
|
88
|
+
# Another way to create the Fibonacci sequence with the build method is this:
|
89
|
+
#
|
90
|
+
# fib = list(1, 1) { build { a + b }.where(:a => fib, :b => fib.drop(1)) }
|
91
|
+
#
|
80
92
|
# You can create lazy lists that are based on arbitrary Enumerables, so can for
|
81
93
|
# example wrap your passwd file in one pretty easily:
|
82
94
|
#
|
@@ -90,6 +102,18 @@
|
|
90
102
|
# pw.find { |x| x =~ /^root:/ } => "root:x:0:0:root:/root:/bin/bash\n"
|
91
103
|
# instead, only every line until the root line is loaded into the memory.
|
92
104
|
#
|
105
|
+
# To create more complex lazy lists, you can build them from already existing
|
106
|
+
# lazy lists.
|
107
|
+
#
|
108
|
+
# Natural numbers:
|
109
|
+
# naturals = LazyList.from(1)
|
110
|
+
#
|
111
|
+
# Odd Numbers > 100:
|
112
|
+
# odds = list { x }.where(:x => naturals) { x % 2 == 1 && x > 100 }
|
113
|
+
#
|
114
|
+
# Alternative definition of square numbers:
|
115
|
+
# squares = build { odds[0, x].inject(0) { |s, y| s + y } }.where :x => naturals
|
116
|
+
#
|
93
117
|
# == References
|
94
118
|
#
|
95
119
|
# A very good introduction into lazy lists can be found in the scheme bible
|
@@ -97,181 +121,173 @@
|
|
97
121
|
# [http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-24.html#%25_sec_3.5]
|
98
122
|
#
|
99
123
|
class LazyList
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
# Returns two lazy lists, the first containing the elements of this lazy
|
104
|
-
# list for which the block evaluates to true, the second containing the
|
105
|
-
# rest.
|
106
|
-
def partition(&block)
|
107
|
-
return select(&block), reject(&block)
|
108
|
-
end
|
124
|
+
require 'dslkit'
|
125
|
+
require 'lazylist/list_builder'
|
126
|
+
require 'lazylist/version'
|
109
127
|
|
128
|
+
require 'lazylist/enumerable'
|
129
|
+
include LazyList::Enumerable
|
110
130
|
|
111
|
-
|
112
|
-
|
113
|
-
# Enumerable#sort.
|
114
|
-
def sort # :yields: a, b
|
115
|
-
LazyList.from_enum(super)
|
116
|
-
end
|
117
|
-
|
118
|
-
# Returns a sorted version of this lazy list. This method should only be
|
119
|
-
# called on finite lazy lists or it will never return. Also see
|
120
|
-
# Enumerable#sort_by.
|
121
|
-
def sort_by # :yields: obj
|
122
|
-
LazyList.from_enum(super)
|
123
|
-
end
|
131
|
+
# Exceptions raised by the LazyList implementation.
|
132
|
+
class Exception < ::StandardError; end
|
124
133
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
+
# ReadQueue is the implementation of an read-only queue that only supports
|
135
|
+
# #shift and #empty? methods. It's used as a wrapper to encapsulate
|
136
|
+
# enumerables in lazy lists.
|
137
|
+
class ReadQueue
|
138
|
+
# Creates an ReadQueue object from an enumerable.
|
139
|
+
def initialize(enumerable)
|
140
|
+
@data = []
|
141
|
+
@producer = Thread.new do
|
142
|
+
Thread.stop
|
143
|
+
begin
|
144
|
+
enumerable.each do |value|
|
145
|
+
old, Thread.critical = Thread.critical, true
|
146
|
+
begin
|
147
|
+
@data << value
|
148
|
+
@consumer.wakeup
|
149
|
+
Thread.stop
|
150
|
+
ensure
|
151
|
+
Thread.critical = old
|
152
|
+
end
|
153
|
+
end
|
154
|
+
rescue => e
|
155
|
+
@consumer.raise e
|
156
|
+
ensure
|
157
|
+
@consumer.wakeup
|
158
|
+
end
|
134
159
|
end
|
160
|
+
Thread.pass until @producer.stop?
|
135
161
|
end
|
136
162
|
|
137
|
-
#
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
# [self[i], others[0][i], others[1][i],... ]
|
143
|
-
# and a lazy list of those arrays is returned.
|
144
|
-
def zip(*others, &block)
|
145
|
-
if empty? or others.any? { |o| o.empty? }
|
146
|
-
Empty
|
163
|
+
# Extracts the top element from the queue or nil if the queue is
|
164
|
+
# empty.
|
165
|
+
def shift
|
166
|
+
if empty?
|
167
|
+
nil
|
147
168
|
else
|
148
|
-
|
149
|
-
list(block.call(head, *others.map { |o| o.head })) do
|
150
|
-
tail.zip(*others.map { |o| o.tail}, &block)
|
151
|
-
end
|
169
|
+
@data.shift
|
152
170
|
end
|
153
171
|
end
|
154
172
|
|
155
|
-
#
|
156
|
-
def combine(other, &operator)
|
157
|
-
warn "method 'combine' is obsolete - use 'zip'"
|
158
|
-
zip(other, &operator)
|
159
|
-
end
|
173
|
+
alias pop shift # for backwards compatibility
|
160
174
|
|
161
|
-
# Returns
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
175
|
+
# Returns true if the queue is empty.
|
176
|
+
def empty?
|
177
|
+
if @data.empty?
|
178
|
+
old, Thread.critical = Thread.critical, true
|
179
|
+
begin
|
180
|
+
@consumer = Thread.current
|
181
|
+
@producer.wakeup
|
182
|
+
Thread.stop
|
183
|
+
rescue ThreadError
|
184
|
+
;
|
185
|
+
ensure
|
186
|
+
@consumer = nil
|
187
|
+
Thread.critical = old
|
188
|
+
end
|
189
|
+
end
|
190
|
+
@data.empty?
|
169
191
|
end
|
192
|
+
end
|
170
193
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
194
|
+
Promise = DSLKit::BlankSlate.with :inspect, :to_s # :nodoc:
|
195
|
+
# A promise that can be evaluated on demand (if forced).
|
196
|
+
class Promise
|
197
|
+
def initialize(&block)
|
198
|
+
@block = block
|
175
199
|
end
|
176
200
|
|
177
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
201
|
+
# Return the value of this Promise.
|
202
|
+
def __value__
|
203
|
+
case v = @block.call
|
204
|
+
when Promise
|
205
|
+
v.__value__
|
206
|
+
else
|
207
|
+
v
|
184
208
|
end
|
185
|
-
return Empty if s.empty?
|
186
|
-
self.class.new(s.head) { s.tail.select(&block) }
|
187
209
|
end
|
188
|
-
alias find_all select
|
189
210
|
|
190
|
-
#
|
191
|
-
def
|
192
|
-
|
193
|
-
select(&p)
|
211
|
+
# Redirect all missing methods to the value of this Promise.
|
212
|
+
def method_missing( *args, &block )
|
213
|
+
__value__.__send__( *args, &block )
|
194
214
|
end
|
215
|
+
end
|
195
216
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
217
|
+
# A promise that can be evaluated on demand (if forced), that caches its
|
218
|
+
# value.
|
219
|
+
class MemoPromise < Promise
|
220
|
+
# Return the value of this Promise and memoize it for later access.
|
221
|
+
def __value__
|
222
|
+
@value ||= super
|
202
223
|
end
|
203
|
-
|
224
|
+
end
|
204
225
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
226
|
+
# This module contains module functions, that are added to LazyList and it's
|
227
|
+
# instances.
|
228
|
+
module ModuleFunctions
|
229
|
+
# Delay the value of _block_ to a Promise.
|
230
|
+
def delay(&block)
|
231
|
+
MemoPromise.new(&block)
|
209
232
|
end
|
210
|
-
end
|
211
|
-
include LazyList::Enumerable
|
212
233
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
# Creates an ReadQueue object from an enumerable.
|
221
|
-
def initialize(enumerable)
|
222
|
-
@enumerable = enumerable
|
223
|
-
@break = proc {}
|
224
|
-
@enumerable.each do |x|
|
225
|
-
@current = x
|
226
|
-
callcc do |@continue|
|
227
|
-
@break.call
|
228
|
-
return
|
229
|
-
end
|
234
|
+
# Force the delayed _obj_ to evaluate to its value.
|
235
|
+
def force(obj)
|
236
|
+
case obj
|
237
|
+
when Promise
|
238
|
+
force obj.__value__
|
239
|
+
else
|
240
|
+
obj
|
230
241
|
end
|
231
|
-
@continue = false
|
232
|
-
@break.call
|
233
242
|
end
|
234
243
|
|
235
|
-
#
|
236
|
-
#
|
237
|
-
def
|
238
|
-
|
239
|
-
|
240
|
-
|
244
|
+
# Returns a LazyList, that consists of the mixed elements of the lists
|
245
|
+
# _lists without any special order.
|
246
|
+
def mix(*lists)
|
247
|
+
return empty if lists.empty?
|
248
|
+
first = lists.shift
|
249
|
+
if lists.empty?
|
250
|
+
first
|
251
|
+
elsif first.empty?
|
252
|
+
mix(*lists)
|
253
|
+
else
|
254
|
+
LazyList.new(first.head) do
|
255
|
+
t = first.tail
|
256
|
+
lists = lists.push delay { t } unless t.empty?
|
257
|
+
mix(*lists)
|
258
|
+
end
|
259
|
+
end
|
241
260
|
end
|
261
|
+
end
|
262
|
+
extend ModuleFunctions
|
263
|
+
include ModuleFunctions
|
242
264
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
end
|
265
|
+
# Returns an empty list.
|
266
|
+
def self.empty
|
267
|
+
new(nil, nil)
|
247
268
|
end
|
248
269
|
|
249
|
-
# Returns
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
if LazyList.const_defined?(:Empty)
|
254
|
-
Empty
|
255
|
-
else
|
256
|
-
super
|
257
|
-
end
|
258
|
-
else
|
259
|
-
super
|
260
|
-
end
|
270
|
+
# Returns an empty list.
|
271
|
+
def empty
|
272
|
+
@klass ||= self.class
|
273
|
+
@klass.new(nil, nil)
|
261
274
|
end
|
262
275
|
|
263
276
|
# Creates a new LazyList element. The tail can be given either as
|
264
277
|
# second argument or as block.
|
265
|
-
def initialize(head, tail = nil, &
|
278
|
+
def initialize(head = nil, tail = nil, &block)
|
266
279
|
@cached = true
|
267
280
|
@ref_cache = {}
|
268
281
|
if tail
|
269
|
-
|
282
|
+
if block
|
270
283
|
raise LazyList::Exception,
|
271
284
|
"Use block xor second argument for constructor"
|
285
|
+
end
|
272
286
|
@head, @tail = head, tail
|
273
|
-
elsif
|
274
|
-
@head, @tail = head,
|
287
|
+
elsif block
|
288
|
+
@head, @tail = head, delay(&block)
|
289
|
+
else
|
290
|
+
@head, @tail = nil, nil
|
275
291
|
end
|
276
292
|
end
|
277
293
|
|
@@ -281,32 +297,45 @@ class LazyList
|
|
281
297
|
attr_writer :cached
|
282
298
|
|
283
299
|
# Returns true, if index references into the lazy list are cached for fast
|
284
|
-
# access to the
|
300
|
+
# access to the referenced elements.
|
285
301
|
def cached?
|
286
302
|
!!@cached
|
287
303
|
end
|
288
304
|
|
289
|
-
#
|
290
|
-
|
291
|
-
|
305
|
+
# If the constant Empty is requested return a new empty list object.
|
306
|
+
def self.const_missing(id)
|
307
|
+
if id == :Empty
|
308
|
+
new(nil, nil)
|
309
|
+
else
|
310
|
+
super
|
311
|
+
end
|
312
|
+
end
|
292
313
|
|
293
314
|
# Returns the value of this element.
|
294
|
-
|
315
|
+
attr_writer :head
|
295
316
|
protected :head=
|
296
317
|
|
318
|
+
# Returns the head of this list by computing its value.
|
319
|
+
def head
|
320
|
+
@head = force @head
|
321
|
+
end
|
322
|
+
|
323
|
+
# Returns the head of this list without forcing it.
|
324
|
+
def peek_head
|
325
|
+
@head
|
326
|
+
end
|
327
|
+
protected :peek_head
|
328
|
+
|
297
329
|
# Writes a tail value.
|
298
330
|
attr_writer :tail
|
299
331
|
protected :tail=
|
300
332
|
|
301
333
|
# Returns the next element by computing its value if necessary.
|
302
334
|
def tail
|
303
|
-
|
304
|
-
@tail = @tail[@head] || Empty
|
305
|
-
end
|
306
|
-
@tail
|
335
|
+
@tail = force @tail
|
307
336
|
end
|
308
337
|
|
309
|
-
# Returns the tail of this list without
|
338
|
+
# Returns the tail of this list without forcing it.
|
310
339
|
def peek_tail
|
311
340
|
@tail
|
312
341
|
end
|
@@ -315,32 +344,67 @@ class LazyList
|
|
315
344
|
# Identity lambda expression, mostly used as a default.
|
316
345
|
Identity = lambda { |x| x }
|
317
346
|
|
347
|
+
# This block returns true.
|
348
|
+
All = lambda { |x| true }
|
349
|
+
|
350
|
+
# Create an array tuple from argument list.
|
351
|
+
Tuple = lambda { |*t| t }
|
352
|
+
|
353
|
+
# Returns true, if a less than b.
|
354
|
+
LessThan = lambda { |a, b| a < b }
|
355
|
+
|
356
|
+
# Swaps _you_ and _me_ and returns an Array tuple of both.
|
357
|
+
SwapTuple = lambda { |you, me| [ me, you ] }
|
358
|
+
|
318
359
|
# Returns a lazy list which is generated from the Enumerable a or
|
319
360
|
# LazyList.span(a, n), if n was given as an argument.
|
320
361
|
def self.[](a, n = nil)
|
321
362
|
case
|
322
363
|
when n
|
323
364
|
span(a, n)
|
324
|
-
when
|
325
|
-
io(a)
|
326
|
-
|
365
|
+
when a.respond_to?(:to_io)
|
366
|
+
io(a.to_io)
|
367
|
+
when a.respond_to?(:to_ary)
|
368
|
+
from_queue(a.to_ary)
|
369
|
+
when Range === a
|
370
|
+
from_range(a)
|
371
|
+
when a.respond_to?(:each)
|
327
372
|
from_enum(a)
|
373
|
+
else
|
374
|
+
list(a)
|
328
375
|
end
|
329
376
|
end
|
330
377
|
|
331
378
|
# Generates a lazy list from any data structure e which
|
332
379
|
# responds to the #each method.
|
333
380
|
def self.from_enum(e)
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
381
|
+
from_queue ReadQueue.new(e)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Generates a lazyx list by popping elements from a queue.
|
385
|
+
def self.from_queue(rq)
|
386
|
+
return empty if rq.empty?
|
387
|
+
new(delay { rq.shift }) { from_queue(rq) }
|
388
|
+
end
|
389
|
+
|
390
|
+
# Generates a lazy list from a Range instance _r_.
|
391
|
+
def self.from_range(r)
|
392
|
+
if r.exclude_end?
|
393
|
+
if r.begin >= r.end
|
394
|
+
empty
|
395
|
+
else
|
396
|
+
new(delay { r.begin }) { from_range(r.begin.succ...r.end) }
|
397
|
+
end
|
398
|
+
else
|
399
|
+
case
|
400
|
+
when r.begin > r.end
|
401
|
+
empty
|
402
|
+
when r.begin == r.end
|
403
|
+
new(delay { r.begin }) { empty }
|
339
404
|
else
|
340
|
-
new(
|
405
|
+
new(delay { r.begin }) { from_range(r.begin.succ..r.end) }
|
341
406
|
end
|
342
407
|
end
|
343
|
-
new(oq.pop, next_top)
|
344
408
|
end
|
345
409
|
|
346
410
|
# Generates a finite lazy list beginning with element a and spanning
|
@@ -348,9 +412,9 @@ class LazyList
|
|
348
412
|
# successor method succ.
|
349
413
|
def self.span(a, n)
|
350
414
|
if n > 0
|
351
|
-
new(a) { span(a.succ, n - 1) }
|
415
|
+
new(delay { a }) { span(a.succ, n - 1) }
|
352
416
|
else
|
353
|
-
|
417
|
+
empty
|
354
418
|
end
|
355
419
|
end
|
356
420
|
|
@@ -359,10 +423,11 @@ class LazyList
|
|
359
423
|
# If none is given the identity function is computed instead.
|
360
424
|
def self.tabulate(n = 0, &f)
|
361
425
|
f = Identity unless f
|
362
|
-
new(f[n]) { tabulate(n.succ, &f) }
|
426
|
+
new(delay { f[n] }) { tabulate(n.succ, &f) }
|
363
427
|
end
|
364
428
|
|
365
|
-
# Returns a list of all elements succeeding _n_
|
429
|
+
# Returns a list of all elements succeeding _n_ (that is created by calling
|
430
|
+
# the #succ method) and starting from _n_.
|
366
431
|
def self.from(n = 0)
|
367
432
|
tabulate(n)
|
368
433
|
end
|
@@ -370,16 +435,16 @@ class LazyList
|
|
370
435
|
# Generates a lazy list which iterates over its previous values
|
371
436
|
# computing something like: f(i), f(f(i)), f(f(f(i))), ...
|
372
437
|
def self.iterate(i = 0, &f)
|
373
|
-
new(i) { iterate(f[i], &f) }
|
438
|
+
new(delay { i }) { iterate(f[i], &f) }
|
374
439
|
end
|
375
440
|
|
376
441
|
# Generates a lazy list of a give IO-object using a given
|
377
442
|
# block or Proc object to read from this object.
|
378
443
|
def self.io(input, &f)
|
379
444
|
if f
|
380
|
-
input.eof? ?
|
445
|
+
input.eof? ? empty : new(delay { f[input] }) { io(input, &f) }
|
381
446
|
else
|
382
|
-
input.eof? ?
|
447
|
+
input.eof? ? empty : new(delay { input.readline }) { io(input) }
|
383
448
|
end
|
384
449
|
end
|
385
450
|
|
@@ -403,9 +468,9 @@ class LazyList
|
|
403
468
|
sublist_span(0, n)
|
404
469
|
elsif m > 0
|
405
470
|
l = ref(n)
|
406
|
-
self.class.new(l.head) { l.tail.sublist_span(0, m - 1) }
|
471
|
+
self.class.new(delay { l.head }) { l.tail.sublist_span(0, m - 1) }
|
407
472
|
else
|
408
|
-
|
473
|
+
empty
|
409
474
|
end
|
410
475
|
end
|
411
476
|
|
@@ -436,23 +501,25 @@ class LazyList
|
|
436
501
|
if s.empty?
|
437
502
|
return set_ref(n, self)
|
438
503
|
end
|
504
|
+
s.head # force the head
|
439
505
|
s = s.tail
|
440
506
|
i -= 1
|
441
507
|
end
|
442
508
|
set_ref(n, s)
|
443
509
|
end
|
444
|
-
|
510
|
+
protected :ref
|
445
511
|
|
446
512
|
# If n is a Range every element in this range is returned.
|
447
513
|
# If n isn't a Range object the element at index n is returned.
|
448
514
|
# If m is given the next m elements beginning the n-th element are
|
449
515
|
# returned.
|
450
516
|
def [](n, m = nil)
|
451
|
-
|
517
|
+
case
|
518
|
+
when Range === n
|
452
519
|
sublist(n)
|
453
|
-
|
520
|
+
when n < 0
|
454
521
|
nil
|
455
|
-
|
522
|
+
when m
|
456
523
|
sublist(n, m)
|
457
524
|
else
|
458
525
|
ref(n).head
|
@@ -464,23 +531,40 @@ class LazyList
|
|
464
531
|
# elements to iterate over.
|
465
532
|
def each(n = nil)
|
466
533
|
s = self
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
534
|
+
if n
|
535
|
+
until n <= 0 or s.empty?
|
536
|
+
yield s.head
|
537
|
+
s = s.tail
|
538
|
+
n -= 1 unless n.nil?
|
539
|
+
end
|
540
|
+
else
|
541
|
+
until s.empty?
|
542
|
+
yield s.head
|
543
|
+
s = s.tail
|
544
|
+
n -= 1 unless n.nil?
|
545
|
+
end
|
471
546
|
end
|
472
547
|
s
|
473
548
|
end
|
474
549
|
|
475
|
-
# Similar to LazyList#each but destroys elements from past
|
476
|
-
#
|
550
|
+
# Similar to LazyList#each but destroys elements from past iterations perhaps
|
551
|
+
# saving some memory. Try to call GC.start from time to time in your block.
|
477
552
|
def each!(n = nil)
|
478
553
|
s = self
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
554
|
+
if n
|
555
|
+
until n <= 0 or s.empty?
|
556
|
+
yield s.head
|
557
|
+
s = s.tail
|
558
|
+
n -= 1 unless n.nil?
|
559
|
+
@head, @tail = s.head, s.tail
|
560
|
+
end
|
561
|
+
else
|
562
|
+
until s.empty?
|
563
|
+
yield s.head
|
564
|
+
s = s.tail
|
565
|
+
n -= 1 unless n.nil?
|
566
|
+
@head, @tail = s.head, s.tail
|
567
|
+
end
|
484
568
|
end
|
485
569
|
self
|
486
570
|
end
|
@@ -489,12 +573,15 @@ class LazyList
|
|
489
573
|
# which elements to place first in the result lazy list. If no compare block
|
490
574
|
# is given lambda { |a,b| a < b } is used as a default value.
|
491
575
|
def merge(other, &compare)
|
492
|
-
compare
|
493
|
-
|
494
|
-
|
495
|
-
|
576
|
+
compare ||= LessThan
|
577
|
+
case
|
578
|
+
when empty?
|
579
|
+
other
|
580
|
+
when other.empty?
|
581
|
+
self
|
582
|
+
when compare[head, other.head]
|
496
583
|
self.class.new(head) { tail.merge(other, &compare) }
|
497
|
-
|
584
|
+
when compare[other.head, head]
|
498
585
|
self.class.new(other.head) { merge(other.tail, &compare) }
|
499
586
|
else
|
500
587
|
self.class.new(head) { tail.merge(other.tail, &compare) }
|
@@ -508,12 +595,12 @@ class LazyList
|
|
508
595
|
def append(*other)
|
509
596
|
if empty?
|
510
597
|
if other.empty?
|
511
|
-
|
598
|
+
empty
|
512
599
|
else
|
513
600
|
other.first.append(*other[1..-1])
|
514
601
|
end
|
515
602
|
else
|
516
|
-
|
603
|
+
self.class.new(delay { head }) { tail.append(*other) }
|
517
604
|
end
|
518
605
|
end
|
519
606
|
|
@@ -569,7 +656,7 @@ class LazyList
|
|
569
656
|
|
570
657
|
# Returns true if this is the empty lazy list.
|
571
658
|
def empty?
|
572
|
-
self.
|
659
|
+
self.peek_head == nil && self.peek_tail == nil
|
573
660
|
end
|
574
661
|
|
575
662
|
# Returns true, if this lazy list and the other lazy list consist of only
|
@@ -586,61 +673,156 @@ class LazyList
|
|
586
673
|
# Inspects the list as far as it has been computed by returning
|
587
674
|
# a string of the form [1, 2, 3,... ].
|
588
675
|
def inspect
|
589
|
-
|
676
|
+
return '[]' if empty?
|
677
|
+
result = '['
|
678
|
+
first = true
|
590
679
|
s = self
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
break
|
597
|
-
when Proc === pt
|
598
|
-
result << s.head.inspect << ",... "
|
599
|
-
break
|
680
|
+
seen = {}
|
681
|
+
until s.empty? or Promise === s.peek_head or seen[s.__id__]
|
682
|
+
seen[s.__id__] = true
|
683
|
+
if first
|
684
|
+
first = false
|
600
685
|
else
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
686
|
+
result << ', '
|
687
|
+
end
|
688
|
+
result << s.head.inspect
|
689
|
+
Promise === s.peek_tail and break
|
690
|
+
s = s.tail
|
691
|
+
end
|
692
|
+
unless empty?
|
693
|
+
if first
|
694
|
+
result << '... '
|
695
|
+
elsif !s.empty?
|
696
|
+
result << ',... '
|
697
|
+
end
|
698
|
+
end
|
699
|
+
result << ']'
|
700
|
+
end
|
701
|
+
|
702
|
+
alias to_s inspect
|
703
|
+
|
704
|
+
# Returns one "half" of the product of this LazyList and the _other_:
|
705
|
+
# list(1,2,3).half_product(list(1,2)).to_a # => [[1, 1], [2, 1], [2, 2], [3, 1], [3, 2]]
|
706
|
+
# _block_ can be used to yield to every pair generated and create a new list
|
707
|
+
# element out of it. It defaults to Tuple.
|
708
|
+
def half_product(other, &block)
|
709
|
+
block ||= Tuple
|
710
|
+
if empty? or other.empty?
|
711
|
+
empty
|
712
|
+
else
|
713
|
+
mix(
|
714
|
+
delay { zip(other, &block) },
|
715
|
+
delay { tail.half_product(other, &block) }
|
716
|
+
)
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
def swap(block) # :nodoc:
|
721
|
+
lambda { |you, me| block[me, you] }
|
722
|
+
end
|
723
|
+
private :swap
|
724
|
+
|
725
|
+
# Returns the (cartesian) product of this LazyList instance and the _other_.
|
726
|
+
# _block_ can be used to yield to every pair generated and create a new list
|
727
|
+
# element out of it, but it's useful to at least return the default Tuple
|
728
|
+
# from the block.
|
729
|
+
def product(other, &block)
|
730
|
+
if empty? or other.empty?
|
731
|
+
empty
|
732
|
+
else
|
733
|
+
other_block =
|
734
|
+
if block
|
735
|
+
swap block
|
605
736
|
else
|
606
|
-
|
607
|
-
|
737
|
+
block = Tuple
|
738
|
+
SwapTuple
|
739
|
+
end
|
740
|
+
mix(
|
741
|
+
delay { half_product(other, &block)},
|
742
|
+
delay { other.tail.half_product(self, &other_block) }
|
743
|
+
)
|
744
|
+
end
|
745
|
+
end
|
746
|
+
alias * product
|
747
|
+
|
748
|
+
# Returns the cartesian_product of this LazyList and the others as a LazyList
|
749
|
+
# of Array tuples. A block can be given to yield to all the created tuples
|
750
|
+
# and create a LazyList out of the block results.
|
751
|
+
def cartesian_product(*others) # :yields: tuple
|
752
|
+
case
|
753
|
+
when empty?
|
754
|
+
self
|
755
|
+
when others.empty?
|
756
|
+
block_given? ? map(&Proc.new) : map
|
757
|
+
else
|
758
|
+
product = others[1..-1].inject(product(others[0])) do |intermediate, list|
|
759
|
+
intermediate.product(list) do |existing, new_element|
|
760
|
+
existing + [ new_element ]
|
608
761
|
end
|
609
762
|
end
|
763
|
+
if block_given?
|
764
|
+
block = Proc.new
|
765
|
+
product.map { |tuple| block[*tuple] }
|
766
|
+
else
|
767
|
+
product
|
768
|
+
end
|
610
769
|
end
|
611
|
-
result << "]"
|
612
770
|
end
|
613
771
|
|
614
|
-
|
615
|
-
|
772
|
+
# This module contains methods that are included into Ruby's Kernel module.
|
773
|
+
module ObjectMethods
|
774
|
+
# A method to improve the user friendliness for creating new lazy lists, that
|
775
|
+
# cannot be described well with LazyList.iterate or LazyList.tabulate.
|
776
|
+
#
|
777
|
+
# - list without any arguments, returns the empty lazy list LazyList::Empty.
|
778
|
+
# - list { x / y } returns a LazyList::ListBuilder object for a list
|
779
|
+
# comprehension, that can be transformed into a LazyList by calling the
|
780
|
+
# LazyList::ListBuilder#where method.
|
781
|
+
# - list(x) returns the lazy list with only the element x as a member,
|
782
|
+
# list(x,y) returns the lazy list with only the elements x and y as a
|
783
|
+
# members, and so on.
|
784
|
+
# - list(x) { xs } returns the lazy list with the element x as a head
|
785
|
+
# element, and that is continued with the lazy list xs as tail. To define an
|
786
|
+
# infinite lazy list of 1s you can do:
|
787
|
+
# ones = list(1) { ones } # => [1,... ]
|
788
|
+
# To define all even numbers directly, you can do:
|
789
|
+
# def even(n = 0) list(n) { even(n + 2) } end
|
790
|
+
# and then:
|
791
|
+
# e = even # => [0,... ]
|
792
|
+
def list(*values, &block)
|
793
|
+
values_empty = values.empty?
|
794
|
+
result = LazyList[values]
|
795
|
+
if block_given?
|
796
|
+
if values_empty
|
797
|
+
result = LazyList::ListBuilder.create_comprehend(&block)
|
798
|
+
else
|
799
|
+
result.instance_eval do
|
800
|
+
ref(values.size - 1)
|
801
|
+
end.instance_variable_set(:@tail, LazyList.delay(&block))
|
802
|
+
end
|
803
|
+
end
|
804
|
+
result
|
805
|
+
end
|
616
806
|
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
# infinite lazy list of 1s you can do:
|
628
|
-
# ones = list(1) { ones } # => [1,... ]
|
629
|
-
# To define all even numbers directly, you can do:
|
630
|
-
# def even(n = 0) list(n) { even(n + 2) } end
|
631
|
-
# and then:
|
632
|
-
# e = even # => [0,... ]
|
633
|
-
def list(*values, &promise)
|
634
|
-
result = LazyList::Empty
|
635
|
-
last = nil
|
636
|
-
values.reverse_each do |v|
|
637
|
-
result = LazyList.new(v, result)
|
638
|
-
last ||= result
|
639
|
-
end
|
640
|
-
if last and block_given?
|
641
|
-
last.instance_variable_set :@tail, promise
|
807
|
+
# This method returns a Lazylist::ListBuilder instance for tuplewise building
|
808
|
+
# of lists like the zip method does. This method call
|
809
|
+
#
|
810
|
+
# build { x + y }.where(:x => 1..3, :y => 1..3)
|
811
|
+
#
|
812
|
+
# returns the same list [ 2, 4, 6 ] as this expression does
|
813
|
+
#
|
814
|
+
# LazyList[1..3].zip(LazyList[1..3]) { |x, y| x + y }
|
815
|
+
def build(&block)
|
816
|
+
LazyList::ListBuilder.create_build(&block)
|
642
817
|
end
|
643
|
-
|
818
|
+
end
|
819
|
+
|
820
|
+
class ::Object
|
821
|
+
unless const_defined? :Infinity
|
822
|
+
Infinity = 1 / 0.0
|
823
|
+
end
|
824
|
+
|
825
|
+
include LazyList::ObjectMethods
|
644
826
|
end
|
645
827
|
end
|
646
828
|
# vim: set et sw=2 ts=2:
|