rubylabs 0.9.0 → 0.9.1

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