lazylist 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|