rubylabs 0.7.5 → 0.8.0

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/demos.rb ADDED
@@ -0,0 +1,141 @@
1
+ =begin rdoc
2
+
3
+ Methods in this module are not intended to be used by students. They are
4
+ implemented here so they can be tested before being included in the book
5
+ or lecture slides. Instructors can also load this module to use the methods
6
+ in lectures.
7
+
8
+ =end
9
+
10
+ module RubyLabs
11
+
12
+ module Demos
13
+
14
+ =begin rdoc
15
+ Convert the temperature +f+ (in degrees Fahrenheit) into the equivalent
16
+ temperature in degrees Celsius.
17
+ =end
18
+
19
+ def celsius(f)
20
+ (f - 32) * 5 / 9
21
+ end
22
+
23
+ =begin rdoc
24
+ This version of the Sieve of Eratosthenes iterates until the worklist is
25
+ empty. Use it as a baseline for counting the number of iterations, to
26
+ compare it to the actual version that iterates until finding the first
27
+ prime greater than sqrt(n)
28
+ =end
29
+
30
+ def sieve(n)
31
+ worklist = Array(2..n)
32
+ primes = []
33
+
34
+ while worklist.length > 0
35
+ primes << worklist.first
36
+ worklist.delete_if { |x| x % primes.last == 0 }
37
+ end
38
+
39
+ return primes
40
+ end
41
+
42
+ def sequence(i,j)
43
+ Array(i..j)
44
+ end
45
+
46
+ =begin rdoc
47
+ The HashLab module has preliminary versions of insert and lookup methods
48
+ that don't use buckets; these are the final versions, with buckets.
49
+ =end
50
+
51
+ def insert(s, t)
52
+ i = h(s, t.length)
53
+ t[i] = Array.new if t[i].nil?
54
+ t[i] << s
55
+ return i
56
+ end
57
+
58
+ def lookup(s, t)
59
+ i = h(s, t.length)
60
+ if t[i] && t[i].include?(s)
61
+ return i
62
+ else
63
+ return nil
64
+ end
65
+ end
66
+
67
+ =begin rdoc
68
+ This is the bare bones version of Eliza.transform, the method that
69
+ applies transformation rule to make a response to a sentence.
70
+ =end
71
+
72
+ def transform(sentence)
73
+ queue = PriorityQueue.new
74
+ Eliza.scan(sentence, queue)
75
+ response = nil
76
+ while queue.length > 0
77
+ rule = queue.shift
78
+ response = Eliza.apply(sentence, rule)
79
+ break if response != nil
80
+ end
81
+ return response
82
+ end
83
+
84
+ =begin rdoc
85
+ Compare two ways of writing a loop
86
+ =end
87
+
88
+ def loops(n)
89
+ i = 1
90
+ while i <= n
91
+ puts i ** 2
92
+ i += 1
93
+ end
94
+
95
+ for i in 1..n
96
+ puts i**2
97
+ end
98
+ end
99
+
100
+ =begin rdoc
101
+ Simple demonstration of why nested loops lead to n^2 operations. This
102
+ version is for a loop structure like the one in isort, but it also works
103
+ for any algorithm that needs all (i,j) pairs and that is symmetric, i.e.
104
+ (i,j) = (j,i).
105
+ =end
106
+
107
+ # :begin :nested
108
+ def nested(n)
109
+ count = 0
110
+ i = 0
111
+ while i < n
112
+ j = 0
113
+ while j < i
114
+ count += 1
115
+ j += 1
116
+ end
117
+ i += 1
118
+ end
119
+ return count
120
+ end
121
+ # :end :nested
122
+
123
+ # code used in figure next to nested
124
+
125
+ def isnest(n)
126
+ i = 1
127
+ while i < n
128
+ j = i-1
129
+ while j >= 0
130
+ puts "i = #{i}, j = #{j}"
131
+ j = j-1
132
+ end
133
+ i = i+1
134
+ end
135
+ end
136
+
137
+
138
+
139
+ end # Demos
140
+
141
+ end # RubyLabs
data/lib/elizalab.rb CHANGED
@@ -10,6 +10,10 @@ writing their own scripts.
10
10
 
11
11
  =end
12
12
 
13
+ =begin
14
+ TODO don't add rule to queue more than once (e.g. word repeated in input sentence)
15
+ =end
16
+
13
17
  module RubyLabs
14
18
 
15
19
  module ElizaLab
@@ -323,7 +327,7 @@ words (a|b|c), and variable names ($x) if they aren't already there.
323
327
 
324
328
  def Eliza.checkout(script, filename = nil)
325
329
  scriptfilename = script.to_s + ".txt"
326
- scriptfilename = File.join(@@dataDirectory, scriptfilename)
330
+ scriptfilename = File.join(@@elizaDirectory, scriptfilename)
327
331
  if !File.exists?(scriptfilename)
328
332
  puts "Script not found: #{scriptfilename}"
329
333
  return nil
@@ -492,7 +496,7 @@ words (a|b|c), and variable names ($x) if they aren't already there.
492
496
  Eliza.clear
493
497
  rule = nil
494
498
  if filename.class == Symbol
495
- filename = File.join(@@dataDirectory, filename.to_s + ".txt")
499
+ filename = File.join(@@elizaDirectory, filename.to_s + ".txt")
496
500
  end
497
501
  File.open(filename).each do |line|
498
502
  line.strip!
@@ -615,7 +619,7 @@ words (a|b|c), and variable names ($x) if they aren't already there.
615
619
  # the ElizaLab module
616
620
 
617
621
  @@verbose = false
618
- @@dataDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'eliza')
622
+ @@elizaDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'eliza')
619
623
  @@pre = Dictionary.new
620
624
  @@post = Dictionary.new
621
625
  @@rules = Dictionary.new
data/lib/hashlab.rb CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Methods used in experiments on hash tables.
6
6
 
7
- <b>NOTE:</b> the +insert+ and +find+ methods defined here are "stubs" used to test the hash
7
+ <b>NOTE:</b> the +insert+ and +lookup+ methods defined here are "stubs" used to test the hash
8
8
  functions. Load the file <tt>hashmethods.rb</tt> to overwrite the stubs with the actual
9
9
  methods that create and search buckets.
10
10
 
@@ -13,45 +13,8 @@ methods that create and search buckets.
13
13
  module RubyLabs
14
14
 
15
15
  module HashLab
16
-
17
- =begin rdoc
18
-
19
- Ruby version of the ``card catalog box'' number, a trivial hash function that
20
- maps two-letter strings to numbers between 0 and 675.
21
-
22
- =end
23
-
24
- # :begin :ccb
25
- def ccb(s)
26
- return s[0].ord * 26 + s[1].ord
27
- end
28
- # :end :ccb
29
-
30
- =begin rdoc
31
-
32
- The "small card catalog box" number, a version of ccb that works
33
- for a smaller catalog (n = 100 boxes).
34
-
35
- =end
36
-
37
- # :begin :sccb
38
- def sccb(s)
39
- return ccb(s) % 100
40
- end
41
- # :end :sccb
42
16
 
43
- =begin rdoc
44
-
45
- "Randomized" version of sccb that uses prime numbers to do
46
- a better job of scattering words more evenly.
47
-
48
- =end
49
-
50
- # :begin :rccb
51
- def rccb(s)
52
- return (s[0].ord * 31 + s[1].ord * 107) % 100
53
- end
54
- # :end :rccb
17
+ TableView = Struct.new(:table, :nrows)
55
18
 
56
19
  =begin rdoc
57
20
 
@@ -98,18 +61,61 @@ RubyLabs module.
98
61
  string to hash, +n+ is the size of the hash table.
99
62
  =end
100
63
 
101
- # :begin :hn
102
- def hn(s, n)
64
+ # :begin :hr
65
+ def hr(s, n)
103
66
  return radix26(s) % n
104
67
  end
105
- # :end :hn
68
+ # :end :hr
106
69
 
107
- alias h hn
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
88
+ def insert(s, t)
89
+ i = h(s, t.length)
90
+ if t[i].nil?
91
+ t[i] = s
92
+ return i
93
+ else
94
+ return nil
95
+ end
96
+ 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
105
+ def lookup(s, t)
106
+ i = h(s, t.length)
107
+ if t[i] == s
108
+ return i
109
+ else
110
+ return nil
111
+ end
112
+ end
113
+ # :end :lookup
108
114
 
109
115
  =begin rdoc
110
116
 
111
117
  Make a hash table with +n+ buckets. The optional parameter is a symbol specifying
112
- an alternative hash function (<tt>:h0</tt> or <tt>:h1</tt>); the default is the method +hn+, which
118
+ an alternative hash function (<tt>:h0</tt> or <tt>:h1</tt>); the default is the method +hr+, which
113
119
  uses the radix26 function.
114
120
 
115
121
  The object returned by this method is an array of the designated size. A bit of
@@ -120,104 +126,156 @@ Ruby magic defines three new methods just for the new object:
120
126
 
121
127
  =end
122
128
 
123
- def make_table(n, f = nil)
124
- t = Array.new(n)
125
-
126
- class <<t
127
- attr_accessor :h
129
+ class HashTable
128
130
 
129
- def hash(s)
130
- return case self.h
131
- when :h0 : h0(s, self.size)
132
- when :h1 : h1(s, self.size)
133
- else hn(s, self.size)
134
- end
135
- end
136
- end
137
-
138
- t.h = f
139
- return t
140
- end
131
+ @@hash_functions = [:h0, :h1, :hr]
132
+
133
+ attr_reader :table
141
134
 
135
+ def initialize(n, f = :hr)
136
+ raise "HashTable: hash function must be one of #{@@hash_functions.inspect}" unless @@hash_functions.include?(f)
137
+ @table = Array.new(n)
138
+ @hash = f
139
+ end
140
+
141
+ # def hash(s)
142
+ # return case @h
143
+ # when :h0 : h0(s, @table.length)
144
+ # when :h1 : h1(s, @table.length)
145
+ # else hr(s, @table.length)
146
+ # end
147
+ # end
148
+
149
+ def lookup(s)
150
+ i = send(@hash, s, @table.length)
151
+ return ( @table[i] && @table[i].include?(s) ) ? i : nil
152
+ end
153
+
154
+ def insert(s)
155
+ i = send(@hash, s, @table.length)
156
+ @table[i] = Array.new if @table[i].nil?
157
+ @table[i] << s
158
+ return i
159
+ end
160
+
161
+ def to_s
162
+ print_table(self)
163
+ end
164
+
165
+ def inspect
166
+ sprintf '#<RubyLabs::HashLab::HashTable: %d rows, :%s>', @table.size, @hash.to_s
167
+ end
168
+
142
169
  =begin rdoc
143
- Return +true+ or +false+, depending on whether string +s+ is in hash table +t+. The
144
- table must be an object created by a call to <tt>make_table</tt>.
170
+ Print usage statistics for the table. Prints lengths of longest and shortest
171
+ buckets, number of empty buckets, and mean bucket length.
145
172
  =end
146
173
 
147
- # :begin :find
148
- def find(s, t)
149
- i = t.hash(s)
150
- return (t[i] == s)
151
- end
152
- # :end :find
174
+ def print_stats
175
+ max = 0
176
+ min = Float::MAX
177
+ nzero = 0
178
+ sum = 0
179
+ @table.each do |bucket|
180
+ n = bucket.nil? ? 0 : bucket.length
181
+ min = n if n < min
182
+ max = n if n > max
183
+ sum += n
184
+ nzero += 1 if n == 0
185
+ end
186
+ printf "shortest bucket: %d\n", min
187
+ printf "longest bucket: %d\n", max
188
+ printf "empty buckets: %d\n", nzero
189
+ if max > 0
190
+ printf "mean bucket length: %.2f\n", sum.to_f / (@table.length - nzero)
191
+ end
192
+ return nil
193
+ end
153
194
 
154
195
  =begin rdoc
155
- Insert string +s+ into hash table +t+. The
156
- table must be an object created by a call to <tt>make_table</tt>.
196
+ Return a list of indices for buckets that have more than +cutoff+ entries.
157
197
  =end
158
-
159
- # :begin :insert
160
- def insert(s, t)
161
- i = t.hash(s)
162
- if t[i].nil?
163
- t[i] = s
164
- return true
165
- else
166
- puts "collision!"
167
- return false
198
+
199
+ def long_rows(cutoff = 5)
200
+ rows = Array.new
201
+ @table.each_with_index do |row, i|
202
+ rows << i if !row.nil? && row.length > cutoff
203
+ end
204
+ return rows
168
205
  end
169
- end
170
- # :end :insert
206
+
207
+ end # HashTable
171
208
 
172
209
  =begin rdoc
173
- Print a nicely formatted representation of hash table +t+. The optional parameter
210
+ Print a nicely formatted representation of hash table. The parameter +t+ can be
211
+ an array or a HashTable object. The optional parameter
174
212
  is the number of rows to print, e.g. to print just the first 10 rows of a large table
175
213
  +t+ call <tt>print_table(t,10)</tt>. Skips rows that have empty buckets.
176
214
  =end
177
215
 
178
216
  def print_table(t, max = nil)
217
+ t = t.table if t.class == HashTable
179
218
  max = t.length unless max
180
- max.times { |i| print_row(i, t) unless t[i].nil? }
181
- nil
219
+ max.times { |i| print_row(i, t[i] ) unless t[i].nil? }
220
+ return nil
182
221
  end
183
222
 
184
- def print_row(n, t) # :nodoc:
185
- printf "%4d: ", n
186
- p t[n]
223
+ def print_row(n, row) # :nodoc:
224
+ printf "%4d: %s\n", n, row.inspect
187
225
  end
188
-
226
+
189
227
  =begin rdoc
190
- Print usage statistics for hash table +t+. Prints lengths of longest and shortest
191
- buckets, number of empty buckets, and mean bucket length.
228
+ Verification of numbers shown in the table for the birthday paradox. Call
229
+ birthday(n,m) to make a table with n rows and fill it with m random words.
230
+ Return true if any row has more than one item.
192
231
  =end
193
-
194
- def print_stats(t)
195
- max = 0
196
- min = Float::MAX
197
- nzero = 0
198
- sum = 0
199
- t.each do |bucket|
200
- n = bucket.nil? ? 0 : bucket.length
201
- min = n if n < min
202
- max = n if n > max
203
- sum += n
204
- nzero += 1 if n == 0
205
- end
206
- puts "shortest bucket: #{min} items"
207
- puts "longest bucket: #{max} items"
208
- puts "empty buckets: #{nzero}"
209
- puts "mean bucket length: #{sum.to_f / (t.length - nzero)}" unless nzero == t.length
210
- nil
211
- end
212
232
 
233
+ def birthday(n, m)
234
+ t = HashTable.new(n)
235
+ TestArray.new(m, :words).each { |s| t.insert(s) }
236
+ # puts t
237
+ t.table.each { |row| return true if row && row.length > 1 }
238
+ return false
239
+ end
240
+
213
241
  =begin rdoc
214
- Scan the hash table +t+ and print all the buckets that have more than +cutoff+ entries.
242
+ Initialize the canvas with a drawing of an hash table +t+ (a table is
243
+ just an array augmented with a hash method)
215
244
  =end
216
245
 
217
- def print_long_rows(t, cutoff = 5)
218
- t.length.times { |i| print_row(i, t) if !t[i].nil? && t[i].length >= cutoff }
219
- nil
246
+ def view_table(t, userOptions = {} )
247
+ options = @@tableOptions.merge(userOptions)
248
+ Canvas.init(300, 500, "HashLab")
249
+ tbl = t.table
250
+ x0 = options[:tableX]
251
+ x1 = x0 + options[:cellWidth]
252
+ # if nil, color = gray, else color = white and draw text
253
+ # todo -- arrow
254
+ # todo -- save text, update with call to insert
255
+ nrows = min(tbl.size, options[:maxRows])
256
+ nrows.times do |i|
257
+ y0 = options[:tableY] + i * (options[:cellHeight] + options[:cellYSpace])
258
+ y1 = y0 + options[:cellHeight]
259
+ Canvas.rectangle(x0, y0, x1, y1, :fill => options[:cellColor])
260
+ # Canvas.text(tbl[i].inspect, textx, y0)
261
+ end
262
+ @@drawing = TableView.new(t, nrows)
263
+ Canvas.sync
264
+ return true
220
265
  end
266
+
267
+ @@tableOptions = {
268
+ :tableX => 20,
269
+ :tableY => 20,
270
+ :cellHeight => 15,
271
+ :cellYSpace => 2,
272
+ :cellWidth => 30,
273
+ :cellColor => :lightgray,
274
+ :textX => 70,
275
+ :maxRows => 25,
276
+ }
277
+
278
+
221
279
 
222
280
  end # HashLab
223
281
 
data/lib/introlab.rb CHANGED
@@ -1,14 +1,53 @@
1
1
  =begin rdoc
2
2
 
3
- Fahrenheit to Celsius conversion.
3
+ Methods used in Chapter 2 (the Ruby Workbench)
4
4
 
5
5
  =end
6
6
 
7
+ module RubyLabs
8
+
9
+ module IntroLab
10
+
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
+ =end
15
+
16
+ # :begin :countertop
17
+ def countertop(x)
18
+ square = x**2
19
+ triangle = ((x/2)**2) / 2
20
+ return square - triangle
21
+ end
22
+ # :end :countertop
23
+
7
24
  =begin rdoc
8
25
  Convert the temperature +f+ (in degrees Fahrenheit) into the equivalent
9
26
  temperature in degrees Celsius.
10
27
  =end
11
28
 
12
- def celsius(f)
13
- (f - 32) * 5 / 9
14
- end
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
50
+
51
+ end # IntroLab
52
+
53
+ end # RubyLabs
data/lib/iterationlab.rb CHANGED
@@ -51,20 +51,20 @@ array, find a location for it to the left of its original position,
51
51
  and insert it back into the array at the new location.
52
52
  =end
53
53
 
54
- # :begin :isort :insert_left :less
54
+ # :begin :isort :move_left :less
55
55
  def isort(array)
56
56
  a = array.clone # don't modify the input array....
57
57
  i = 1
58
58
  while i < a.length
59
- insert_left(a, i) # find a place for a[i] somewhere to the left
59
+ move_left(a, i) # find a place for a[i] somewhere to the left
60
60
  i += 1
61
61
  end
62
62
  return a
63
63
  end
64
64
  # :end :isort
65
65
 
66
- # :begin :insert_left
67
- def insert_left(a, i)
66
+ # :begin :move_left
67
+ def move_left(a, i)
68
68
  x = a.slice!(i) # remove the item at location i
69
69
  j = i-1 # start scanning from the left of i
70
70
  while j >= 0 && less(x, a[j])
@@ -72,7 +72,7 @@ and insert it back into the array at the new location.
72
72
  end
73
73
  a.insert(j+1, x) # insert x back into a at location j
74
74
  end
75
- # :end :insert_left
75
+ # :end :move_left
76
76
 
77
77
  # :begin :less
78
78
  def less(x, y)
@@ -80,34 +80,6 @@ and insert it back into the array at the new location.
80
80
  end
81
81
  # :end :less
82
82
 
83
- =begin rdoc
84
- Simple demonstration of why nested loops lead to n^2 operations
85
- =end
86
-
87
- # :begin :nested
88
- def nested(n)
89
- n.times do |i|
90
- n.times do |j|
91
- puts "i = #{i}, j = #{j}"
92
- end
93
- end
94
- end
95
- # :end :nested
96
-
97
- # code used in figure next to nested
98
-
99
- def isnest(n)
100
- i = 1
101
- while i < n
102
- j = i-1
103
- while j >= 0
104
- puts "i = #{i}, j = #{j}"
105
- j = j-1
106
- end
107
- i = i+1
108
- end
109
- end
110
-
111
83
  =begin rdoc
112
84
  A helper method that can be called from a probe to display the contents
113
85
  of an array during a search or sort.
data/lib/marslab.rb CHANGED
@@ -289,7 +289,7 @@ module MARSLab
289
289
 
290
290
  def initialize(filename)
291
291
  if filename.class == Symbol
292
- filename = File.join(@@dataDirectory, filename.to_s + ".txt")
292
+ filename = File.join(@@marsDirectory, filename.to_s + ".txt")
293
293
  end
294
294
 
295
295
  if ! File.exists?(filename)
@@ -1062,7 +1062,7 @@ module MARSLab
1062
1062
  def MARS.listing(prog, dest = nil)
1063
1063
  filename = prog.to_s
1064
1064
  filename += ".txt" unless filename =~ /\.txt$/
1065
- filename = File.join(@@dataDirectory, filename)
1065
+ filename = File.join(@@marsDirectory, filename)
1066
1066
  dest = STDOUT if dest.nil?
1067
1067
  if !File.exists?(filename)
1068
1068
  puts "File not found: #{filename}"
@@ -1092,8 +1092,8 @@ module MARSLab
1092
1092
  =end
1093
1093
 
1094
1094
  def MARS.dir
1095
- puts "Redcode programs in #{@@dataDirectory}:"
1096
- Dir.open(@@dataDirectory).each do |file|
1095
+ puts "Redcode programs in #{@@marsDirectory}:"
1096
+ Dir.open(@@marsDirectory).each do |file|
1097
1097
  next if file[0] == ?.
1098
1098
  file.slice!(/\.txt/)
1099
1099
  puts " " + file
@@ -1120,7 +1120,7 @@ module MARSLab
1120
1120
  # -- Initializations -- These are "global" vars in the outer MARSLab scope that are
1121
1121
  # accessible to all the classes and modules defined inside MARSLab
1122
1122
 
1123
- @@dataDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'mars')
1123
+ @@marsDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'mars')
1124
1124
 
1125
1125
  @@opcodes = ["DAT", "MOV", "ADD", "SUB", "JMP", "JMZ", "JMN", "DJN", "CMP", "SPL", "END", "SLT", "EQU"]
1126
1126
  @@modes = "@<#"