rubylabs 0.5.5 → 0.6.2

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