Module: RubyLabs::RandomLab

Defined in:
lib/randomlab.rb

Overview

RandomLab

The RandomLab module has definitions of classes and methods used in the projects for Chapter 9 of Explorations in Computing. The module has methods used in experiments with pseudorandom number generators.

Defined Under Namespace

Classes: Card, DotPlot, Histogram, NumberLine, PRNG

Constant Summary collapse

@@numberLineOptions =
{ 
  :lineThickness => 3, 
  :lineColor => '#777777', 
  :tickHeight => 20, 
  :tickWidth => 1,
  :tickColor => '#0000FF',
}
@@histogramOptions =
{
  :binColor => '#000080',
  :boxIncrement => 8.0,
  :rescaleTrigger => 50,
}
@@dotPlotOptions =
{
  :dotColor => '#000080',
  :dotRadius => 1.0,
}
@@drawing =
nil
@@delay =
0.01

Instance Method Summary collapse

Instance Method Details

#brackets(a, i, r) ⇒ Object

A helper method intended to be called via a probe to print the contents of an array during the execution of the permute! method. The arguments are the array to display, the location of a left bracket, and the item location where the item next to the bracket will be moved on the next swap.



161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/randomlab.rb', line 161

def brackets(a, i, r)
  res = "#{r}: "
  if i <= 0
    res += ("[" + a.join("  ") + "]")
  elsif i >= a.length
    res += (" " + a.join("  ") + "  [ ]")
  else
    pre = a.slice(0..(i-1))
    post = a.slice(i..-1)
    res += (" " + pre.join("  ") + " [" + post.join("  ") + "]")
  end
  return res
end

#get_countsObject

Return an array of counts for the bins in the histogram currently on the RubyLabs Canvas.



488
489
490
491
492
493
494
495
# File 'lib/randomlab.rb', line 488

def get_counts
  if @@drawing.class == Histogram
    return @@drawing.counts
  else
    puts "current drawing is not a histogram"
    return nil
  end
end

#new_deckObject

Make a new deck of cards. Returns an array of 52 card objects, arranged in order from card #0 (the ace of spades) through card #51 (the two of clubs).



137
138
139
# File 'lib/randomlab.rb', line 137

def new_deck
  (0..51).map { |i| Card.new(i) }
end

#pdup(n, d) ⇒ Object

Compute the probability of a duplicate item in a collection of n items drawn from the range [1..d].

Example – to compute the probability of drawing the same card twice when sampling with replacement 5 times from a deck of 52 cards:

>> pdup(5, 52)
=> 0.179716221420819


151
152
153
154
# File 'lib/randomlab.rb', line 151

def pdup(n, d)
  return 1.0 if n > d
  return 1.0 - (1..(n-1)).inject(1.0) { |p, k| p * (1.0 - (k / 52.0)) }
end

#plot_point(x, y) ⇒ Object

Add a point at location ‘x’, ‘y’ to the dot plot on the RubyLabs Canvas.

Example: if the canvas was initialized to show a 250 x 250 plot, this call will display a point in the center of the drawing:

>> plot_point(125,125)
=> nil


520
521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/randomlab.rb', line 520

def plot_point(x,y)
  if @@drawing.class != DotPlot
    puts "call view_dotplot to initialize a dot plot"
  elsif x < 0 || x >= @@drawing.max || y < 0 || y >= @@drawing.max
    puts "plot_point: 0 <= x, y < #{@@drawing.max}"
  else
    px = (x.to_f / @@drawing.max) * Canvas.width
    py = (y.to_f / @@drawing.max) * Canvas.height
    r = @@drawing.options[:dotRadius]
    color = @@drawing.options[:dotColor]
    Canvas::Circle.new( px, py, r, :outline => color, :fill => color ) 
  end
  return nil
end

#poker_countsObject

Initialize a Hash object with keys that are poker rank symbols and values that are all initially 0. Used in experiments that count the number of times various hands are dealt.

Example:

>> poker_counts
=> {:flush=>0, :full_house=>0, ... :high_card=>0}


224
225
226
227
228
# File 'lib/randomlab.rb', line 224

def poker_counts
  h = Hash.new
  poker_rankings.each { |x| h[x] = 0 }
  return h
end

#poker_rank(a) ⇒ Object

Given an array of 5 Card objects, determine what type of poker hand is represented by the cards. The return value is a symbol, e.g. :pair, :full_house, etc. (call poker_rankings to see the complete list of symbols).

Example (assuming d is a complete deck of 52 Card objects):

>> h = permute!(d).first(5)
=> [AH, 6S, 7C, JH, AC]
>> poker_rank(h)
=> :pair


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/randomlab.rb', line 186

def poker_rank(a)
  rcount = Array.new(Card::Ranks.length, 0)
  scount = Array.new(Card::Suits.length, 0)
  a.each do |x|
    rcount[ Card::Ranks.index(x.rank) ] += 1
    scount[ Card::Suits.index(x.suit) ] += 1
  end
  if rcount.max == 1
    straight = (rcount.rindex(1) - rcount.index(1) == 4)
    flush = scount.max == 5
    return :straight_flush if (straight && flush) 
    return :straight if straight
    return :flush if flush
    return :high_card
  else
    rcount.reject! { |x| x == 0 }
    rcount.sort! { |x,y| y <=> x }
    return :four_of_a_kind if rcount[0] == 4
    return :full_house if (rcount[0] == 3 && rcount[1] == 2)
    return :three_of_a_kind if rcount[0] == 3
    return :two_pair if (rcount[0] == 2 && rcount[1] == 2)
    return :pair
  end
end

#poker_rankingsObject

Return a list of symbols used to classify poker hands.



213
214
215
# File 'lib/randomlab.rb', line 213

def poker_rankings
  return [:high_card, :pair, :two_pair, :three_of_a_kind, :straight, :flush, :full_house, :four_of_a_kind, :straight_flush]
end

#prng_sequence(a, c, m) ⇒ Object

Return an array of m numbers defined by the pseudorandom sequence with parameters a, c, and m. The first number in the sequence is 0, and the remaining numbers are defined by the recurrence

x[i+1] = (a * x[i] + c) % m

– :begin :prng_sequence



125
126
127
128
129
130
131
# File 'lib/randomlab.rb', line 125

def prng_sequence(a, c, m)
  seq = [0]
  (m-1).times do
    seq << (a * seq.last + c) % m
  end
  return seq
end

#tick_mark(i) ⇒ Object

Draw a tick mark on the RubyLabs Canvas, provided the canvas has been initialized with a call to view_numberline.



368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/randomlab.rb', line 368

def tick_mark(i)
  if @@drawing.class != NumberLine
    puts "call view_numberline to initialize the number line"
  elsif i < 0 || i >= @@drawing.npoints
    puts "tick_mark: 0 <= i < #{@@drawing.npoints}"
  else
    x0, y0, x1, y1 = @@drawing.line.coords
    tx = (i.to_f / @@drawing.npoints) * (x1-x0)
    ty = y0 - @@drawing.options[:tickHeight]
    Canvas::Line.new(tx, y0, tx, ty, :width => @@drawing.options[:tickWidth], :fill => @@drawing.options[:tickColor])
    sleep(@@delay)
  end
  return true
end

#update_bin(x) ⇒ Object

Update the bin for data item x, presuming the RubyLabs Canvas has been initialized to show a histogram for data of this type. The bin for x increases in height. If it reaches the maximum height, rescale all the bins and increase the maximum height.



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/randomlab.rb', line 448

def update_bin(x)
  if @@drawing.class != Histogram
    puts "call view_histogram to initialize a histogram"
    return nil
  end
  if @@drawing.keys
    i = @@drawing.keys.index(x)
    if i.nil?
      puts "unknown bin: #{x}"
      return nil
    end
  else
    xmax = @@drawing.max
    nb = @@drawing.bins.length
    if x < 0 || x >= xmax
      puts "x must be between 0 and #{xmax-1}"
      return nil
    end
    i = ((x / xmax) * nb).to_i
  end
  @@drawing.counts[i] += 1
  rect = @@drawing.bins[i]
  x1, y1, x2, y2 = rect.coords
  y1 = y1 - @@drawing.options[:boxIncrement]
  rect.coords = [x1, y1, x2, y2]
  if y1 < @@drawing.options[:rescaleTrigger]
    base = @@drawing.base
    @@drawing.bins.each do |rect|
      x1, y1, x2, y2 = rect.coords
      y1 =  base - ((base - y1) / 2)
      rect.coords = [x1, y1, x2, y2]
    end
    @@drawing.options[:boxIncrement] /= 2
  end
  sleep(@@delay)
  return true
end

#view_dotplot(npoints, userOptions = {}) ⇒ Object

Initialize the RubyLabs Canvas to show a dot plot with npoints in both the x and y dimension. Drawing options and their defaults are

:dotColor => '#000080'
:dotRadius => 1.0

Example: intialize the drawing for a 250 x 250 dot plot with green dots:

>> view_dotplot(250, :dotColor => 'darkgreen')
=> true


506
507
508
509
510
511
# File 'lib/randomlab.rb', line 506

def view_dotplot(npoints, userOptions = {})
  Canvas.init(500, 500, "RandomLab::DotPlot")
  options = @@dotPlotOptions.merge(userOptions)
  @@drawing = DotPlot.new(npoints, options)
  return true
end

#view_histogram(*args) ⇒ Object

Initialize the RubyLabs Canvas to show a histogram with the specified bins. In the initial drawing each bin is represented by a rectangle one pixel tall, i.e. a horizontal line.

As items are added to a bin the rectangle will grow in height. If any rectangle reaches the maximum height, all the rectangles are rescaled so the bins can continue to grow.

The argument to view_histogram can either be an integer, which specifies the number of bins, or an array of symbols, in which case there will be one bin for each symbol. If the argument is an integer, a second argument can specify a maximum data value; for example, calling view_histogram(10,100) will make a histogram with 10 bins for numbers between 0 and 99, so that data values 0 through 9 will go in the first bin, 10 through 19 in the second bin, and so on.

Display options and their default values are:

:binColor => '#000080'
:boxIncrement => 8.0
:rescaleTrigger => 50

Example: make a histogram for the numbers between 0 and 5:

>> view_histogram(6)
=> true

Example: make a histogram to count the number of times each type of poker hand is seen in an experiment:

>> view_histogram(poker_rankings, :binColor => 'darkgreen')
=> true


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
436
437
438
439
440
441
442
# File 'lib/randomlab.rb', line 407

def view_histogram(*args)
  begin
    if args[0].class == Array
      userOptions = args.length > 1 ? args[1] : { }
      raise "usage:  view_histogram(keys, options)" unless userOptions.class == Hash
      keys = args[0]
      nbins = max = keys.length
    else
      userOptions = args.length > 2 ? args[2] : { }
      raise "usage:  view_histogram(nbins, max, options)" unless userOptions.class == Hash
      nbins = args[0]
      max = args.length > 1 ? args[1] : nbins
      keys = nil
    end
  rescue Exception => e
    puts e
    return false
  end
  
  Canvas.init(500, 300, "RandomLab::Histogram")
  counts = Hash.new(0)
  options = @@histogramOptions.merge(userOptions)
  bins = []
  binHeight = 3
  binBorder = 2
  binWidth = (500/(nbins+1))
  binTop = 280
  nbins.times do |i| 
    x = i * binWidth + binWidth/2
    bins << Canvas::Rectangle.new( x + binBorder, binTop, x + binWidth - binBorder, binTop + binHeight, :outline => options[:binColor], :fill => options[:binColor] ) 
  end

  @@drawing = Histogram.new(bins, max.to_f, keys, counts, binTop, options)

  return true  
end

#view_numberline(npoints, userOptions = {}) ⇒ Object

Initialize the RubyLabs Canvas with a drawing of a number line for integers from 0 to npoints-1. Options that control the appearance of the display, and their default values, are:

:lineThickness => 3
:lineColor => '#777777'
:tickHeight => 20
:tickWidth => 1
:tickColor => '#0000FF'

Example:

>> view_numberline(500, :lineColor => 'blue', :lineThickness => 1)
=> true


357
358
359
360
361
362
363
# File 'lib/randomlab.rb', line 357

def view_numberline(npoints, userOptions = {})
  Canvas.init(500, 100, "RandomLab::NumberLine")
  options = @@numberLineOptions.merge(userOptions)
  line = Canvas::Line.new(0, 70, 500, 70, :width => options[:lineThickness], :fill => options[:lineColor])
  @@drawing = NumberLine.new(line, npoints, options)
  return true
end