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/lib/hashlab.rb CHANGED
@@ -1,92 +1,155 @@
1
+ module RubyLabs
2
+
1
3
  =begin rdoc
2
4
 
3
5
  == HashLab
4
6
 
5
- Methods used in experiments on hash tables.
6
-
7
- <b>NOTE:</b> the +insert+ and +lookup+ methods defined here are "stubs" used to test the hash
8
- functions. Load the file <tt>hashmethods.rb</tt> to overwrite the stubs with the actual
9
- methods that create and search buckets.
7
+ The HashLab module has definitions of classes and methods used in the projects for Chapter 6
8
+ of <em>Explorations in Computing</em>. The module has methods used to compute hash functions,
9
+ simple stand-alone methods named +insert+ and +lookup+ to demonsstrate how hash functions work,
10
+ and a HashTable class for larger experiments with hash tables.
10
11
 
11
12
  =end
12
13
 
13
- module RubyLabs
14
14
 
15
15
  module HashLab
16
16
 
17
+ # When a hash table is drawn on the screen the +drawing+ attribute is set to a
18
+ # TableView struct that describes the drawing.
19
+
17
20
  TableView = Struct.new(:cells, :buckets, :nrows, :options)
18
21
 
19
- =begin rdoc
20
-
21
- Compute the sum of the letters in a string, where each letter is weighted
22
- according to its position. Requires the +ord+ method added to Fixnum in the
23
- RubyLabs module.
24
-
25
- =end
22
+ # Default options for drawing a hash table on the canvas
23
+
24
+ @@tableOptions = {
25
+ :tableX => 20,
26
+ :tableY => 20,
27
+ :cellHeight => 15,
28
+ :cellYSpace => 2,
29
+ :cellWidth => 30,
30
+ :cellColor => :lightgray,
31
+ :textX => 70,
32
+ :maxRows => 26,
33
+ }
26
34
 
27
- # :begin :radix26
35
+ # Compute the wighted sum of the ordinal values of characters in a string, where the
36
+ # weight for a character is defined by its position in the string: the weight for the
37
+ # character +i+ positions from the right is 26**+i+. The numeric value of a character
38
+ # is determined by Fixnum#ord, an extension to the Fixnum class, which is defined in
39
+ # rubylabs.rb. Upper and lower case letters are mapped to integer from 0 to 25, while
40
+ # other characters are unchanged.
41
+ #
42
+ # Examples:
43
+ # >> radix26("be")
44
+ # => 30
45
+ # >> radix26("bee")
46
+ # => 784
47
+ # >> radix26("beetle")
48
+ # => 13792718
49
+ # >> radix26("beethoven")
50
+ # => 242419173737
51
+ #
52
+ #--
53
+ # :begin :radix26
28
54
  def radix26(s)
29
- x = 0
30
- s.each_byte do |b|
31
- x = x * 26 + b.ord
32
- end
33
- return x
55
+ x = 0
56
+ s.each_byte do |b|
57
+ x = x * 26 + b.ord
58
+ end
59
+ return x
34
60
  end
35
- # :end :radix26
36
-
37
- =begin rdoc
38
- Trival hash function that uses only the first letter in the input string. +s+ is the
39
- string to hash, +n+ is the size of the hash table.
40
- =end
41
-
42
- # :begin :h0
61
+ # :end :radix26
62
+
63
+ # Trival hash function that uses only the first letter in the input string. +s+ is the
64
+ # string to hash, +n+ is the size of the hash table.
65
+ #
66
+ # Example:
67
+ # >> ?b.ord
68
+ # => 1
69
+ # >> h0("beer", 10)
70
+ # => 1
71
+ #
72
+ #--
73
+ # :begin :h0
43
74
  def h0(s, n)
44
- return s[0].ord % n
75
+ return s[0].ord % n
45
76
  end
46
- # :end :h0
47
-
48
- =begin rdoc
49
- Slightly better hash function that uses the first two letters in the input string. +s+ is the
50
- string to hash, +n+ is the size of the hash table.
51
- =end
52
-
53
- # :begin :h1
77
+ # :end :h0
78
+
79
+ # A hash function that uses the first two letters in the input string, weighing the
80
+ # first letter by 26**1 = 26 and the second by 26**0 = 1. +s+ is the
81
+ # string to hash, +n+ is the size of the hash table.
82
+ #
83
+ # Example:
84
+ # >> ?b.ord * 26 + ?e.ord
85
+ # => 30
86
+ # >> h1("beer", 10)
87
+ # => 0
88
+ #
89
+ #--
90
+ # :begin :h1
54
91
  def h1(s, n)
55
- return ((s[0].ord * 26) + s[1].ord) % n
92
+ return ((s[0].ord * 26) + s[1].ord) % n
56
93
  end
57
- # :end :h1
58
-
59
- =begin rdoc
60
- Hash function based on the radix-26 representation of the input string. +s+ is the
61
- string to hash, +n+ is the size of the hash table.
62
- =end
63
-
64
- # :begin :hr
94
+ # :end :h1
95
+
96
+ # A hash function based on the radix-26 representation of the full input string. +s+ is the
97
+ # string to hash, +n+ is the size of the hash table.
98
+ #
99
+ # Example:
100
+ # >> radix26("beer")
101
+ # => 20401
102
+ # >> hr("beer", 10)
103
+ # => 1
104
+ #
105
+ #--
106
+ # :begin :hr
65
107
  def hr(s, n)
66
- return radix26(s) % n
108
+ return radix26(s) % n
67
109
  end
68
- # :end :hr
69
-
70
- =begin rdoc
71
- Simple hash function used in the first project, to introduce the idea of hashing.
72
- Uses a default table size of 10 and the h0 method (which just looks at the first
73
- letter in a string).
74
- =end
75
-
76
- # :begin :h
77
- def h(s, n = 10)
78
- return s[0].ord % n
79
- end
80
- # :end :h
81
-
82
- =begin rdoc
83
- Method used in the first project: if +s+ can be added to +t+ (i.e. if no
84
- collision) return the location, otherwise return +nil+
85
- =end
86
-
87
- # :begin :insert
110
+ # :end :hr
111
+
112
+ # Simple hash function used in the first project, to introduce the idea of hashing.
113
+ # Uses the first letter in tring +s+ to find where it would go in a table of
114
+ # size +n+ (which has a default value of 10).
115
+ #
116
+ # Example:
117
+ # >> ?z.ord
118
+ # => 25
119
+ # >> h("zymurgy")
120
+ # => 5
121
+ # >> h("zymurgy", 15)
122
+ # => 10
123
+ #
124
+ #--
125
+ # :begin :h
126
+ def h(s, n = 10)
127
+ return s[0].ord % n
128
+ end
129
+ # :end :h
130
+
131
+ # Insert string +s+ into array +t+. The location for +s+
132
+ # is determined by the hash function implemented in method +h+.
133
+ # If +s+ can be added to +t+ (i.e. if there is no
134
+ # collision) return the location where +s+ is stored, otherwise return +nil+.
135
+ #
136
+ # This method is intended to be used to demonstrate how hash fucnctions
137
+ # work; for a more complete implementation see HashTable#insert.
138
+ #
139
+ # Example:
140
+ # >> t = Array.new(10)
141
+ # => [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
142
+ # >> insert("delta", t)
143
+ # => 3
144
+ # >> t
145
+ # => [nil, nil, nil, "delta", nil, nil, nil, nil, nil, nil]
146
+ # >> insert("derp", t)
147
+ # => nil
148
+ #
149
+ #--
150
+ # :begin :insert
88
151
  def insert(s, t)
89
- i = h(s, t.length)
152
+ i = h(s, t.length)
90
153
  if t[i].nil?
91
154
  t[i] = s
92
155
  return i
@@ -94,66 +157,173 @@ end
94
157
  return nil
95
158
  end
96
159
  end
97
- # :end :insert
98
-
99
- =begin rdoc
100
- Method used in the first project: if +s+ is in array +t+ return the location,
101
- otherwise return +nil+
102
- =end
103
-
104
- # :begin :lookup
160
+ # :end :insert
161
+
162
+ # Look for string +s+ in array +t+, returning the location where +s+ is stored
163
+ # if it is in the array, otherwise +nil+. If +s+ is in +t+ it will be in the
164
+ # row computed by the hash function implmeneted in method +h+.
165
+ #
166
+ # This method is for demonstrations of simple hash tables; for experiments
167
+ # use tHashTable#insert and #HashTable#lookup.
168
+ #
169
+ # Example:
170
+ # >> t
171
+ # => [nil, nil, nil, "delta", nil, nil, nil, nil, nil, nil]
172
+ # >> lookup("delta", t)
173
+ # => 3
174
+ # >> lookup("epsilon", t)
175
+ # => nil
176
+ #
177
+ #--
178
+ # :begin :lookup
105
179
  def lookup(s, t)
106
- i = h(s, t.length)
107
- if t[i] == s
108
- return i
109
- else
110
- return nil
180
+ i = h(s, t.length)
181
+ if t[i] == s
182
+ return i
183
+ else
184
+ return nil
111
185
  end
112
186
  end
113
- # :end :lookup
187
+ # :end :lookup
188
+
189
+ # Print a nicely formatted representation of table +t. The argument +t+ can be
190
+ # an array or a HashTable object. To make the table structure easier to see only
191
+ # non-empty rows are printed, one row per line. The row number is printed at the
192
+ # front of the line. The optional parameter +max+
193
+ # is the number of rows to print.
194
+ #
195
+ # Example:
196
+ # >> t
197
+ # => ["upsilon", nil, nil, "delta", "omega", nil, nil, nil, nil, nil]
198
+ # >> print_table(t)
199
+ # 0: "upsilon"
200
+ # 3: "delta"
201
+ # 4: "omega"
202
+ # => nil
203
+ #
204
+ def print_table(t, max = nil)
205
+ t = t.table if t.class == HashTable
206
+ max = t.length unless max
207
+ max.times { |i| print_row(i, t[i] ) unless t[i].nil? }
208
+ return nil
209
+ end
114
210
 
115
- =begin rdoc
211
+ # Helper method called by <tt>print_table</tt>. Print a row number followed
212
+ # by the contents of the row.
213
+
214
+ def print_row(n, row)
215
+ printf "%4d: %s\n", n, row.inspect
216
+ end
217
+
218
+ # Initialize the RubyLabs Canvas and draw a picture of hash table +t+.
219
+ # Subsequent calls to this table's +insert+ method will update the drawing
220
+ # to show where the new item is placed.
221
+ #
222
+ # Table drawing parameters can be passed as optional arguments. The defaults are:
223
+ # :tableX => 20
224
+ # :tableY => 20
225
+ # :cellHeight => 15
226
+ # :cellYSpace => 2
227
+ # :cellWidth => 30
228
+ # :cellColor => :lightgray
229
+ # :textX => 70
230
+ # :maxRows => 26
231
+ #
232
+ # Example: to make a drawing of a table +t+, showing only the first 10 rows and using
233
+ # light blue to show empty rows:
234
+ # >> view_table(t, :cellColor => :lightblue, :maxRows => 10)
235
+ # => true
236
+
237
+ def view_table(t, userOptions = {} )
238
+ options = @@tableOptions.merge(userOptions)
239
+
240
+ height = 2 * options[:tableX] + options[:maxRows] * (options[:cellHeight] + options[:cellYSpace] )
241
+ Canvas.init(400, height, "HashLab")
242
+
243
+ Canvas::Font.new('bucketfont', :family => 'Helvetica', :size => 11)
244
+ tbl = t.table
245
+ x0 = options[:tableX]
246
+ x1 = x0 + options[:cellWidth]
247
+ cells = []
248
+ buckets = []
249
+ nrows = min(tbl.size, options[:maxRows])
250
+
251
+ nrows.times do |i|
252
+ y0 = options[:tableY] + i * (options[:cellHeight] + options[:cellYSpace])
253
+ y1 = y0 + options[:cellHeight]
254
+ cells << Canvas::Rectangle.new(x0, y0, x1, y1)
255
+ if tbl[i]
256
+ buckets << Canvas::Text.new(tbl[i].join(", "), options[:textX], y0, {:font => :bucketfont})
257
+ cells[i].fill = 'white'
258
+ else
259
+ cells[i].fill = options[:cellColor]
260
+ end
261
+ end
262
+
263
+ t.drawing = TableView.new(cells, buckets, nrows, options)
264
+ return true
265
+
266
+ end
116
267
 
117
- Make a hash table with +n+ buckets. The optional parameter is a symbol specifying
118
- an alternative hash function (<tt>:h0</tt> or <tt>:h1</tt>); the default is the method +hr+, which
119
- uses the radix26 function.
268
+ =begin rdoc
120
269
 
121
- The object returned by this method is an array of the designated size. A bit of
122
- Ruby magic defines three new methods just for the new object:
123
- [<tt>h=(f)</tt>] tells the table to use method f for the hash function (:h0, :h1, or nil)
124
- [<tt>h</tt>] returns the id of the hash function currently being used
125
- [<tt>hash(s)</tt>] call the current hash function on string s
270
+ == HashTable
271
+
272
+ A HashTable object is an array of strings. When an object is created, it is
273
+ given a specified number of rows, and each row is initially empty. The class has methods
274
+ to add strings to the table, look to see if a string is in the table, and
275
+ various methods for displaying information about the table.
276
+
277
+ Example:
278
+ >> t = HashTable.new(10)
279
+ => #<RubyLabs::HashLab::HashTable: 10 rows, :hr>
280
+ >> TestArray.new(5, :cars).each { |x| t.insert(x) }
281
+ => ["oldsmobile", "maserati", "porsche", "lotus", "saturn"]
282
+ >> puts t
283
+ 2: ["porsche", "lotus"]
284
+ 6: ["oldsmobile"]
285
+ 7: ["saturn"]
286
+ 8: ["maserati"]
287
+ => nil
288
+ >> t.lookup("lotus")
289
+ => 2
290
+ >> t.lookup("lexus")
291
+ => nil
126
292
 
127
293
  =end
128
294
 
129
295
  class HashTable
130
-
296
+
131
297
  @@hash_functions = [:h0, :h1, :hr]
132
-
298
+
133
299
  attr_reader :table
134
300
  attr_accessor :drawing
135
301
 
302
+ # Make a hash table with +n+ rows. Each row is a bucket that will expand to
303
+ # hold new items that hash to that row.
304
+ # By default the hash function is the one implemented by the method
305
+ # +hr+. The optional parameter is a symbol specifying
306
+ # an alternative hash function, either <tt>:h0</tt> or <tt>:h1</tt>.
307
+
136
308
  def initialize(n, f = :hr)
137
309
  raise "HashTable: hash function must be one of #{@@hash_functions.inspect}" unless @@hash_functions.include?(f)
138
310
  @table = Array.new(n)
139
311
  @hash = f
140
312
  end
141
313
 
142
- # def hash(s)
143
- # return case @h
144
- # when :h0 : h0(s, @table.length)
145
- # when :h1 : h1(s, @table.length)
146
- # else hr(s, @table.length)
147
- # end
148
- # end
149
-
314
+ # Look up string +s+ in the table. Return the row number where +s+ is found, or
315
+ # +nil+ if +s+ is not in the table.
316
+
150
317
  def lookup(s)
151
- i = send(@hash, s, @table.length)
152
- return ( @table[i] && @table[i].include?(s) ) ? i : nil
318
+ i = send(@hash, s, @table.length)
319
+ return ( @table[i] && @table[i].include?(s) ) ? i : nil
153
320
  end
154
321
 
322
+ # Add string +s+ to the table. Collisions are resolved by appending +s+ to the
323
+ # end of the bucket in the row for +s+.
324
+
155
325
  def insert(s)
156
- i = send(@hash, s, @table.length)
326
+ i = send(@hash, s, @table.length)
157
327
  @table[i] = Array.new if @table[i].nil?
158
328
  @table[i] << s
159
329
  if @drawing
@@ -169,26 +339,29 @@ Ruby magic defines three new methods just for the new object:
169
339
  return i
170
340
  end
171
341
 
342
+ # Call HashLab#print_table to display the contents of the table.
343
+
172
344
  def to_s
173
345
  print_table(self)
174
346
  end
175
347
 
348
+ # Return a string that contains essential information about a table
349
+ # (number of rows and the hash function used to insert or look up strings).
350
+
176
351
  def inspect
177
352
  sprintf '#<RubyLabs::HashLab::HashTable: %d rows, :%s>', @table.size, @hash.to_s
178
353
  end
179
-
180
- =begin rdoc
181
- Print usage statistics for the table. Prints lengths of longest and shortest
182
- buckets, number of empty buckets, and mean bucket length.
183
- =end
184
-
354
+
355
+ # Print usage statistics for the table: the lengths of longest and shortest
356
+ # buckets, number of empty buckets, and mean bucket length.
357
+
185
358
  def print_stats
186
359
  max = 0
187
360
  min = Float::MAX
188
361
  nzero = 0
189
362
  sum = 0
190
363
  @table.each do |bucket|
191
- n = bucket.nil? ? 0 : bucket.length
364
+ n = bucket.nil? ? 0 : bucket.length
192
365
  min = n if n < min
193
366
  max = n if n > max
194
367
  sum += n
@@ -200,96 +373,20 @@ Ruby magic defines three new methods just for the new object:
200
373
  if max > 0
201
374
  printf "mean bucket length: %.2f\n", sum.to_f / (@table.length - nzero)
202
375
  end
203
- return nil
376
+ return nil
204
377
  end
205
378
 
206
- =begin rdoc
207
- Return a list of indices for buckets that have more than +cutoff+ entries.
208
- =end
379
+ # Return a list of indices for buckets that have more than +cutoff+ entries.
209
380
 
210
381
  def long_rows(cutoff = 5)
211
382
  rows = Array.new
212
- @table.each_with_index do |row, i|
213
- rows << i if !row.nil? && row.length > cutoff
214
- end
215
- return rows
216
- end
217
-
218
- end # HashTable
219
-
220
- =begin rdoc
221
- Print a nicely formatted representation of hash table. The parameter +t+ can be
222
- an array or a HashTable object. The optional parameter
223
- is the number of rows to print, e.g. to print just the first 10 rows of a large table
224
- +t+ call <tt>print_table(t,10)</tt>. Skips rows that have empty buckets.
225
- =end
226
-
227
- def print_table(t, max = nil)
228
- t = t.table if t.class == HashTable
229
- max = t.length unless max
230
- max.times { |i| print_row(i, t[i] ) unless t[i].nil? }
231
- return nil
232
- end
233
-
234
- def print_row(n, row) # :nodoc:
235
- printf "%4d: %s\n", n, row.inspect
236
- end
237
-
238
- =begin rdoc
239
- Verification of numbers shown in the table for the birthday paradox. Call
240
- birthday(n,m) to make a table with n rows and fill it with m random words.
241
- Return true if any row has more than one item.
242
- =end
243
-
244
- def birthday(n, m)
245
- t = HashTable.new(n)
246
- TestArray.new(m, :words).each { |s| t.insert(s) }
247
- # puts t
248
- t.table.each { |row| return true if row && row.length > 1 }
249
- return false
250
- end
251
-
252
- =begin rdoc
253
- Initialize the canvas with a drawing of an hash table +t+
254
- =end
255
-
256
- def view_table(t, userOptions = {} )
257
- options = @@tableOptions.merge(userOptions)
258
- Canvas.init(400, 500, "HashLab")
259
- Canvas::Font.new('bucketfont', :family => 'Helvetica', :size => 11)
260
- tbl = t.table
261
- x0 = options[:tableX]
262
- x1 = x0 + options[:cellWidth]
263
- cells = []
264
- buckets = []
265
- nrows = min(tbl.size, options[:maxRows])
266
- nrows.times do |i|
267
- y0 = options[:tableY] + i * (options[:cellHeight] + options[:cellYSpace])
268
- y1 = y0 + options[:cellHeight]
269
- cells << Canvas::Rectangle.new(x0, y0, x1, y1)
270
- if tbl[i]
271
- buckets << Canvas::Text.new(tbl[i].join(", "), options[:textX], y0, {:font => :bucketfont})
272
- cells[i].fill = 'white'
273
- else
274
- cells[i].fill = options[:cellColor]
383
+ @table.each_with_index do |row, i|
384
+ rows << i if !row.nil? && row.length > cutoff
275
385
  end
386
+ return rows
276
387
  end
277
- t.drawing = TableView.new(cells, buckets, nrows, options)
278
- return true
279
- end
280
-
281
- @@tableOptions = {
282
- :tableX => 20,
283
- :tableY => 20,
284
- :cellHeight => 15,
285
- :cellYSpace => 2,
286
- :cellWidth => 30,
287
- :cellColor => :lightgray,
288
- :textX => 70,
289
- :maxRows => 25,
290
- }
291
-
292
388
 
389
+ end # HashTable
293
390
 
294
391
  end # HashLab
295
392
 
data/lib/introlab.rb CHANGED
@@ -1,52 +1,47 @@
1
- =begin rdoc
1
+ module RubyLabs
2
2
 
3
- Methods used in Chapter 2 (the Ruby Workbench)
3
+ =begin rdoc
4
4
 
5
- =end
5
+ == IntroLab
6
6
 
7
- module RubyLabs
8
-
9
- module IntroLab
7
+ The IntroLab module has Ruby code described in from Chapter 2 of <em>Explorations in Computing</em>.
8
+ These simple examples illustrate how methods are defined in Ruby.
10
9
 
11
- =begin rdoc
12
- Compute the area of a 5-sided counter that is a square with a missing
13
- triangle shape. The parameter +x+ is the length of one side of the square.
14
10
  =end
15
11
 
16
- # :begin :countertop
12
+ module IntroLab
13
+
14
+ # Compute the area of a 5-sided counter that is a square with a missing
15
+ # triangle shape. The parameter +x+ is the length of one side of the square.
16
+ #
17
+ #--
18
+ # :begin :countertop
17
19
  def countertop(x)
18
20
  square = x**2
19
21
  triangle = ((x/2)**2) / 2
20
22
  return square - triangle
21
23
  end
22
- # :end :countertop
23
-
24
- =begin rdoc
25
- Convert the temperature +f+ (in degrees Fahrenheit) into the equivalent
26
- temperature in degrees Celsius.
27
- =end
28
-
29
- def celsius(f)
30
- (f - 32) * 5 / 9
31
- end
32
-
33
- # :begin :fahrenheit
34
- def fahrenheit(c)
35
- # your expression goes here
36
- end
37
- # :end :fahrenheit
38
-
39
- # :begin :payment
40
- def payment(c)
41
- # your expression goes here
42
- end
43
- # :end :payment
44
-
45
- # :begin :payment
46
- def era(runs, ip)
47
- return (9.0 / ip) * runs
48
- end
49
- # :end :payment
24
+ # :end :countertop
25
+
26
+ # Convert the temperature +f+ (in degrees Fahrenheit) into the equivalent
27
+ # temperature in degrees Celsius.
28
+ #
29
+ #--
30
+ # :begin :celsius
31
+ def celsius(f)
32
+ (f - 32) * 5 / 9
33
+ end
34
+ # :end :celsius
35
+
36
+ # Stub for a method to convert temperature +c+ (in degrees Celsius) into the equivalent temperature
37
+ # in degrees Fahrenheit
38
+ #
39
+ #--
40
+ # :begin :fahrenheit
41
+ def fahrenheit(c)
42
+ # your expression goes here
43
+ end
44
+ # :end :fahrenheit
50
45
 
51
46
  end # IntroLab
52
47