rubylabs 0.5.5 → 0.6.2

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/lib/randomlab.rb CHANGED
@@ -11,145 +11,122 @@ module RubyLabs
11
11
 
12
12
  module RandomLab
13
13
 
14
- require 'statistics2.rb'
15
-
16
14
  =begin rdoc
17
- Simple random number generator, using "roulette wheel" algorithm.
15
+ Pseudo-random number generator. Constraints on a, c, and m:
16
+ * c and m must be relatively prime
17
+ * a-1 divisible by prime factors of m
18
+ * if m is multiple of 4, a-1 must also be a multiple of 4
19
+
20
+ Note: some good values (for small m) from Numerical Recipes:
21
+ m = 53125, a = 171, c = 11213
18
22
  =end
19
23
 
20
- class Wheel
24
+ class PRNG
21
25
 
22
- @@x = 0
23
- @@c = 337
24
- @@m = 1000
26
+ attr_accessor :a, :c, :m, :x
27
+
28
+ def initialize(a, c, m)
29
+ @a = a
30
+ @c = c
31
+ @m = m
32
+ @x = 0
33
+ end
25
34
 
26
- def Wheel.rand(n)
27
- @@x = (@@x + @@c) % @@m
28
- return @@x % n
29
- end
35
+ def state
36
+ return @x
37
+ end
30
38
 
31
- def Wheel.params(*args)
32
- if args.length == 0
33
- return [@@x, @@m, @@c]
34
- elsif args.length == 3
35
- @@x, @@m, @@c = args
36
- else
37
- raise "params: expected 3 integers"
39
+ def seed(val)
40
+ @x = val
38
41
  end
39
- end
40
- end
41
-
42
- =begin rdoc
43
- Better random number generator, using parameters from Numerical Recipes in C.
44
- =end
45
-
46
- class PRNG
47
-
48
- @@x = 0
49
- @@a = 3
50
- @@c = 337
51
- @@m = 1000
52
42
 
53
- def PRNG.rand(n)
54
- @@x = (@@x * @@a + @@c) % @@m
55
- return @@x % n
56
- end
43
+ # :begin :advance
44
+ def advance
45
+ @x = (@x * @a + @c) % @m
46
+ end
47
+ # :end :advance
57
48
 
58
- def PRNG.params(*args)
59
- if args.length == 0
60
- return [@@x, @@m, @@a, @@c]
61
- elsif args.length == 4
62
- @@x, @@m, @@a, @@c = args
63
- else
64
- raise "params: expected 4 integers"
49
+ # :begin :random
50
+ def random(min, max)
51
+ return nil if max <= min
52
+ range = max - min + 1
53
+ return (advance() % range) + min
65
54
  end
66
- end
55
+ # :end :random
67
56
 
68
- end
69
-
70
- # :begin :uniform_test
71
- def uniform_test(n, m, opt = nil)
72
- bins = Array.new(m,0)
73
- n.times { x = rand(m); bins[x] += 1 }
74
- exp = Array.new(m, n/m)
75
- if opt == :trace
76
- print "bins: "
77
- p bins
78
- end
79
- return chi_square_test(bins, exp, opt)
80
- end
81
- # :end :uniform_test
82
-
83
- # :begin :poker_test
84
- def poker_test(n, opt = nil)
85
- types = [:nada, :pair, :two_pair, :three_of_a_kind, :full_house, :straight, :flush, :four_of_a_kind, :straight_flush]
86
- counts = Hash.new
87
- types.each { |x| counts[x] = 0 }
88
- n.times do
89
- h = random_deal(5)
90
- # p h
91
- t = poker_hand(h)
92
- counts[t] += 1
57
+ def inspect
58
+ sprintf "#<RandomLab::PRNG a: #{@a} c: #{@c} m: #{@m}>"
59
+ end
60
+
61
+ alias to_s inspect
62
+
63
+ end # class PRNG
64
+
65
+ # :begin :prng_sequence
66
+ def prng_sequence(a, c, m)
67
+ seq = [0]
68
+ (m-1).times do
69
+ seq << (a * seq.last + c) % m
70
+ end
71
+ return seq
93
72
  end
94
- return counts
95
- end
96
- # :end :poker_test
73
+ # :end :prng_sequence
97
74
 
98
75
  =begin rdoc
99
- Compute the chi-square statistic for a set of bins containing results from n random draws.
100
- The two parameters are arrays of equal length, containing the observed and expected values.
101
- Return +true+ if the chi-square is less than the critical value for p = 0.95 (i.e. return
102
- false if the hypothesis that the distributions are the same is rejected).
76
+ Make a new deck of cards
103
77
  =end
104
78
 
105
- def chi_square_test(obs, exp, opt = nil)
106
- x2 = chi_square(obs, exp)
107
- p = Statistics2.chi2dist(obs.length-1, x2)
108
- if opt == :trace
109
- printf "chi-square: %7.3f p = %.2f\n", x2, p
110
- end
111
- return p < 0.95
112
- end
113
-
114
- def chi_square(obs, exp)
115
- sum = 0.0
116
- for i in 0...obs.length
117
- sum += ( (obs[i] - exp[i])**2 ).to_f / exp[i]
79
+ def new_deck
80
+ (0..51).map { |i| Card.new(i) }
118
81
  end
119
- return sum
120
- end
121
82
 
122
83
  =begin rdoc
123
- Make a random array of +n+ Card objects (selected without replacement).
84
+ Permute the order of items in x. Does not copy x -- shuffles the
85
+ items in place. Works for strings, arrays, any container that responds to
86
+ length, [], and []=
124
87
  =end
125
88
 
126
- def random_deal(n)
127
- h = Hash.new
128
- while h.size < n
129
- h[ rand(52) ] = 1
130
- end
131
- a = Array.new
132
- h.keys.each do |x|
133
- a << Card.new(x)
89
+ # :begin :permute
90
+ def permute(x)
91
+ for i in 0..x.length-2
92
+ r = random(i, x.length-1)
93
+ x[i], x[r] = x[r], x[i]
94
+ end
95
+ return x
134
96
  end
135
- return a
136
- end
97
+ # :end :permute
137
98
 
138
99
  =begin rdoc
139
- Make a hand using numbers in array +a+ (used for testing)
100
+ A "helper method" for permute, that makes it easier to see which two
101
+ locations are being swapped. A call to random(i,j) returns a random
102
+ integer in the range i..j. See also PRNG::random
140
103
  =end
141
104
 
142
- def deal(a)
143
- a.map { |x| Card.new(x) }
144
- end
105
+ def random(min, max)
106
+ return nil if max < min
107
+ range = max - min + 1
108
+ return rand(range) + min
109
+ end
145
110
 
146
111
  =begin rdoc
147
- Return a random shuffling of all 52 cards
112
+ A "helper method" that can be called via a probe, to print the contents
113
+ of an array during the execution of the permute method
148
114
  =end
149
115
 
150
- def shuffle
151
- permutation((0..51).to_a).map { |x| Card.new(x) }
152
- end
116
+ def brackets(a, i, r)
117
+ res = "#{r}: "
118
+ if i <= 0
119
+ res += ("[" + a.join(" ") + "]")
120
+ elsif i >= a.length
121
+ res += (" " + a.join(" ") + " [ ]")
122
+ else
123
+ pre = a.slice(0..(i-1))
124
+ post = a.slice(i..-1)
125
+ res += (" " + pre.join(" ") + " [" + post.join(" ") + "]")
126
+ end
127
+ return res
128
+ end
129
+
153
130
 
154
131
  =begin rdoc
155
132
  Classify a poker hand -- returns a symbol describing a hand. Return values:
@@ -163,32 +140,40 @@ end
163
140
  :straight_flush
164
141
  =end
165
142
 
166
- # TODO: royal flush in RAND test?
143
+ def poker_rank(a)
144
+ rcount = Array.new(Card::Ranks.length, 0)
145
+ scount = Array.new(Card::Suits.length, 0)
146
+ a.each do |x|
147
+ rcount[ Card::Ranks.index(x.rank) ] += 1
148
+ scount[ Card::Suits.index(x.suit) ] += 1
149
+ end
150
+ if rcount.max == 1
151
+ straight = (rcount.rindex(1) - rcount.index(1) == 4)
152
+ flush = scount.max == 5
153
+ return :straight_flush if (straight && flush)
154
+ return :straight if straight
155
+ return :flush if flush
156
+ return :high_card
157
+ else
158
+ rcount.reject! { |x| x == 0 }
159
+ rcount.sort! { |x,y| y <=> x }
160
+ return :four_of_a_kind if rcount[0] == 4
161
+ return :full_house if (rcount[0] == 3 && rcount[1] == 2)
162
+ return :three_of_a_kind if rcount[0] == 3
163
+ return :two_pair if (rcount[0] == 2 && rcount[1] == 2)
164
+ return :pair
165
+ end
166
+ end
167
167
 
168
- def poker_hand(a)
169
- rcount = Array.new(Card::Ranks.length, 0)
170
- scount = Array.new(Card::Suits.length, 0)
171
- a.each do |x|
172
- rcount[ Card::Ranks.index(x.rank) ] += 1
173
- scount[ Card::Suits.index(x.suit) ] += 1
168
+ def poker_rankings
169
+ return [:high_card, :pair, :two_pair, :three_of_a_kind, :straight, :flush, :full_house, :four_of_a_kind, :straight_flush]
174
170
  end
175
- if rcount.max == 1
176
- straight = (rcount.rindex(1) - rcount.index(1) == 4)
177
- flush = scount.max == 5
178
- return :straight_flush if (straight && flush)
179
- return :straight if straight
180
- return :flush if flush
181
- return :nada
182
- else
183
- rcount.reject! { |x| x == 0 }
184
- rcount.sort! { |x,y| y <=> x }
185
- return :four_of_a_kind if rcount[0] == 4
186
- return :full_house if (rcount[0] == 3 && rcount[1] == 2)
187
- return :three_of_a_kind if rcount[0] == 3
188
- return :two_pair if (rcount[0] == 2 && rcount[1] == 2)
189
- return :pair
171
+
172
+ def poker_counts
173
+ h = Hash.new
174
+ poker_rankings.each { |x| h[x] = 0 }
175
+ return h
190
176
  end
191
- end
192
177
 
193
178
 
194
179
  =begin rdoc
@@ -202,93 +187,343 @@ end
202
187
  # To test the expression that assigns suits and ranks:
203
188
  # 52.times { |x| puts (x/13).to_s + " " + (x%13).to_s }
204
189
 
205
- class Card
206
- attr_accessor :rank, :suit
190
+ class Card
191
+ attr_accessor :rank, :suit
207
192
 
208
- unless defined? Suits
209
- Suits = [:spades, :hearts, :diamonds, :clubs]
210
- end
193
+ unless defined? Suits
194
+ Suits = [:spades, :hearts, :diamonds, :clubs]
195
+ end
211
196
 
212
- unless defined? Ranks
213
- Ranks = [:ace, :king, :queen, :jack, :ten, :nine, :eight, :seven, :six, :five, :four, :three, :two]
214
- end
197
+ unless defined? Ranks
198
+ Ranks = [:ace, :king, :queen, :jack, :ten, :nine, :eight, :seven, :six, :five, :four, :three, :two]
199
+ end
215
200
 
216
- def initialize(id = nil)
217
- id = rand(52) if id.nil?
218
- raise "card must be between 0 and 51" if id < 0 || id > 51
219
- @suit = Suits[id / 13]
220
- @rank = Ranks[id % 13]
221
- end
201
+ def initialize(id = nil)
202
+ id = rand(52) if id.nil?
203
+ raise "card must be between 0 and 51" if id < 0 || id > 51
204
+ @suit = Suits[id / 13]
205
+ @rank = Ranks[id % 13]
206
+ end
222
207
 
223
- def ==(x)
224
- return @suit == x.suit && @rank == x.rank
225
- end
208
+ def ==(x)
209
+ return @suit == x.suit && @rank == x.rank
210
+ end
226
211
 
227
- def <=>(x)
228
- r0 = Ranks.index(@rank); r1 = Ranks.index(x.rank)
229
- s0 = Suits.index(@suit); s1 = Suits.index(x.suit)
230
- if (res = (s0 <=> s1)) == 0
231
- return (r0 <=> r1)
232
- else
233
- return res
212
+ def <=>(x)
213
+ r0 = Ranks.index(@rank); r1 = Ranks.index(x.rank)
214
+ s0 = Suits.index(@suit); s1 = Suits.index(x.suit)
215
+ if (res = (s0 <=> s1)) == 0
216
+ return (r0 <=> r1)
217
+ else
218
+ return res
219
+ end
234
220
  end
235
- end
236
221
 
237
- @@outputform = :utf8
222
+ @@outputform = :utf8
238
223
 
239
- def inspect
240
- s = ""
241
- s << case @rank
242
- when :ace : "A"
243
- when :king : "K"
244
- when :queen : "Q"
245
- when :jack : "J"
246
- when :ten : "10"
247
- when :nine : "9"
248
- when :eight : "8"
249
- when :seven : "7"
250
- when :six : "6"
251
- when :five : "5"
252
- when :four : "4"
253
- when :three : "3"
254
- when :two : "2"
255
- end
256
- if $KCODE[0] == ?U
257
- if @@outputform == :utf8
258
- s << case @suit
259
- when :spades : "\xe2\x99\xa0"
260
- when :hearts : "\xe2\x99\xa5"
261
- when :clubs : "\xe2\x99\xa3"
262
- when :diamonds : "\xe2\x99\xa6"
224
+ def inspect
225
+ s = ""
226
+ s << case @rank
227
+ when :ace : "A"
228
+ when :king : "K"
229
+ when :queen : "Q"
230
+ when :jack : "J"
231
+ when :ten : "10"
232
+ when :nine : "9"
233
+ when :eight : "8"
234
+ when :seven : "7"
235
+ when :six : "6"
236
+ when :five : "5"
237
+ when :four : "4"
238
+ when :three : "3"
239
+ when :two : "2"
240
+ end
241
+ if $KCODE[0] == ?U
242
+ if @@outputform == :utf8
243
+ s << case @suit
244
+ when :spades : "\xe2\x99\xa0"
245
+ when :hearts : "\xe2\x99\xa5"
246
+ when :clubs : "\xe2\x99\xa3"
247
+ when :diamonds : "\xe2\x99\xa6"
248
+ end
249
+ else
250
+ s << "!irb" + @suit.to_s.chop
263
251
  end
264
252
  else
265
- s << "!irb" + @suit.to_s.chop
266
- end
267
- else
268
- s << case @suit
269
- when :spades : "S"
270
- when :hearts : "H"
271
- when :clubs : "C"
272
- when :diamonds : "D"
253
+ s << case @suit
254
+ when :spades : "S"
255
+ when :hearts : "H"
256
+ when :clubs : "C"
257
+ when :diamonds : "D"
258
+ end
273
259
  end
260
+ return s
261
+ # "#{@rank} #{@suit}"
274
262
  end
275
- return s
276
- # "#{@rank} #{@suit}"
277
- end
278
263
 
279
- alias to_s inspect
264
+ alias to_s inspect
280
265
 
281
- def Card.print_latex
282
- @@outputform = :latex
283
- end
266
+ def Card.print_latex
267
+ @@outputform = :latex
268
+ end
284
269
 
285
- def Card.print_utf8
286
- @@outputform = :utf8
287
- end
270
+ def Card.print_utf8
271
+ @@outputform = :utf8
272
+ end
288
273
 
274
+ end # class Card
275
+
276
+ =begin rdoc
277
+ Visualization helpers (private):
278
+ make_canvas create a new blank canvas (same one used for all tests)
279
+ Visualization methods called by students to test a PRNG object:
280
+ view(type,*args) initialize canvas for a visualization of the specified type
281
+ tick_mark(i) add a tick mark at location i on the number line
282
+ State vars maintained by the visualization methods:
283
+ @@drawing initially nil, set to a Draw object when canvas has been created
284
+ @@tkroot top level Tk application that manages the canvas (should never be used)
285
+ @@visual set to :line, :histogram, or :dot_plot when drawing initialized
286
+ @@endpoint last point on number line (value of n passed to init_line)
287
+ =end
288
+
289
+ def make_canvas
290
+ require 'tk'
289
291
 
290
- end
292
+ if ! defined? @@tkroot
293
+ @@tkroot = TkRoot.new { title "RandomLab" }
294
+ end
295
+
296
+ if @@drawing == nil
297
+ @@drawing = Draw.new(@tkroot)
298
+ @@threads = []
299
+ @@threads << Thread.new() do
300
+ Tk.mainloop
301
+ end
302
+ end
303
+ end
304
+
305
+ def view(*args)
306
+ kind = args.shift
307
+ if ! @@views.include?(kind)
308
+ puts "view type must be one of #{@@views.join(', ')}"
309
+ else
310
+ @@visual = kind
311
+ case kind
312
+ when :line
313
+ @@npoints = args[0]
314
+ if @@npoints.kind_of? Numeric
315
+ make_canvas if @@drawing.nil?
316
+ @@drawing.new_line(@@npoints)
317
+ else
318
+ puts "usage: view(:line, npoints)"
319
+ end
320
+ when :dotplot
321
+ @@max = args[0]
322
+ if @@max.kind_of? Numeric
323
+ make_canvas if @@drawing.nil?
324
+ @@drawing.new_dotplot(@@max)
325
+ else
326
+ puts "usage: view(:dotplot, max)"
327
+ end
328
+ when :histogram
329
+ if args[0].class == Array
330
+ @@keys = args[0]
331
+ @@nbins = @@max = @@keys.length
332
+ @@counts = Hash.new
333
+ @@keys.each { |k| @@counts[k] = 0 }
334
+ else
335
+ @@nbins, @@max = args
336
+ @@max = @@nbins if @@max.nil?
337
+ @@keys = nil
338
+ end
339
+ if [@@nbins, @@max].all? { |x| x.kind_of? Numeric }
340
+ make_canvas if @@drawing.nil?
341
+ @@drawing.new_histogram(@@nbins, @@max)
342
+ else
343
+ puts "usage: view(:histogram, nbins, max) or view(:histogram, keys)"
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ def tick_mark(i)
350
+ if @@visual != :line
351
+ puts "call 'view(:line,n)' to make a number line of size n'"
352
+ elsif i < 0 || i >= @@npoints
353
+ puts "tick_mark: 0 <= i < #{@@npoints}"
354
+ else
355
+ @@drawing.add_tick(i)
356
+ sleep(@@delay)
357
+ end
358
+ return nil
359
+ end
360
+
361
+ def update_bin(x)
362
+ if @@visual != :histogram
363
+ puts "call view(:histogram, n, max) to initialize a histogram with n bins"
364
+ else
365
+ i = @@keys ? @@keys.index(x) : x
366
+ if i.nil?
367
+ puts "unknown item: #{x}"
368
+ elsif i < 0 || i >= @@max
369
+ puts "count: 0 <= i < #{@@max}"
370
+ else
371
+ @@drawing.add_hist(i)
372
+ @@counts[x] += 1 if @@keys
373
+ sleep(@@delay)
374
+ end
375
+ end
376
+ return nil
377
+ end
378
+
379
+ def get_counts
380
+ return @@counts
381
+ end
382
+
383
+ def plot_point(x,y)
384
+ if @@visual != :dotplot
385
+ puts "call view(:dotplot, max) to initialize a dot plot"
386
+ elsif x < 0 || x >= @@max || y < 0 || y >= @@max
387
+ puts "plot_point: 0 <= x, y < #{@@max}"
388
+ else
389
+ @@drawing.add_point(x,y)
390
+ sleep(@@delay)
391
+ end
392
+ return nil
393
+ end
291
394
 
395
+
396
+ =begin rdoc
397
+ Make a window to display dot plots and histograms
398
+ =end
399
+
400
+ class Draw
401
+
402
+ attr_accessor :canvas, :ticks, :bins
403
+
404
+ @@canvasWidth = @@canvasHeight = 500
405
+ @@tickHeight = 20
406
+ @@tickWidth = 1
407
+ @@traceSize = 10
408
+ @@tickColor = 'blue'
409
+ @@histColor = '#000080'
410
+ @@dotSize = 2
411
+ @@dotColor = '#000080'
412
+
413
+ def new_line(nticks)
414
+ self.erase_objects
415
+ @title = TkcText.new(@@drawing.canvas, 100, 10, :text => "Number Line")
416
+ @endpoint = nticks
417
+ y = @@canvasHeight / 2
418
+ @line = TkcLine.new( @canvas, 0, y, @@canvasWidth, y, :width => 3, :fill => '#777777' )
419
+ return nil
420
+ end
421
+
422
+ def add_tick(i)
423
+ x = (i.to_f / @endpoint) * @@canvasWidth
424
+ y = @@canvasHeight / 2
425
+ @ticks << TkcLine.new( @canvas, x, y, x, y-@@tickHeight, :width => @@tickWidth, :fill => @@tickColor )
426
+ @ticks.last(@@traceSize).each_with_index { |t,i| t['fill'] = @palette[i] }
427
+ end
428
+
429
+ def new_histogram(nbins, max)
430
+ self.erase_objects
431
+ @title = TkcText.new(@@drawing.canvas, 100, 10, :text => "Histogram")
432
+ @nbins, @max = nbins, max
433
+ @baseline = @@canvasHeight / 2
434
+ w = @@canvasWidth / @nbins
435
+ @nbins.times do |i|
436
+ x = i * w
437
+ @bins[i] = TkcRectangle.new( @canvas, x, @baseline, x+w, @baseline-3, :outline => "#CCCCCC", :fill => @@histColor )
438
+ end
439
+ @dy = 8.0
440
+ @yt = 50
441
+ return nil
442
+ end
443
+
444
+ def add_hist(x)
445
+ i = (x.to_f / @max) * @nbins
446
+ rect = @bins[i]
447
+ x1, y1, x2, y2 = rect.coords
448
+ y1 = y1 - @dy
449
+ rect.coords = [x1, y1, x2, y2]
450
+ if y1 < @yt
451
+ @bins.each do |rect|
452
+ x1, y1, x2, y2 = rect.coords
453
+ y1 = @baseline - ((@baseline - y1) / 2)
454
+ rect.coords = [x1, y1, x2, y2]
455
+ end
456
+ @dy = @dy / 2
457
+ end
458
+ end
459
+
460
+ def new_dotplot(n)
461
+ self.erase_objects
462
+ @title = TkcText.new(@@drawing.canvas, 100, 10, :text => "Dot Plot")
463
+ @max = n.to_f
464
+ end
465
+
466
+ def add_point(i,j)
467
+ x = (i.to_f / @max) * @@canvasWidth
468
+ y = (j.to_f / @max) * @@canvasHeight
469
+ @points << TkcRectangle.new( @canvas, x, y, x+@@dotSize, y+@@dotSize, :outline => @@dotColor, :fill => @@dotColor )
470
+ # @points.last(@@traceSize).each_with_index { |p,i| p[:outline] = p['fill'] = @palette[i] }
471
+ end
472
+
473
+ def erase_objects
474
+ zap(@ticks)
475
+ zap(@bins)
476
+ zap(@points)
477
+ @title.delete if @title
478
+ @line.delete if @line
479
+ return nil
480
+ end
481
+
482
+ private
483
+
484
+ def zap(a)
485
+ a.each { |x| x.delete }
486
+ a.clear
487
+ end
488
+
489
+ # Make a range of colors starting from first and going to last in n steps.
490
+ # First and last are expected to be 3-tuples of integer RGB values. The
491
+ # result is an array that starts with first, has n-1 intermediate colors,
492
+ # and ends with last. Example:
493
+ # makePalette( [255,0,0], [0,0,0], 10)
494
+ # makes 11 colors starting with red and ending with black.
495
+
496
+ def makePalette(first, last, n)
497
+ d = Array.new(3)
498
+ 3.times { |i| d[i] = (first[i] - last[i]) / n }
499
+ a = [first]
500
+ (n-1).times do |i|
501
+ a << a.last.clone
502
+ 3.times { |j| a.last[j] -= d[j] }
503
+ end
504
+ a << last
505
+ a.map { |c| sprintf("#%02X%02X%02X",c[0],c[1],c[2]) }
506
+ end
507
+
508
+ def initialize(parent)
509
+ content = TkFrame.new(parent)
510
+ @canvas = TkCanvas.new(content, :borderwidth => 1, :width => @@canvasWidth, :height => @@canvasHeight)
511
+ @canvas.grid :column => 0, :row => 0, :columnspan => 4, :padx => 10, :pady => 10
512
+ content.pack :pady => 20
513
+ @palette = makePalette( [128,128,255], [32, 32, 255], @@traceSize-1 )
514
+ @palette << "#0000FF"
515
+ @ticks = Array.new
516
+ @bins = Array.new
517
+ @points = Array.new
518
+ end
519
+
520
+ end # class Draw
521
+
522
+ @@drawing = nil
523
+ @@views = [:line, :dotplot, :histogram]
524
+ @@delay = 0.01
525
+ @@keys = nil
526
+
292
527
  end # RandomLab
293
528
 
294
529
  end # RubyLabs