rubylabs 0.9.0 → 0.9.1
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/README.rdoc +15 -6
- data/Rakefile +3 -0
- data/VERSION +1 -1
- data/lib/bitlab.rb +593 -328
- data/lib/demos.rb +20 -9
- data/lib/elizalab.rb +660 -507
- data/lib/hashlab.rb +289 -192
- data/lib/introlab.rb +33 -38
- data/lib/iterationlab.rb +117 -61
- data/lib/marslab.rb +608 -475
- data/lib/randomlab.rb +227 -121
- data/lib/recursionlab.rb +197 -140
- data/lib/rubylabs.rb +936 -390
- data/lib/sievelab.rb +32 -24
- data/lib/spherelab.rb +308 -220
- data/lib/tsplab.rb +634 -312
- data/test/bit_test.rb +4 -4
- data/test/tsp_test.rb +18 -0
- metadata +2 -2
data/lib/rubylabs.rb
CHANGED
@@ -1,77 +1,87 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
= RubyLabs
|
4
|
-
|
5
|
-
Common methods used by two or more lab projects.
|
6
|
-
|
7
|
-
Methods used to monitor execution of programs during experiments.
|
8
|
-
|
9
|
-
=end
|
1
|
+
# RubyLabs gem, top level module.
|
10
2
|
|
11
3
|
SCRIPT_LINES__ = Hash.new unless defined? SCRIPT_LINES__
|
12
4
|
|
13
|
-
autoload :IntroLab,
|
14
|
-
autoload :SieveLab,
|
5
|
+
autoload :IntroLab, "introlab.rb"
|
6
|
+
autoload :SieveLab, "sievelab.rb"
|
15
7
|
autoload :IterationLab, "iterationlab.rb"
|
16
8
|
autoload :RecursionLab, "recursionlab.rb"
|
17
|
-
autoload :HashLab,
|
18
|
-
autoload :BitLab,
|
19
|
-
autoload :MARSLab,
|
20
|
-
autoload :RandomLab,
|
21
|
-
autoload :EncryptionLab,
|
22
|
-
autoload :ElizaLab,
|
9
|
+
autoload :HashLab, "hashlab.rb"
|
10
|
+
autoload :BitLab, "bitlab.rb"
|
11
|
+
autoload :MARSLab, "marslab.rb"
|
12
|
+
autoload :RandomLab, "randomlab.rb"
|
13
|
+
autoload :EncryptionLab, "encryptionlab.rb"
|
14
|
+
autoload :ElizaLab, "elizalab.rb"
|
23
15
|
autoload :SphereLab, "spherelab.rb"
|
24
|
-
autoload :TSPLab,
|
16
|
+
autoload :TSPLab, "tsplab.rb"
|
25
17
|
|
26
|
-
autoload :Demos,
|
18
|
+
autoload :Demos, "demos.rb"
|
27
19
|
|
28
20
|
include Math
|
29
21
|
|
30
|
-
module RubyLabs
|
31
|
-
|
32
22
|
=begin rdoc
|
33
|
-
|
34
|
-
=== hello
|
35
23
|
|
36
|
-
|
24
|
+
== RubyLabs
|
25
|
+
|
26
|
+
RubyLabs is a collection of modules for projects described in
|
27
|
+
<em>Explorations in Computing: An Introduction to Computer Science</em>.
|
37
28
|
|
29
|
+
There is one submodule for each project, e.g. RubyLabs::SieveLab has
|
30
|
+
methods used in the Sieve of Eratosthenes project in Chapter 3.
|
31
|
+
The top level RubyLabs module also has common methods used in
|
32
|
+
several projects.
|
38
33
|
=end
|
39
34
|
|
35
|
+
module RubyLabs
|
36
|
+
|
37
|
+
# A simple 'hello world' method to test the installation.
|
38
|
+
|
40
39
|
def hello
|
41
40
|
"Hello, you have successfully installed RubyLabs!"
|
42
41
|
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
log(x) / log(2.0)
|
50
|
-
end
|
51
|
-
|
52
|
-
=begin rdoc
|
53
|
-
Return the larger of a and b
|
54
|
-
=end
|
43
|
+
# Return the larger of +a+ and +b+ (determined by calling <tt>a < b</tt>).
|
44
|
+
#
|
45
|
+
# :call-seq:
|
46
|
+
# max(a,b) => Object
|
47
|
+
#
|
55
48
|
|
56
49
|
def max(a,b)
|
57
|
-
a
|
50
|
+
a < b ? b : a
|
58
51
|
end
|
59
52
|
|
60
|
-
|
61
|
-
|
62
|
-
|
53
|
+
# Return the smaller of +a+ and +b+ (determined by calling <tt>a < b</tt>).
|
54
|
+
#
|
55
|
+
# :call-seq:
|
56
|
+
# min(a,b) => Object
|
57
|
+
#
|
63
58
|
|
64
59
|
def min(a,b)
|
65
60
|
a < b ? a : b
|
66
61
|
end
|
67
62
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
# Compute the logarithm (base 2) of a number +x+.
|
64
|
+
#
|
65
|
+
# :call-seq:
|
66
|
+
# log2(x) => Float
|
67
|
+
#
|
73
68
|
|
74
|
-
|
69
|
+
def log2(x)
|
70
|
+
log(x) / log(2.0)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Measure the execution time of a block of code, which can be any Ruby expression
|
74
|
+
# enclosed in braces after the method name. The return value is the number of seconds
|
75
|
+
# required to evaluate the block.
|
76
|
+
# Any output produced by the block is discarded.
|
77
|
+
#
|
78
|
+
# Example:
|
79
|
+
# >> time { sieve(1000) }
|
80
|
+
# => 0.014921
|
81
|
+
#
|
82
|
+
# :call-seq:
|
83
|
+
# time { f } => Float
|
84
|
+
#
|
75
85
|
|
76
86
|
def time(&f)
|
77
87
|
tstart = Time.now
|
@@ -79,91 +89,132 @@ method will time any arbitrary Ruby expression.
|
|
79
89
|
return Time.now - tstart
|
80
90
|
end
|
81
91
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
the
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
# Monitor the execution of a block of code, which can be any Ruby expression enclosed in braces. Sets up
|
93
|
+
# a callback method which checks to see if a probe has been attached to a line in
|
94
|
+
# a method called by the block. See Source#probe for information about attaching
|
95
|
+
# probes. If a probe is attached, the expression associated with
|
96
|
+
# the probe is evaluated. The value returned from trace is the value returned
|
97
|
+
# by the block.
|
98
|
+
#
|
99
|
+
# See also RubyLabs#count.
|
100
|
+
#
|
101
|
+
# Example -- call a method named +brackets+ to print brackets around array
|
102
|
+
# elements as the arry is being sorted by a call to +isort+:
|
103
|
+
# >> Source.probe( "isort", 5, "puts brackets(a,i)" )
|
104
|
+
# => true
|
105
|
+
# >> trace { isort(cars) }
|
106
|
+
# mazda [ford bmw saab chrysler]
|
107
|
+
# ford mazda [bmw saab chrysler]
|
108
|
+
# bmw ford mazda [saab chrysler]
|
109
|
+
# ...
|
110
|
+
# => ["bmw", "chrysler", "ford", "mazda", "saab"]
|
111
|
+
#
|
112
|
+
# :call-seq:
|
113
|
+
# trace { f } => Object
|
114
|
+
#
|
115
|
+
#--
|
91
116
|
# Debugging aid: put this line at the front of the trace_func:
|
92
117
|
# p [event, file, line, id, binding, classname]
|
93
118
|
|
94
119
|
def trace(&f)
|
95
|
-
|
120
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
96
121
|
if expr = Source.probing(file, id, line)
|
97
122
|
eval(expr, binding) if expr != :count
|
98
123
|
end
|
99
|
-
|
124
|
+
}
|
100
125
|
res = f.call
|
101
|
-
|
102
|
-
|
126
|
+
set_trace_func nil
|
127
|
+
res
|
103
128
|
end
|
104
129
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
130
|
+
# Monitor the execution of a block of code, which can be any Ruby expression enclosed in braces.
|
131
|
+
# If a counting probe (see Source#probe) is attached to any lines of code evaluated by the block
|
132
|
+
# this method will return the total number of times those lines are executed.
|
133
|
+
#
|
134
|
+
# See also RubyLabs#trace.
|
135
|
+
#
|
136
|
+
# Example -- count the number of times line 2 in the method named +less+ is executed
|
137
|
+
# during a call to +isort+:
|
138
|
+
# >> Source.probe("less", 2, :count)
|
139
|
+
# => true
|
140
|
+
# >> count { isort(cars) }
|
141
|
+
# => 8
|
142
|
+
#
|
143
|
+
# :call-seq:
|
144
|
+
# count { f } => Fixnum
|
145
|
+
#
|
116
146
|
|
117
147
|
def count(&f)
|
118
148
|
counter = 0
|
119
|
-
|
149
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
120
150
|
if expr = Source.probing(file, id, line)
|
121
151
|
counter += 1 if expr == :count
|
122
152
|
end
|
123
|
-
|
153
|
+
}
|
124
154
|
f.call
|
125
|
-
|
126
|
-
|
155
|
+
set_trace_func nil
|
156
|
+
return counter
|
127
157
|
end
|
128
158
|
|
129
|
-
=begin rdoc
|
130
159
|
|
131
|
-
|
160
|
+
=begin rdoc
|
132
161
|
|
133
|
-
|
134
|
-
algorithms. Call TestArray.new(n) to make an array of n unique random numbers.
|
162
|
+
== TestArray
|
135
163
|
|
164
|
+
A TestArray is an array of random values that can be used to test searching and sorting
|
165
|
+
algorithms.
|
136
166
|
A method named +random+ will return a random element to use as a search target.
|
137
|
-
|
138
|
-
|
139
|
-
|
167
|
+
If +a+ is a TestArray object,
|
168
|
+
call <tt>a.random(:success)</tt> to get a value that is in the array +a+, or call
|
169
|
+
<tt>a.random(:fail)</tt> to get a number that is not in the array.
|
170
|
+
|
171
|
+
#--
|
172
|
+
The constructor uses a hash to create unique numbers -- it draws random numbers
|
173
|
+
and uses them as keys to insert into the hash, and returns when the hash has n
|
174
|
+
items. The hash is saved so it can be reused by a call to random(:fail) -- this
|
175
|
+
time draw random numbers until one is not a key in the hash. A lot of machinery
|
176
|
+
to keep around for very few calls, but it's efficient enough -- making an array
|
177
|
+
of 100K items takes less than a second.
|
178
|
+
|
179
|
+
An earlier version used a method named test_array to make a regular Array object
|
180
|
+
and augment it with the location method, but the singleton's methods were not passed
|
181
|
+
on to copies made by a call to sort:
|
182
|
+
>> x = test_array(3)
|
183
|
+
=> [16, 13, 4]
|
184
|
+
>> x.sort.random(:fail)
|
185
|
+
NoMethodError: undefined method `random' for [4, 13, 16]:Array
|
186
|
+
|
140
187
|
=end
|
141
188
|
|
142
|
-
# The constructor uses a hash to create unique numbers -- it draws random numbers
|
143
|
-
# and uses them as keys to insert into the hash, and returns when the hash has n
|
144
|
-
# items. The hash is saved so it can be reused by a call to random(:fail) -- this
|
145
|
-
# time draw random numbers until one is not a key in the hash. A lot of machinery
|
146
|
-
# to keep around for very few calls, but it's efficient enough -- making an array
|
147
|
-
# of 100K items takes less than a second.
|
148
|
-
|
149
|
-
# An earlier version used a method named test_array to make a regular Array object
|
150
|
-
# and augment it with the location method, but the singleton's methods were not passed
|
151
|
-
# on to copies made by a call to sort:
|
152
|
-
# >> x = test_array(3)
|
153
|
-
# => [16, 13, 4]
|
154
|
-
# >> x.sort.random(:fail)
|
155
|
-
# NoMethodError: undefined method `random' for [4, 13, 16]:Array
|
156
|
-
|
157
189
|
class TestArray < Array
|
158
190
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
191
|
+
data = File.join(File.dirname(__FILE__), '..', 'data', 'arrays')
|
192
|
+
|
193
|
+
@@sources = {
|
194
|
+
:cars => "#{data}/cars.txt",
|
195
|
+
:colors => "#{data}/colors.txt",
|
196
|
+
:fruits => "#{data}/fruit.txt",
|
197
|
+
:words => "#{data}/wordlist.txt",
|
198
|
+
}
|
199
|
+
|
200
|
+
# Create a new TestArray of size +n+ containing items of the specified +type+.
|
201
|
+
# If a type is not supplied, create an array of integers. Types are identified
|
202
|
+
# by symbols, e.g. <tt>:cars</tt> tells the constructor to return an array of
|
203
|
+
# car names (see TestArray.sources). If a type is specified, the symbol <tt>:all</tt>
|
204
|
+
# can be given instead of an array size, in which case the constructor returns
|
205
|
+
# all items of that type.
|
206
|
+
#
|
207
|
+
# Examples:
|
208
|
+
# >> TestArray.new(5)
|
209
|
+
# => [3, 28, 48, 64, 4]
|
210
|
+
# >> TestArray.new(5, :cars)
|
211
|
+
# => ["lamborghini", "lincoln", "chrysler", "toyota", "rolls-royce"]
|
212
|
+
# >> TestArray.new(:all, :colors)
|
213
|
+
# => ["almond", "antique white", ... "yellow green"]
|
214
|
+
#
|
215
|
+
# :call-seq:
|
216
|
+
# TestArray.new(n, type) => Array
|
217
|
+
#
|
167
218
|
|
168
219
|
def initialize(size, src = nil)
|
169
220
|
|
@@ -179,41 +230,59 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
179
230
|
raise "TestArray: array size must be an integer or :all" unless size.class == Fixnum || size == :all
|
180
231
|
end
|
181
232
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
233
|
+
@h = Hash.new
|
234
|
+
|
235
|
+
# if @max is defined make an array of integers, otherwise src defines the type of data;
|
236
|
+
# size might be :all, in which case return the whole file, and set @all to true so random
|
237
|
+
# doesn't try to make a random value not in the array.
|
238
|
+
|
239
|
+
if @max
|
240
|
+
while @h.size < size
|
241
|
+
@h[ rand( @max ) ] = 1
|
242
|
+
end
|
243
|
+
else
|
244
|
+
fn = @@sources[src] or raise "TestArray: undefined source: #{src}"
|
245
|
+
@words = File.open(fn).readlines
|
246
|
+
if size != :all
|
196
247
|
max = @words.length
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
248
|
+
raise "TestArray: size must be less than #{max} for an array of #{src}" unless size < max
|
249
|
+
while @h.size < size
|
250
|
+
@h[ @words[ rand(max) ].chomp ] = 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
if size == :all
|
256
|
+
self.concat @words.map { |s| s.chomp! }
|
257
|
+
@all = true
|
258
|
+
else
|
259
|
+
self.concat @h.keys
|
209
260
|
for i in 0..length-2
|
210
261
|
r = rand(length-i) + i # i <= r < length
|
211
262
|
self[i],self[r] = self[r],self[i]
|
212
263
|
end
|
213
|
-
|
214
|
-
|
264
|
+
end
|
265
|
+
|
215
266
|
end
|
216
267
|
|
268
|
+
# Return a value that is guaranteed to be in the array or not in the array,
|
269
|
+
# depending on the value of +outcome+. Pass <tt>:success</tt> to get a random
|
270
|
+
# value in the array, or pass <tt>:fail</tt> to get an item of the same type as the
|
271
|
+
# items in the array but which is not itself in the array. Call <tt>a.random(:fail)</tt>
|
272
|
+
# to get a value that will cause a search algorithm to do the maximum number of comparisons.
|
273
|
+
#
|
274
|
+
# Example:
|
275
|
+
# >> a = TestArray.new(10).sort
|
276
|
+
# => [13, 23, 24, 26, 47, 49, 86, 88, 92, 95]
|
277
|
+
# >> x = a.random(:fail)
|
278
|
+
# => 22
|
279
|
+
# >> search(a, x)
|
280
|
+
# => nil
|
281
|
+
#
|
282
|
+
# :call-seq:
|
283
|
+
# a.random(outcome) => Object
|
284
|
+
#
|
285
|
+
|
217
286
|
def random(outcome)
|
218
287
|
if outcome == :success
|
219
288
|
return self[ rand(self.length) ]
|
@@ -231,108 +300,176 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
231
300
|
return nil
|
232
301
|
end
|
233
302
|
end
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
303
|
+
|
304
|
+
# Return a list of types of items that can be passed as arguments
|
305
|
+
# to <tt>TestArray.new</tt>
|
306
|
+
#
|
307
|
+
# :call-seq:
|
308
|
+
# TestArray.sources() => Array
|
309
|
+
#
|
310
|
+
|
311
|
+
def TestArray.sources
|
312
|
+
return @@sources.keys.sort { |a,b| a.to_s <=> b.to_s }
|
313
|
+
end
|
314
|
+
|
315
|
+
end # class TestArray
|
240
316
|
|
241
317
|
|
318
|
+
# Equivalent to calling <tt>TestArray.new</tt>.
|
319
|
+
#
|
320
|
+
# Examples:
|
321
|
+
# >> TestArray(5)
|
322
|
+
# => [65, 79, 60, 88, 30]
|
323
|
+
# >> TestArray(5, :cars)
|
324
|
+
# => ["mini", "opel", "chevrolet", "isuzu", "cadillac"]
|
325
|
+
#
|
326
|
+
# :call-seq:
|
327
|
+
# TestArray(n, type) => Array
|
328
|
+
#
|
329
|
+
|
242
330
|
def TestArray(n, type = nil)
|
243
331
|
TestArray.new(n, type)
|
244
332
|
end
|
245
|
-
|
246
|
-
|
247
|
-
=begin
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
333
|
+
|
334
|
+
|
335
|
+
=begin rdoc
|
336
|
+
|
337
|
+
== Source
|
338
|
+
|
339
|
+
The Source module provides access the source code for a lab
|
340
|
+
project. When IRB reads the modules from a file, the source is
|
341
|
+
saved in a global array named <tt>SCRIPT_LINES__</tt>. The methods
|
342
|
+
defined in this module scan the source code to look for tags that
|
343
|
+
mark the first and last lines of methods described in the book.
|
344
|
+
|
345
|
+
A method name can be passed either as a String or a Symbol. For
|
346
|
+
example, to print a listing of the +isort+ method a user can call
|
347
|
+
Source.listing("isort")
|
348
|
+
or
|
349
|
+
Source.listing(:isort)
|
350
|
+
|
351
|
+
#--
|
352
|
+
Code that will be accessed by methods in this module should be delimited
|
353
|
+
by :begin and :end tags. See the definition of isort in iterationlab.rb
|
354
|
+
for an example of how to name a method and its helper methods.
|
355
|
+
|
253
356
|
=end
|
254
357
|
|
255
358
|
module Source
|
256
359
|
|
257
360
|
@@probes = Hash.new
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
=begin
|
265
|
-
Print the source code for method +name+.
|
266
|
-
=end
|
361
|
+
@@file = Hash.new
|
362
|
+
@@size = Hash.new
|
363
|
+
@@base = Hash.new
|
364
|
+
@@helpers = Hash.new
|
365
|
+
@@line = nil
|
267
366
|
|
367
|
+
# Print a listing (source code along with line numbers) for method +name+.
|
368
|
+
#
|
369
|
+
# :call-seq:
|
370
|
+
# Source.listing(name)
|
371
|
+
#
|
372
|
+
|
268
373
|
def Source.listing(name)
|
269
374
|
begin
|
270
375
|
id = Source.find(name)
|
271
|
-
|
272
|
-
|
376
|
+
for i in @@base[id]..(@@base[id]+@@size[id]-1)
|
377
|
+
line = SCRIPT_LINES__[@@file[id]][i-1].chomp
|
273
378
|
printf "%3d: %s\n", i - @@base[id] + 1, line.gsub(/\t/," ")
|
274
|
-
|
379
|
+
end
|
275
380
|
rescue Exception => e
|
276
381
|
puts e
|
277
382
|
end
|
278
383
|
return true
|
279
384
|
end
|
280
385
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
386
|
+
# Save a copy of the source code for method +name+ in a file. If a file name is not
|
387
|
+
# specified, the output file name is the name of the method with ".rb" appended.
|
388
|
+
# Prompts the user before overwriting an existing file.
|
389
|
+
#
|
390
|
+
# Example -- Write the source for the method +isort+ to "isort.rb":
|
391
|
+
# Source.checkout("isort")
|
392
|
+
#
|
393
|
+
# Example -- Write the source for +isort+ to "mysort.rb"
|
394
|
+
# Source.checkout("isort", "mysort.rb")
|
395
|
+
#
|
396
|
+
# :call-seq:
|
397
|
+
# Source.checkout(name)
|
398
|
+
#
|
286
399
|
|
287
400
|
def Source.checkout(name, newfilename = nil)
|
288
401
|
begin
|
289
402
|
id = Source.find(name)
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
403
|
+
if newfilename.nil?
|
404
|
+
newfilename = id.to_s
|
405
|
+
end
|
406
|
+
if newfilename[-1] == ?? || newfilename[1] == ?!
|
407
|
+
newfilename.chop!
|
408
|
+
end
|
409
|
+
if newfilename !~ /\.rb$/
|
410
|
+
newfilename += ".rb"
|
411
|
+
end
|
412
|
+
if File.exists?(newfilename)
|
413
|
+
print "Replace existing #{newfilename}? [yn] "
|
414
|
+
if STDIN.gets[0] != ?y
|
415
|
+
puts "File not written"
|
416
|
+
return false
|
417
|
+
end
|
418
|
+
end
|
419
|
+
File.open(newfilename, "w") do |f|
|
420
|
+
f.puts "# #{name} method exported from #{File.basename(@@file[id])}"
|
421
|
+
f.puts
|
309
422
|
Source.print_source(f, id)
|
310
|
-
|
311
|
-
|
312
|
-
|
423
|
+
@@helpers[id].each do |m|
|
424
|
+
f.puts
|
425
|
+
xid = Source.find(m)
|
313
426
|
Source.print_source(f, xid)
|
314
|
-
|
315
|
-
|
427
|
+
end
|
428
|
+
end
|
316
429
|
rescue Exception => e
|
317
430
|
puts e
|
318
431
|
end
|
319
|
-
|
320
|
-
|
432
|
+
puts "Saved a copy of source in #{newfilename}"
|
433
|
+
return true
|
321
434
|
end
|
322
435
|
|
323
|
-
|
324
|
-
|
325
|
-
|
436
|
+
# Helper method called from Source.checkout -- print the code for
|
437
|
+
# method +id+ in the file +f+
|
438
|
+
|
439
|
+
def Source.print_source(f, id) # :nodoc:
|
440
|
+
for i in @@base[id]..(@@base[id]+@@size[id]-1)
|
441
|
+
line = SCRIPT_LINES__[@@file[id]][i-1].chomp
|
326
442
|
f.puts line.gsub(/\t/," ")
|
327
|
-
|
443
|
+
end
|
328
444
|
end
|
329
445
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
446
|
+
# Attach a software probe to a line in method +name+. The line can be specified
|
447
|
+
# by line number relative to the start of the method or a string the specifies a pattern. The third argument
|
448
|
+
# is either a string or the symbol <tt>:count</tt>.
|
449
|
+
# If the third argument is a string, it should be a Ruby expression that will be
|
450
|
+
# evaluated whenever the probe is activated via a call to +trace+ (see RubyLabs#trace).
|
451
|
+
# If the third argument is <tt>:count</tt>, a call to +count+ (see RubyLabs#count)
|
452
|
+
# will count the number of times the specified line is executed.
|
453
|
+
#
|
454
|
+
# Example: attach a probe to the +sieve+ method, so that when +sieve+ is called
|
455
|
+
# via +trace+ the statement "p worklist" will be evaluated whenever line 6 is executed:
|
456
|
+
# >> Source.probe("sieve", 6, "p worksheet")
|
457
|
+
# => true
|
458
|
+
#
|
459
|
+
# Example: attach a counting probe, so +count+ can record how many times line 6 is executed:
|
460
|
+
# >> Source.probe("sieve", 6, :count)
|
461
|
+
# => true
|
462
|
+
#
|
463
|
+
# If the second argument to Source.probe is a string, the probe is attached to every line that
|
464
|
+
# contains that string. For example, to attach a counting probe to every line in +merge+ that
|
465
|
+
# has a call to the << operator:
|
466
|
+
# >> Source.probe("merge", '<<', :count)
|
467
|
+
# => true
|
468
|
+
#
|
469
|
+
# :call-seq:
|
470
|
+
# Source.probe(name, line, spec)
|
471
|
+
#
|
472
|
+
|
336
473
|
|
337
474
|
def Source.probe(name, spec, expr = :count)
|
338
475
|
begin
|
@@ -345,24 +482,25 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
345
482
|
puts e
|
346
483
|
end
|
347
484
|
return true
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
=end
|
485
|
+
end
|
486
|
+
|
487
|
+
# -- Method for internal use only --
|
488
|
+
# Return probes (if any) attached to the specified file, method, and line number.
|
489
|
+
# Intended to be called from a trace func callback (which is why the file is one
|
490
|
+
# of the parameters).
|
355
491
|
|
356
|
-
def Source.probing(filename, method, line)
|
492
|
+
def Source.probing(filename, method, line) # :nodoc:
|
357
493
|
return nil if line == @@line
|
358
494
|
@@line = line
|
359
495
|
return nil unless @@probes[method] && @@file[method] == filename
|
360
496
|
return @@probes[method][line]
|
361
497
|
end
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
498
|
+
|
499
|
+
# Print a description of all the currently defined probes.
|
500
|
+
#
|
501
|
+
# :call-seq:
|
502
|
+
# Source.probes()
|
503
|
+
#
|
366
504
|
|
367
505
|
def Source.probes
|
368
506
|
@@probes.each do |name, plist|
|
@@ -371,12 +509,15 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
371
509
|
printf "%s %2d: %s\n", name, n, exprs
|
372
510
|
end
|
373
511
|
end
|
374
|
-
|
512
|
+
return true
|
375
513
|
end
|
376
514
|
|
377
|
-
|
378
|
-
|
379
|
-
|
515
|
+
# Remove all the probes on a designated method, or if no method name is passed,
|
516
|
+
# remove all probes from all methods.
|
517
|
+
#
|
518
|
+
# :call-seq:
|
519
|
+
# Source.clear(name)
|
520
|
+
#
|
380
521
|
|
381
522
|
def Source.clear(name = nil)
|
382
523
|
@@probes.each do |id, plist|
|
@@ -386,62 +527,56 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
386
527
|
return true
|
387
528
|
end
|
388
529
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
decision if monitoring user-defined methods....
|
530
|
+
# Internal use only -- locate the filename, starting line number, and length
|
531
|
+
# of method +name+, record the
|
532
|
+
# information for any methods that need it. This information only needs to
|
533
|
+
# be found once, so it is recorded in a set of class variables. Revisit this
|
534
|
+
# decision if monitoring user-defined methods....
|
395
535
|
|
396
|
-
|
397
|
-
=end
|
398
|
-
|
399
|
-
def Source.find(name)
|
536
|
+
def Source.find(name) # :nodoc:
|
400
537
|
id = name.to_sym
|
401
538
|
return id if @@file[id] # return if we looked for this source previously
|
402
539
|
|
403
540
|
filename, base, size, helpers = nil, nil, nil, nil
|
404
541
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
542
|
+
catch (:found) do
|
543
|
+
SCRIPT_LINES__.each do |file, lines|
|
544
|
+
line_num = 0
|
545
|
+
lines.each do |s|
|
546
|
+
line_num += 1
|
547
|
+
if match = s.match(/:(begin|end)\s+(.*)/)
|
548
|
+
verb = match[1]
|
549
|
+
names = match[2].split.collect{|x| eval(x)}
|
550
|
+
if names[0] == id
|
551
|
+
if verb == "begin"
|
552
|
+
filename = file
|
553
|
+
base = line_num + 1
|
554
|
+
helpers = names[1..-1]
|
555
|
+
else
|
556
|
+
size = line_num - base
|
557
|
+
throw :found
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
raise "Can't find method named '#{name}'" if size.nil?
|
566
|
+
|
567
|
+
@@file[id] = filename
|
568
|
+
@@size[id] = size
|
569
|
+
@@base[id] = base
|
570
|
+
@@probes[id] = Hash.new
|
571
|
+
@@helpers[id] = helpers
|
572
|
+
return id
|
436
573
|
end
|
437
574
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
to make sure line numbers are valid.
|
442
|
-
=end
|
575
|
+
# Internal use only -- make an array of line numbers to use for probing method +name+.
|
576
|
+
# Argument can be a single line number, an array of line numbers, or a pattern. Checks
|
577
|
+
# to make sure line numbers are valid.
|
443
578
|
|
444
|
-
def Source.lines(spec, id)
|
579
|
+
def Source.lines(spec, id) # :nodoc:
|
445
580
|
if spec.class == Fixnum && Source.range_check(spec, id)
|
446
581
|
return [spec]
|
447
582
|
elsif spec.class == Array
|
@@ -453,27 +588,25 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
453
588
|
return res
|
454
589
|
elsif spec.class == String
|
455
590
|
res = Array.new
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
591
|
+
for i in @@base[id]..(@@base[id]+@@size[id]-1)
|
592
|
+
line = SCRIPT_LINES__[@@file[id]][i-1].chomp
|
593
|
+
res << i - @@base[id] + 1 if line.index(spec)
|
594
|
+
end
|
595
|
+
return res
|
461
596
|
else
|
462
597
|
raise "invalid spec: '#{spec}' (must be an integer, array of integers, or a pattern)"
|
463
598
|
end
|
464
599
|
end
|
465
600
|
|
466
|
-
def Source.range_check(n, id)
|
601
|
+
def Source.range_check(n, id) # :nodoc:
|
467
602
|
max = @@size[id]
|
468
603
|
raise "line number must be between 1 and #{max}" unless n >= 1 && n <= max
|
469
604
|
return true
|
470
605
|
end
|
471
606
|
|
472
|
-
|
473
|
-
Internal use only -- show info about method
|
474
|
-
=end
|
607
|
+
# Internal use only -- show info about method to verify it's being found by Source.lines
|
475
608
|
|
476
|
-
def Source.info(name)
|
609
|
+
def Source.info(name) # :nodoc:
|
477
610
|
unless id = Source.find(name)
|
478
611
|
puts "Can't find method named '#{name}'"
|
479
612
|
return
|
@@ -488,26 +621,95 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
488
621
|
|
489
622
|
end # Source
|
490
623
|
|
624
|
+
|
491
625
|
=begin rdoc
|
492
|
-
|
626
|
+
|
627
|
+
== Canvas
|
628
|
+
|
629
|
+
The Canvas module defines a graphics window that can be used
|
630
|
+
for interactive visualizations. Classes in this module describe objects that are
|
631
|
+
drawn in the window; for example, objects of type Canvas::Circle are circles
|
632
|
+
on the canvas.
|
633
|
+
|
634
|
+
In the current implementation all drawing objects are derived from a base class
|
635
|
+
named TkObject, which provides an interface to the Tk library of ActiveTcl.
|
636
|
+
Instances of TkObject are proxies for the actual objects defined in Tk.
|
637
|
+
When the user calls a method that opens the RubyLabs Canvas, the method
|
638
|
+
initializes a pipe to a new process running the Tcl shell (wish). If a TkObject is
|
639
|
+
updated, it sends a Tcl command over the pipe, and the wish process updates
|
640
|
+
the window.
|
641
|
+
|
493
642
|
=end
|
494
643
|
|
495
644
|
module Canvas
|
496
645
|
|
646
|
+
=begin rdoc
|
647
|
+
|
648
|
+
== TkObject
|
649
|
+
|
650
|
+
Base class of all objects defined for RubyLabs::Canvas. Objects derived from
|
651
|
+
this class are proxies for Tk objects in a wish shell, opened when the canvas
|
652
|
+
is initialized.
|
653
|
+
|
654
|
+
Applications should not try to instantiate a TkObject directly,
|
655
|
+
but instead should call a constructor of one of the derived classes. For
|
656
|
+
example, to draw a circle on the canvas:
|
657
|
+
|
658
|
+
c = Canvas::Circle.new(x, y, r)
|
659
|
+
|
660
|
+
Public instance methods defined here are inherited by all objects derived from TkObject,
|
661
|
+
and can be used to manipulate the object. For example, to change the color of the circle:
|
662
|
+
|
663
|
+
c.fill = 'green'
|
664
|
+
|
665
|
+
The current implementation is very sparse: the only operations defined are the
|
666
|
+
ones needs for the visualizations described in the textbook, which are just
|
667
|
+
the basic methods that create an object or move it around on the screen.
|
668
|
+
|
669
|
+
In addition to the usual attributes, these objects have a "pen point", which is
|
670
|
+
the location of an imaginary pen relative to the object's base
|
671
|
+
coordinate. The pen point is used by methods that draw a track as an object is
|
672
|
+
moved (e.g. see the methods that control the motion of the robot in SphereLab)
|
673
|
+
|
674
|
+
=end
|
675
|
+
|
497
676
|
class TkObject
|
498
677
|
|
499
678
|
attr_accessor :name, :coords, :penpoint
|
500
679
|
|
680
|
+
# Initialization: set the object ID to 0 and save the file descriptor
|
681
|
+
# for the connection to the wish shell.
|
682
|
+
#
|
683
|
+
# :call-seq:
|
684
|
+
# TkObject.reset(p)
|
685
|
+
#
|
686
|
+
|
501
687
|
def TkObject.reset(p)
|
502
688
|
@@pipe = p
|
503
689
|
@@id = 0
|
504
690
|
end
|
505
691
|
|
692
|
+
# Return a unique ID number for a new proxy object.
|
693
|
+
#
|
694
|
+
# :call-seq:
|
695
|
+
# TkObject.nextId()
|
696
|
+
#
|
697
|
+
|
506
698
|
def TkObject.nextId
|
507
699
|
@@id += 1
|
508
700
|
return "obj" + @@id.to_s
|
509
701
|
end
|
510
702
|
|
703
|
+
# Translate a Ruby hash +h+ into a Tk option string. Example:
|
704
|
+
# >> Canvas::TkObject.options( { :outline => :black, :fill => :red } )
|
705
|
+
# => "-fill red -outline black"
|
706
|
+
# Note the ordering of the options in the Tk string may not be the same as
|
707
|
+
# the order they are listed when the hash object is created.
|
708
|
+
#
|
709
|
+
# :call-seq:
|
710
|
+
# TkObject.options(h) => String
|
711
|
+
#
|
712
|
+
|
511
713
|
def TkObject.options(h)
|
512
714
|
a = []
|
513
715
|
h.each do |k,v|
|
@@ -516,23 +718,64 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
516
718
|
return a.join(" ")
|
517
719
|
end
|
518
720
|
|
721
|
+
# Bring an object to the foreground (on top of all other objects on the canvas).
|
722
|
+
#
|
723
|
+
# :call-seq:
|
724
|
+
# x.raise()
|
725
|
+
#
|
726
|
+
|
519
727
|
def raise
|
520
728
|
@@pipe.puts ".canvas raise $#{@name}"
|
521
729
|
end
|
522
730
|
|
731
|
+
# Put an object in the background (below all other objects on the canvas).
|
732
|
+
#
|
733
|
+
# :call-seq:
|
734
|
+
# x.lower()
|
735
|
+
#
|
736
|
+
|
523
737
|
def lower
|
524
738
|
@@pipe.puts ".canvas lower $#{@name}"
|
525
739
|
end
|
526
740
|
|
741
|
+
# Remove an object from the canvas.
|
742
|
+
#
|
743
|
+
# :call-seq:
|
744
|
+
# x.erase()
|
745
|
+
#
|
746
|
+
|
527
747
|
def erase
|
528
748
|
@@pipe.puts ".canvas delete $#{@name}"
|
529
749
|
end
|
530
750
|
|
751
|
+
# Assign new coordinates for an object. Can be used to move or resize an
|
752
|
+
# object, but applications should use the more abstract interface defined by class methods in Canvas
|
753
|
+
# (e.g. Canvas#move).
|
754
|
+
#
|
755
|
+
# Example: if an object x is a proxy for a rectangle
|
756
|
+
# that has an upper left corner at (10,10) and
|
757
|
+
# a lower right corner at (20,50), this call will move it down and to the right
|
758
|
+
# by 10 pixels:
|
759
|
+
# >> x.coords = [20, 20, 30, 60]
|
760
|
+
# => [20, 20, 30, 60]
|
761
|
+
#
|
762
|
+
# :call-seq:
|
763
|
+
# x.coords = [...]
|
764
|
+
#
|
765
|
+
|
531
766
|
def coords=(a)
|
532
767
|
@coords = a
|
533
768
|
@@pipe.puts ".canvas coords $#{@name} #{a.join(' ')}"
|
534
769
|
end
|
535
770
|
|
771
|
+
# Set the fill color for an object. Example:
|
772
|
+
# >> x.fill = "green"
|
773
|
+
# => "green"
|
774
|
+
#
|
775
|
+
# :call-seq:
|
776
|
+
# x.fill = String
|
777
|
+
#
|
778
|
+
|
536
779
|
def fill=(x)
|
537
780
|
@@pipe.puts ".canvas itemconfigure $#{name} -fill #{x}"
|
538
781
|
end
|
@@ -540,10 +783,27 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
540
783
|
end
|
541
784
|
|
542
785
|
=begin rdoc
|
543
|
-
|
786
|
+
|
787
|
+
== Line
|
788
|
+
|
789
|
+
A Line object is a proxy for a line segment defined by a pair of (x,y) coordinates.
|
790
|
+
|
791
|
+
There are no instance methods for Lines beyond those defined in the TkObject base class.
|
544
792
|
=end
|
545
793
|
|
546
794
|
class Line < TkObject
|
795
|
+
|
796
|
+
# Create a new line segment that runs from (x0,y0) to (x1,y1). Attributes
|
797
|
+
# of the line can be passed in a hash object at the end of the argument list.
|
798
|
+
#
|
799
|
+
# Example -- create a gray line one pixel wide from (0,0) to (100,100):
|
800
|
+
# >> z = Line.new(0, 0, 100, 100, :fill => :gray, :width => 1)
|
801
|
+
# => #<RubyLabs::Canvas::Line:...>
|
802
|
+
#
|
803
|
+
# :call-seq:
|
804
|
+
# x = Line.new(x0, y0, x1, y1, args = {})
|
805
|
+
#
|
806
|
+
|
547
807
|
def initialize(x0, y0, x1, y1, args = {})
|
548
808
|
raise "No canvas" unless @@pipe
|
549
809
|
@name = TkObject.nextId
|
@@ -553,6 +813,15 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
553
813
|
@@pipe.puts cmnd
|
554
814
|
end
|
555
815
|
|
816
|
+
# Erase all Line objects that are tagged with the ID +tag+. Tags are optional
|
817
|
+
# arguments defined by passing <tt>-tag x</tt> to <tt>Line.new</tt> (see the
|
818
|
+
# visualization methods in tsplab.rb, which uses tags to erase all
|
819
|
+
# edges from a graph in a single call).
|
820
|
+
#
|
821
|
+
# :call-seq:
|
822
|
+
# Line.erase_all(tag)
|
823
|
+
#
|
824
|
+
|
556
825
|
def Line.erase_all(tag)
|
557
826
|
@@pipe.puts ".canvas delete withtag #{tag}"
|
558
827
|
end
|
@@ -560,10 +829,27 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
560
829
|
end
|
561
830
|
|
562
831
|
=begin rdoc
|
563
|
-
|
832
|
+
|
833
|
+
== Circle
|
834
|
+
|
835
|
+
A Circle object is a proxy for a Tk oval.
|
836
|
+
|
837
|
+
There are no instance methods for Circles beyond those defined in the TkObject base class.
|
564
838
|
=end
|
565
839
|
|
566
840
|
class Circle < TkObject
|
841
|
+
|
842
|
+
# Create a new circle with center at (x,y) and radius r. Attributes
|
843
|
+
# of the circle can be passed in a hash object at the end of the argument list.
|
844
|
+
#
|
845
|
+
# Example -- create a green cicle with radius 5 at location (20,20):
|
846
|
+
# >> x = Canvas::Circle.new(20, 20, 5, :fill => "green")
|
847
|
+
# => #<RubyLabs::Canvas::Circle:...>
|
848
|
+
#
|
849
|
+
# :call-seq:
|
850
|
+
# x = Circle.new(x, y, r, args = {})
|
851
|
+
#
|
852
|
+
|
567
853
|
def initialize(x, y, r, args = {})
|
568
854
|
raise "No canvas" unless @@pipe
|
569
855
|
@name = TkObject.nextId
|
@@ -575,10 +861,29 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
575
861
|
end
|
576
862
|
|
577
863
|
=begin rdoc
|
578
|
-
|
864
|
+
|
865
|
+
== Rectangle
|
866
|
+
|
867
|
+
A Rectangle object is a proxy for a Tk rectangle.
|
868
|
+
|
869
|
+
There are no instance methods for Rectangles beyond those defined in the TkObject base class.
|
579
870
|
=end
|
580
871
|
|
581
872
|
class Rectangle < TkObject
|
873
|
+
|
874
|
+
# Create a new rectangle with its upper left corner at (x0,y0)
|
875
|
+
# lower right corner at (x1,y1). Attributes
|
876
|
+
# of the rectangle can be passed in a hash object at the end of the argument list.
|
877
|
+
#
|
878
|
+
# Example -- create a 20x20 square with upper left corner at (10,10), with a dark green
|
879
|
+
# outline and yellow interior:
|
880
|
+
# >> x = Canvas::Rectangle.new(10, 10, 30, 30, :fill => "yellow", :outline => "darkgreen")
|
881
|
+
# => #<RubyLabs::Canvas::Rectangle:...>
|
882
|
+
#
|
883
|
+
# :call-seq:
|
884
|
+
# x = Rectangle.new(x0, y0, x1, y1, args = {})
|
885
|
+
#
|
886
|
+
|
582
887
|
def initialize(x0, y0, x1, y1, args = {})
|
583
888
|
raise "No canvas" unless @@pipe
|
584
889
|
@name = TkObject.nextId
|
@@ -590,12 +895,30 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
590
895
|
end
|
591
896
|
|
592
897
|
=begin rdoc
|
593
|
-
|
594
|
-
|
595
|
-
|
898
|
+
|
899
|
+
== Polygon
|
900
|
+
|
901
|
+
A Polygon object is a proxy for a Tk polygon.
|
902
|
+
|
903
|
+
In addition to the methods defined in the TkObject base class, a method named +rotate+
|
904
|
+
will rotate a Polygon by a specified angle.
|
596
905
|
=end
|
597
906
|
|
598
907
|
class Polygon < TkObject
|
908
|
+
|
909
|
+
# Create a new polygon. The first argument is an array of vertex coordinates. If
|
910
|
+
# the polygon has +n+ vertices, the argument should be a flat array of 2*n points.
|
911
|
+
# Attributes of the polygon can be passed in a hash object at the end of the argument list.
|
912
|
+
#
|
913
|
+
# Example -- create a small triangle in the upper left corner of the canvas, with a black
|
914
|
+
# outline and green interior:
|
915
|
+
# >> x = Canvas::Polygon.new([10,10,20,20,30,10], :fill => "green", :outline => "black")
|
916
|
+
# => #<RubyLabs::Canvas::Polygon ... >
|
917
|
+
#
|
918
|
+
# :call-seq:
|
919
|
+
# x = Polygon.new(a, args = {})
|
920
|
+
#
|
921
|
+
|
599
922
|
def initialize(a, args = {})
|
600
923
|
raise "No canvas" unless @@pipe
|
601
924
|
@name = TkObject.nextId
|
@@ -604,14 +927,56 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
604
927
|
cmnd = "set #{@name} [.canvas create polygon #{@coords.join(" ")} #{TkObject.options(args)}]"
|
605
928
|
@@pipe.puts cmnd
|
606
929
|
end
|
930
|
+
|
931
|
+
# Rotate the polygon by an angle +theta+ (expressed in degrees). The object is
|
932
|
+
# rotated about the point defined by the first pair of (x,y) coordinates. The
|
933
|
+
# return value is an array with the new coordinates.
|
934
|
+
#
|
935
|
+
# Example: Suppose +x+ is a square with coordinates (10,10), (20,10), (20,20), and (10,20).
|
936
|
+
# This call will rotate it clockwise by 45 degrees and return the new vertices:
|
937
|
+
# >> x.rotate(45)
|
938
|
+
# => [10.0, 10.0, 17.07, 17.07, 10.0, 24.14, 2.93, 17.07]
|
939
|
+
|
940
|
+
def rotate(theta)
|
941
|
+
theta = Canvas.radians(theta)
|
942
|
+
a = self.coords
|
943
|
+
x0 = a[0]
|
944
|
+
y0 = a[1]
|
945
|
+
(0...a.length).step(2) do |i|
|
946
|
+
x = a[i] - x0
|
947
|
+
y = a[i+1] - y0
|
948
|
+
a[i] = x0 + x * cos(theta) - y * sin(theta)
|
949
|
+
a[i+1] = y0 + x * sin(theta) + y * cos(theta)
|
950
|
+
end
|
951
|
+
self.coords = a
|
952
|
+
return a
|
953
|
+
end
|
607
954
|
end
|
608
955
|
|
609
956
|
=begin rdoc
|
610
|
-
|
611
|
-
|
957
|
+
|
958
|
+
== Text
|
959
|
+
|
960
|
+
A Text object is a string displayed on the RubyLabs canvas.
|
961
|
+
|
612
962
|
=end
|
613
963
|
|
614
964
|
class Text < TkObject
|
965
|
+
|
966
|
+
# Display string +s+ at location (+x+,+y+) on the canvas. By default the coordinates
|
967
|
+
# specify the location of the top left of the first character in the string (in Tk
|
968
|
+
# terminology, the anchor point for the text is "northwest"). A different anchor
|
969
|
+
# position and other attributes of the text can be passed in a hash object at the
|
970
|
+
# end of the argument list.
|
971
|
+
#
|
972
|
+
# Example -- display a message in dark green letters:
|
973
|
+
# >> x = Canvas::Text.new("hello", 50, 50, :fill => 'darkgreen')
|
974
|
+
# => #<RubyLabs::Canvas::Text: ... >
|
975
|
+
#
|
976
|
+
# :call-seq:
|
977
|
+
# x = Text.new(s, x, y, args = {})
|
978
|
+
#
|
979
|
+
|
615
980
|
def initialize(s, x, y, args = {})
|
616
981
|
raise "No canvas" unless @@pipe
|
617
982
|
@name = TkObject.nextId
|
@@ -623,15 +988,48 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
623
988
|
@@pipe.puts cmnd
|
624
989
|
end
|
625
990
|
|
991
|
+
# Replace the string displayed by a text object. Example: erase the current string
|
992
|
+
# for object +x+ and replace it with "hello, world":
|
993
|
+
# >> x.update("hello, world")
|
994
|
+
# => nil
|
995
|
+
# The new string is displayed at the same location and with the same attributes
|
996
|
+
# as the old string.
|
997
|
+
|
626
998
|
def update(s)
|
627
999
|
@@pipe.puts ".canvas itemconfigure $#{name} -text \"#{s}\""
|
628
1000
|
end
|
629
1001
|
end
|
630
1002
|
|
1003
|
+
=begin rdoc
|
1004
|
+
|
1005
|
+
== Font
|
1006
|
+
|
1007
|
+
A Font object defines the appearance of strings displayed by a Text object. A Font
|
1008
|
+
object is a proxy for a Tk font object. The proxy is never used by an application;
|
1009
|
+
instead, when text is created, it is passed the name of the Tk font (see the example in
|
1010
|
+
the description of Font.new).
|
1011
|
+
|
1012
|
+
=end
|
1013
|
+
|
631
1014
|
class Font < TkObject
|
632
1015
|
|
633
1016
|
@@fonts = []
|
634
1017
|
|
1018
|
+
# Create a font named +name+ with attributes defined in +args+. When creating
|
1019
|
+
# a new Text object, +name+ can be passed as an argument to Text.new so the
|
1020
|
+
# string will be displayed in the specified font.
|
1021
|
+
#
|
1022
|
+
# Example -- create a new font for displaying fixed width text, and then display
|
1023
|
+
# a message using this font:
|
1024
|
+
# >> f = Canvas::Font.new('code', :family => 'Courier', :size => 20)
|
1025
|
+
# => #<RubyLabs::Canvas::Font:0x1012587b0 @name="code">
|
1026
|
+
# >> t = Canvas::Text.new("Go Ducks!", 100, 100, :font => 'code')
|
1027
|
+
# => #<RubyLabs::Canvas::Text:0x101249300 @coords=[100, 100], @name="obj3", @penpoint=nil>
|
1028
|
+
#
|
1029
|
+
# :call-seq:
|
1030
|
+
# x = Font.new(name,args)
|
1031
|
+
#
|
1032
|
+
|
635
1033
|
def initialize(name, args)
|
636
1034
|
@name = name
|
637
1035
|
if @@fonts.index(name).nil?
|
@@ -643,22 +1041,32 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
643
1041
|
|
644
1042
|
end
|
645
1043
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
1044
|
+
# Initialize a drawing canvas with the specified width and height. If this is the first
|
1045
|
+
# call in an IRB session, open a connection to a wish shell and send it Tcl commands to
|
1046
|
+
# make a window with a single widget, a canvas centered in the middle. The +title+ argument
|
1047
|
+
# passed to Canvas.init becomes part of the new window.
|
1048
|
+
#
|
1049
|
+
# If this is not the first call to Canvas.init, the existing Tk canvas is resized according
|
1050
|
+
# to the new width and height arguments and the window is renamed using the new name.
|
1051
|
+
#
|
1052
|
+
# An optional fourth argument can be the symbol <tt>:debug</tt>, in which case the return
|
1053
|
+
# value is the Ruby Pipe object used to communicate with Tk. The Pipe can be useful for
|
1054
|
+
# developing new TkObject objects, since it can be used to see how Tcl commands are
|
1055
|
+
# processed. Example:
|
1056
|
+
#
|
1057
|
+
# >> p = Canvas.init(200, 200, "Test", :debug)
|
1058
|
+
# => #<IO:0x1012a1cd0>
|
1059
|
+
# >> p.puts ".canvas create text 30 30 -text hello"
|
1060
|
+
# => nil
|
1061
|
+
#
|
1062
|
+
# :call-seq:
|
1063
|
+
# Canvas.init(width, height, title, opts = nil)
|
1064
|
+
#
|
1065
|
+
#--
|
1066
|
+
# TODO Read the path to wish from a configuration file, so users can alter it
|
1067
|
+
# TODO there are probably other configuration options that can go there, too
|
1068
|
+
# TODO use popen3 on OSX, capture stderr
|
656
1069
|
|
657
|
-
@@tkpipe = nil
|
658
|
-
@@title = ""
|
659
|
-
@@height = 0
|
660
|
-
@@width = 0
|
661
|
-
|
662
1070
|
def Canvas.init(width, height, title, *opts)
|
663
1071
|
|
664
1072
|
@@title = "RubyLabs::#{title}"
|
@@ -669,7 +1077,7 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
669
1077
|
if @@tkpipe.nil?
|
670
1078
|
if Canvas.OS == "Windows"
|
671
1079
|
@@tkpipe = IO.popen("wish", "w")
|
672
|
-
|
1080
|
+
elsif Canvas.OS == "Linux"
|
673
1081
|
@@tkpipe = IO.popen("/opt/ActiveTcl-8.5/bin/wish", "w")
|
674
1082
|
else
|
675
1083
|
@@tkpipe = IO.popen("/usr/local/bin/wish", "w")
|
@@ -687,9 +1095,16 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
687
1095
|
@@tkpipe.puts "wm geometry . #{width+2*pad}x#{height+2*pad}+30+50"
|
688
1096
|
@@tkpipe.puts "wm title . #{@@title}"
|
689
1097
|
|
690
|
-
|
1098
|
+
return opts[0] == :debug ? @@tkpipe : true
|
691
1099
|
end
|
692
1100
|
|
1101
|
+
# Send an +exit+ command to the wish shell, which closes the drawing window and
|
1102
|
+
# terminates the shell.
|
1103
|
+
#
|
1104
|
+
# :call-seq:
|
1105
|
+
# Canvas.close()
|
1106
|
+
#
|
1107
|
+
|
693
1108
|
def Canvas.close
|
694
1109
|
if @@tkpipe
|
695
1110
|
@@tkpipe.puts "exit"
|
@@ -698,22 +1113,37 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
698
1113
|
end
|
699
1114
|
end
|
700
1115
|
|
701
|
-
|
702
|
-
|
703
|
-
|
1116
|
+
# Return the current width of the canvas.
|
1117
|
+
#
|
1118
|
+
# :call-seq:
|
1119
|
+
# Canvas.width() => Fixnum
|
1120
|
+
#
|
704
1121
|
|
705
1122
|
def Canvas.width
|
706
1123
|
@@width
|
707
1124
|
end
|
708
1125
|
|
1126
|
+
# Return the current height of the canvas.
|
1127
|
+
#
|
1128
|
+
# :call-seq:
|
1129
|
+
# Canvas.height() => Fixnum
|
1130
|
+
#
|
1131
|
+
|
709
1132
|
def Canvas.height
|
710
1133
|
@@height
|
711
1134
|
end
|
712
1135
|
|
1136
|
+
# Return a reference to the Pipe object used to communicate with the wish shell.
|
1137
|
+
#
|
1138
|
+
# :call-seq:
|
1139
|
+
# Canvas.pipe() => IO
|
1140
|
+
#
|
1141
|
+
|
713
1142
|
def Canvas.pipe
|
714
1143
|
@@tkpipe
|
715
1144
|
end
|
716
1145
|
|
1146
|
+
#--
|
717
1147
|
# Idea for the future, abandoned for now: allow applications to define a coordinate
|
718
1148
|
# system, e.g. cartesian with the origin in the middle of the canvas, and map coords
|
719
1149
|
# pass to line, rectangle, etc from user-specified coordinates to Tk coordinates.
|
@@ -737,14 +1167,22 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
737
1167
|
# a[i], a[i+1] = @@map.call( a[i], a[i+1] )
|
738
1168
|
# end
|
739
1169
|
# end
|
1170
|
+
#++
|
740
1171
|
|
741
|
-
#
|
1172
|
+
# Return a string that uses the <tt>RUBY_PLATFORM</tt> environment variable to
|
1173
|
+
# determine the host operating system type. Possible return values are "Mac OS X",
|
1174
|
+
# "Linux", or "Windows".
|
1175
|
+
#
|
1176
|
+
# :call-seq:
|
1177
|
+
# Canvas.OS() => String
|
1178
|
+
#
|
1179
|
+
#--
|
742
1180
|
# TODO: find out what the platform string is for the legacy windows one-click installer;
|
743
1181
|
# the new installer uses the "mingw" compiler.
|
744
1182
|
|
745
1183
|
def Canvas.OS
|
746
1184
|
if RUBY_PLATFORM =~ %r{darwin}
|
747
|
-
return "OS X"
|
1185
|
+
return "Mac OS X"
|
748
1186
|
elsif RUBY_PLATFORM =~ %r{linux}
|
749
1187
|
return "Linux"
|
750
1188
|
elsif RUBY_PLATFORM =~ %r{mingw}
|
@@ -754,9 +1192,20 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
754
1192
|
end
|
755
1193
|
end
|
756
1194
|
|
757
|
-
|
758
|
-
|
759
|
-
|
1195
|
+
# Move an object by a distance +dx+ vertically and a distance +dy+ horizontally.
|
1196
|
+
# If the fourth argument is the symbol <tt>:track</tt> a line segment is drawn
|
1197
|
+
# connecting the pen position of the object's previous location with the
|
1198
|
+
# pen position of its new location. The return value is an array with the new
|
1199
|
+
# coordinates.
|
1200
|
+
#
|
1201
|
+
# Example: Suppose +x+ is a Polygon with coordinates (10,10), (20,20), and (30,10).
|
1202
|
+
# This call moves it down and to the right by 10 pixels and returns the new location:
|
1203
|
+
# >> Canvas.move(x, 10, 10)
|
1204
|
+
# => [20, 20, 30, 30, 40, 20]
|
1205
|
+
#
|
1206
|
+
# :call-seq:
|
1207
|
+
# Canvas.move(obj, dx, dy, track = nil)
|
1208
|
+
#
|
760
1209
|
|
761
1210
|
def Canvas.move(obj, dx, dy, option = nil)
|
762
1211
|
a = obj.coords
|
@@ -778,91 +1227,116 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
778
1227
|
return a
|
779
1228
|
end
|
780
1229
|
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
#
|
787
|
-
#
|
788
|
-
#
|
789
|
-
#
|
790
|
-
|
791
|
-
# obj.peny = yoff
|
792
|
-
# @@objects << obj
|
793
|
-
# return obj
|
794
|
-
# end
|
795
|
-
|
796
|
-
=begin rdoc
|
797
|
-
Rotate an object by an angle theta (expressed in degrees). The object is
|
798
|
-
rotated about the point defined by the first pair of (x,y) coordinates.
|
799
|
-
=end
|
800
|
-
|
801
|
-
def Canvas.rotate(obj, theta)
|
802
|
-
theta = Canvas.radians(theta)
|
803
|
-
a = obj.coords
|
804
|
-
x0 = a[0]
|
805
|
-
y0 = a[1]
|
806
|
-
(0...a.length).step(2) do |i|
|
807
|
-
x = a[i] - x0
|
808
|
-
y = a[i+1] - y0
|
809
|
-
a[i] = x0 + x * cos(theta) - y * sin(theta)
|
810
|
-
a[i+1] = y0 + x * sin(theta) + y * cos(theta)
|
811
|
-
end
|
812
|
-
obj.coords = a
|
813
|
-
return a
|
814
|
-
end
|
815
|
-
|
1230
|
+
# Convert an angle from degrees to radians. Example:
|
1231
|
+
# >> Math::PI / 4
|
1232
|
+
# => 0.785398163397448
|
1233
|
+
# >> Canvas.radians(45)
|
1234
|
+
# => 0.785398163397448
|
1235
|
+
#
|
1236
|
+
# :call-seq:
|
1237
|
+
# Canvas.radians(deg) => Float
|
1238
|
+
#
|
1239
|
+
|
816
1240
|
def Canvas.radians(deg)
|
817
1241
|
deg * Math::PI / 180
|
818
1242
|
end
|
819
1243
|
|
1244
|
+
# Convert an angle from radians to degrees. Example:
|
1245
|
+
# >> Canvas.degrees( Math::PI / 2 )
|
1246
|
+
# => 90.0
|
1247
|
+
#
|
1248
|
+
# :call-seq:
|
1249
|
+
# Canvas.degrees(rad) => Float
|
1250
|
+
#
|
1251
|
+
|
820
1252
|
def Canvas.degrees(rad)
|
821
1253
|
180 * rad / Math::PI
|
822
1254
|
end
|
823
1255
|
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
1256
|
+
# Make a range of colors starting from +first+ and going to +last+ in +n+ steps.
|
1257
|
+
# Color arguments should be be 3-tuples of integer RGB values. The
|
1258
|
+
# result is an array that starts with +first+, has +n+-1 intermediate colors,
|
1259
|
+
# and ends with +last+.
|
1260
|
+
#
|
1261
|
+
# Example:
|
1262
|
+
# >> Canvas.palette( [255,0,0], [0,0,0], 10)
|
1263
|
+
# => ["#FF0000", "#E60000", "#CD0000", ... "#1E0000", "#000000"]
|
1264
|
+
# The return value is an array of 11 colors starting with red and ending with black.
|
1265
|
+
#
|
1266
|
+
# :call-seq:
|
1267
|
+
# Canvas.palette(first, last, n) => Array
|
1268
|
+
#
|
832
1269
|
|
833
1270
|
def Canvas.palette(first, last, n)
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
1271
|
+
d = Array.new(3)
|
1272
|
+
3.times { |i| d[i] = (first[i] - last[i]) / n }
|
1273
|
+
a = [first]
|
1274
|
+
(n-1).times do |i|
|
1275
|
+
a << a.last.clone
|
1276
|
+
3.times { |j| a.last[j] -= d[j] }
|
1277
|
+
end
|
1278
|
+
a << last
|
1279
|
+
a.map { |c| sprintf("#%02X%02X%02X",c[0],c[1],c[2]) }
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
@@tkpipe = nil
|
1283
|
+
@@title = ""
|
1284
|
+
@@height = 0
|
1285
|
+
@@width = 0
|
1286
|
+
|
845
1287
|
end # Canvas
|
846
1288
|
|
847
1289
|
=begin rdoc
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
a
|
1290
|
+
|
1291
|
+
== PriorityQueue
|
1292
|
+
|
1293
|
+
A PriorityQueue is a collection of objects that is always maintained in order.
|
1294
|
+
Any kind of object can be in the queue as long as it can be compared with the < operator.
|
1295
|
+
More precisely, if an object +x+ responds to the < operator, and +x+ < +y+ is
|
1296
|
+
defined for every object +y+ already in the queue, then +x+ can be inserted.
|
1297
|
+
|
1298
|
+
The methods that insert and remove items check to see if an instance variable
|
1299
|
+
named <tt>@on_canvas</tt> is +true+. If so, a method named +update+ is called.
|
1300
|
+
+update+ is not defined here, but is added to the class by modules that
|
1301
|
+
use the queue during visualizations (see the definition of the priority queue
|
1302
|
+
used in the Huffman tree project in bitlab.rb for an example).
|
1303
|
+
|
1304
|
+
Several methods of the Array class are defined dynamically in the PriorityQueue
|
1305
|
+
class when this module
|
1306
|
+
is loaded. These methods have the same meaning for PriorityQueue objects as
|
1307
|
+
they do for Array objects:
|
1308
|
+
|
1309
|
+
+length+:: return the number of items in the queue
|
1310
|
+
+first+:: return a reference to the first item in the queue
|
1311
|
+
+last+:: return a reference to the last item in the queue
|
1312
|
+
+to_s+:: generate a String representation of the queue (by calling Array#to_s)
|
1313
|
+
+inspect+:: generate a String representation of the queue (by calling Array#inspect)
|
1314
|
+
+clear+:: remove all items from the queue
|
1315
|
+
<tt>empty?</tt>:: return +true+ if the queue has no items
|
1316
|
+
|
858
1317
|
=end
|
859
1318
|
|
860
1319
|
class PriorityQueue
|
861
1320
|
|
1321
|
+
# Create a new, initially empty, priority queue.
|
1322
|
+
|
862
1323
|
def initialize
|
863
1324
|
@q = Array.new
|
864
1325
|
end
|
865
|
-
|
1326
|
+
|
1327
|
+
# Insert an item into the queue. The item's location is determined by how
|
1328
|
+
# it compares with other items, according to the < operator. Specifically,
|
1329
|
+
# when item +x+ is added to a queue, it it put before the first item +y+
|
1330
|
+
# where +x+ < +y+.
|
1331
|
+
#
|
1332
|
+
# Example: suppose object +q+ has three strings:
|
1333
|
+
# >> q
|
1334
|
+
# => ["Au", "He", "O"]
|
1335
|
+
# This expression adds the string "Fe" to the queue:
|
1336
|
+
# >> q << "Fe"
|
1337
|
+
# => ["Au", "Fe", "He", "O"]
|
1338
|
+
# The new string went into the second position because "Fe" < "Au" is false but "Fe" < "He" is true.
|
1339
|
+
|
866
1340
|
def <<(obj)
|
867
1341
|
raise "Object cannot be inserted into priority queue" unless obj.respond_to?(:<)
|
868
1342
|
i = 0
|
@@ -875,57 +1349,129 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
875
1349
|
return @q
|
876
1350
|
end
|
877
1351
|
|
1352
|
+
# Remove the first item from the queue, returning a reference to that item.
|
1353
|
+
#
|
1354
|
+
# Example: suppose object +q+ has three strings:
|
1355
|
+
# >> q
|
1356
|
+
# => ["Au", "He", "O"]
|
1357
|
+
# Then a call to shift removes the string "Au" and leaves the remaining items in order in the queue:
|
1358
|
+
# >> x = q.shift
|
1359
|
+
# => "Au"
|
1360
|
+
# >> q
|
1361
|
+
# => ["He", "O"]
|
1362
|
+
#
|
1363
|
+
|
878
1364
|
def shift
|
879
1365
|
res = @q.shift
|
880
1366
|
update(:shift, res) if @on_canvas
|
881
1367
|
return res
|
882
1368
|
end
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
1369
|
+
|
1370
|
+
# Return the item at location +i+ in the queue. *Note*: unlike Array objects, an
|
1371
|
+
# index expression cannot be used on the left side of an assignment. If
|
1372
|
+
# +q+ is a PriorityQueue object,
|
1373
|
+
# x = q[i]
|
1374
|
+
# is valid, but
|
1375
|
+
# q[i] = x
|
1376
|
+
# is undefined.
|
887
1377
|
|
888
1378
|
def [](i)
|
889
1379
|
@q[i]
|
890
1380
|
end
|
891
1381
|
|
1382
|
+
# Call +f+ for every item in the queue, and return an array with
|
1383
|
+
# the result of each call (essentially the same as the +map+ operation
|
1384
|
+
# defined for Ruby's Enumeration interface).
|
1385
|
+
#
|
1386
|
+
# Example:
|
1387
|
+
# >> q
|
1388
|
+
# => ["avocado", "boysenberry", "clementine", "elderberry", "loquat"]
|
1389
|
+
# >> q.collect { |x| x.length }
|
1390
|
+
# => [7, 11, 10, 10, 6]
|
1391
|
+
|
892
1392
|
def collect(&f)
|
893
1393
|
@q.map { |x| f.call(x) }
|
894
1394
|
end
|
895
1395
|
|
1396
|
+
# Evaluate block +b+ for every item in the queue (equivalent to <tt>Array#each</tt>)
|
1397
|
+
#
|
1398
|
+
# Example:
|
1399
|
+
# >> q
|
1400
|
+
# => ["Au", "He", "O"]
|
1401
|
+
# >> q.each { |x| puts x }
|
1402
|
+
# Ar
|
1403
|
+
# He
|
1404
|
+
# O
|
1405
|
+
# => ["Au", "He", "O"]
|
1406
|
+
|
896
1407
|
def each(&b)
|
897
1408
|
@q.each &b
|
898
1409
|
end
|
899
1410
|
|
1411
|
+
# Evaluate block +b+ for every item in the queue, also passing the item's
|
1412
|
+
# location in the queue to the block (equivalent to <tt>Array#each_with_index</tt>)
|
1413
|
+
#
|
1414
|
+
# Example:
|
1415
|
+
# >> q
|
1416
|
+
# => ["Au", "He", "O"]
|
1417
|
+
# >> q.each_with_index { |x,i| puts "#{i}: #{x}" }
|
1418
|
+
# 0: Ar
|
1419
|
+
# 1: He
|
1420
|
+
# 2: O
|
1421
|
+
# => ["Au", "He", "O"]
|
1422
|
+
|
900
1423
|
def each_with_index(&b)
|
901
1424
|
@q.each_with_index &b
|
902
1425
|
end
|
903
1426
|
|
1427
|
+
%w{length first last to_s inspect clear empty?}.each do |name|
|
1428
|
+
eval "def #{name}() @q.#{name} end"
|
1429
|
+
end
|
1430
|
+
|
904
1431
|
end # PriorityQueue
|
905
1432
|
|
906
1433
|
end # RubyLabs
|
907
1434
|
|
908
|
-
class Fixnum
|
909
|
-
|
910
1435
|
=begin rdoc
|
911
1436
|
|
912
|
-
|
913
|
-
to numbers between 0 and 25.
|
1437
|
+
== Fixnum
|
914
1438
|
|
915
|
-
|
916
|
-
|
917
|
-
|
1439
|
+
When the RubyLabs module is loaded it defines a new method named +ord+ to the
|
1440
|
+
Fixnum class. In Ruby 1.8, using the <tt>[]</tt> operator to access items in a String object
|
1441
|
+
returns the ASCII value of a character. The +ord+ method defined here (and used by hash functions defined in hashlab.rb)
|
1442
|
+
maps the ASCII value of a letter to a number between 0 and 25.
|
1443
|
+
|
1444
|
+
The BitLab module also extends Fixnum by defining a method named +code+ that returns a Code
|
1445
|
+
object containing the binary or hexadecimal representation of an integer.
|
1446
|
+
#--
|
1447
|
+
NOTE: +ord+ is built in to Ruby 1.9, so this method will have to be renamed or reimplemented
|
1448
|
+
when RubyLabs is ported to 1.9.
|
918
1449
|
=end
|
919
1450
|
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
1451
|
+
class Fixnum
|
1452
|
+
|
1453
|
+
# If a number is the ASCII code for a letter from the Roman alphabet (upper or lower case,
|
1454
|
+
# in the range 'A' to 'Z') map it to a number between 0 and 25, otherwise just return the
|
1455
|
+
# value of the number.
|
1456
|
+
#
|
1457
|
+
# Example:
|
1458
|
+
# >> "Ducks!".each_byte { |x| puts x.ord }
|
1459
|
+
# 3
|
1460
|
+
# 20
|
1461
|
+
# 2
|
1462
|
+
# 10
|
1463
|
+
# 18
|
1464
|
+
# 33
|
1465
|
+
|
1466
|
+
def ord
|
1467
|
+
if self >= ?a && self <= ?z
|
1468
|
+
self - ?a
|
1469
|
+
elsif self >= ?A && self <= ?Z
|
1470
|
+
self - ?A
|
1471
|
+
else
|
1472
|
+
self
|
1473
|
+
end
|
1474
|
+
end
|
929
1475
|
|
930
1476
|
end # Fixnum
|
931
1477
|
|