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/recursionlab.rb CHANGED
@@ -1,69 +1,86 @@
1
+ module RubyLabs
1
2
 
2
3
  =begin rdoc
3
4
 
4
5
  == RecursionLab
5
6
 
6
- Divide and conquer algorithms for searching and sorting.
7
- The methods implemented in this module are +bsearch+ (binary search),
8
- +msort+ (merge sort), and +qsort!+ (QuickSort). The module also
9
- contains 'helper methods' that can be used to trace the execution
7
+ The RecursionLab module has definitions of methods from Chapter 5
8
+ of <em>Explorations in Computing</em>.
9
+
10
+ The methods implemented in this module show how a divide and conquer strategy
11
+ can lead to more efficient algorithms for searching and sorting.
12
+ The method defined here include +bsearch+ (binary search),
13
+ +msort+ (merge sort), and +qsort+ (QuickSort).
14
+
15
+ The module also contains 'helper methods' that can be used to trace the execution
10
16
  of the algorithms.
11
17
 
12
18
  =end
13
19
 
14
- module RubyLabs
15
-
16
20
  module RecursionLab
17
21
 
18
- =begin rdoc
19
-
20
- Linear search -- use for baseline tests. Form that makes it easy to attach
21
- probe to count comparisons.
22
- =end
23
-
22
+ # The linear search method from iterationlab.rb is replicated here so it can be
23
+ # used for baseline tests.
24
+ #--
24
25
  # :begin :search
25
- def search(a, k)
26
- i = 0
27
- while i < a.length
28
- return i if a[i] == k
29
- i += 1
30
- end
31
- return nil
32
- end
26
+ def search(a, k) # :nodoc:
27
+ i = 0
28
+ while i < a.length
29
+ return i if a[i] == k
30
+ i += 1
31
+ end
32
+ return nil
33
+ end
33
34
  # :end :search
34
35
 
35
-
36
- =begin rdoc
37
-
38
- Call <tt>bsearch(k, a)</tt> to search for item +k+ in array +a+, using
39
- the binary search algorithm. Based on the specification in Introduction
40
- to Algorithms, by Cormen et al.
41
-
42
- =end
43
-
36
+ # Search array +a+ for item +k+ using the binary search algorithm. Returns
37
+ # the location of +k+ if it's found, or +nil+ if +k+ is not in the array.
38
+ # Note that the array must be sorted or the results are unpredictable.
39
+ #
40
+ # Example:
41
+ # >> a = TestArray.new(10).sort
42
+ # => [5, 9, 10, 22, 38, 43, 74, 78, 86, 88]
43
+ # >> bsearch(a, 38)
44
+ # => 4
45
+ # >> bsearch(a, 26)
46
+ # => nil
47
+ #
48
+ # :call-seq:
49
+ # bsearch(a,k) => Fixnum
50
+ #
51
+ # Based on the specification in Introduction to Algorithms, by Cormen et al.
52
+ #--
44
53
  # :begin :bsearch
45
- def bsearch(a, k)
46
- lower = -1
47
- upper = a.length
48
- while true # iteration ends with return statement
49
- mid = (lower + upper) / 2 # set the mid point for this iteration
50
- return nil if upper == lower + 1 # search fails if the region is empty
51
- return mid if k == a[mid] # search succeeds if k is at the midpoint
52
- if k < a[mid]
53
- upper = mid # next search: lower half of the region
54
- else
55
- lower = mid # next search: upper half
56
- end
57
- end
58
- end
54
+ def bsearch(a, k)
55
+ lower = -1
56
+ upper = a.length
57
+ while true # iteration ends with return statement
58
+ mid = (lower + upper) / 2 # set the mid point for this iteration
59
+ return nil if upper == lower + 1 # search fails if the region is empty
60
+ return mid if k == a[mid] # search succeeds if k is at the midpoint
61
+ if k < a[mid]
62
+ upper = mid # next search: lower half of the region
63
+ else
64
+ lower = mid # next search: upper half
65
+ end
66
+ end
67
+ end
59
68
  # :end :bsearch
60
69
 
61
- =begin rdoc
62
-
63
- Recursive implementation of binary search.
64
-
65
- =end
66
-
70
+ # An alternative implementation of binary search, using a recursive method.
71
+ #
72
+ # Example:
73
+ # >> a = TestArray.new(10).sort
74
+ # => [5, 9, 10, 22, 38, 43, 74, 78, 86, 88]
75
+ # >> rbsearch(a, 38)
76
+ # => 4
77
+ # >> rbsearch(a, 26)
78
+ # => nil
79
+ #
80
+ # :call-seq:
81
+ # rbsearch(a,k) => Fixnum
82
+ #
83
+ #--
67
84
  # :begin :rbsearch
68
85
  def rbsearch(a, k, lower = -1, upper = a.length)
69
86
  mid = (lower + upper) / 2
@@ -77,12 +94,28 @@ Recursive implementation of binary search.
77
94
  end
78
95
  # :end :rbsearch
79
96
 
80
- =begin rdoc
81
- A helper method that can be called from a probe to display the contents
82
- of an array during a search or sort.
83
- =end
84
-
85
- def brackets(a, left, right = a.length-1, mid = nil) # :nodoc:
97
+ # A helper method that can be called from a probe to display the contents
98
+ # of an array during a search or sort. See also IterationLab#brackets.
99
+ #
100
+ # The version implemented in this module accepts an additional argument,
101
+ # which specifies the location of the right bracket in the output string
102
+ # (if this argument is not give the right bracket is inserted at the end).
103
+ #
104
+ # An optional third argument, intended for experiments with binary search, tells
105
+ # the method where to insert an asterisk before an item.
106
+ #
107
+ # Examples:
108
+ #
109
+ # >> a = TestArray.new(15)
110
+ # => [22, 3, 70, 74, 35, 0, 82, 64, 90, 54, 88, 26, 75, 18, 17]
111
+ # >> puts brackets(a, 8, 10)
112
+ # 22 3 70 74 35 0 82 64 [90 54 88] 26 75 18 17
113
+ # => nil
114
+ # >> puts brackets(a, 8, 10, 9)
115
+ # 22 3 70 74 35 0 82 64 [90 *54 88] 26 75 18 17
116
+ # => nil
117
+ #
118
+ def brackets(a, left, right = a.length-1, mid = nil)
86
119
  left = 0 if left < 0
87
120
  right = a.length-1 if right >= a.length
88
121
  pre = left == 0 ? "" : " " + a.slice(0..(left-1)).join(" ") + " "
@@ -93,29 +126,24 @@ of an array during a search or sort.
93
126
  res[res.index(a[mid].to_s)-1] = ?*
94
127
  end
95
128
  return res
96
- end
97
-
98
- =begin rdoc
99
-
100
- A test harness for bsearch -- makes an array, gets an item not in the array,
101
- counts comparisons (assuming user has set a counting probe)
102
-
103
- =end
104
-
105
- def bsearch_count(n)
106
- a = TestArray.new(n)
107
- x = a.random(:fail)
108
- count { bsearch(a,x) }
109
129
  end
110
-
111
- =begin rdoc
112
-
113
- Merge sort. Makes a copy of the input array, returns a sorted
114
- version of the copy. Uses a "helper function" to
115
- combine successively bigger pieces of the input array.
116
-
117
- =end
118
-
130
+
131
+
132
+ # Return a copy of +a+, sorted using the merge sort algorithm.
133
+ # Each iteration merges successively larger groups of sorted sub-arrays.
134
+ # Since the group size doubles from one iteration to the next, the
135
+ # method needs only log(n) iterations to sort an array with +n+ items.
136
+ #
137
+ # Example:
138
+ # >> a = TestArray.new(10)
139
+ # => [14, 23, 74, 83, 7, 19, 57, 29, 20, 1]
140
+ # >> msort(a)
141
+ # => [1, 7, 14, 19, 20, 23, 29, 57, 74, 83]
142
+ #
143
+ # :call-seq:
144
+ # msort(a) => Array
145
+ #
146
+ #--
119
147
  # :begin :msort :merge :merge_groups :less
120
148
  def msort(array)
121
149
  a = array.clone
@@ -124,12 +152,13 @@ def msort(array)
124
152
  merge_groups(a, size)
125
153
  size = size * 2
126
154
  end
127
- return a
155
+ return a
128
156
  end
129
157
  # :end :msort
130
158
 
131
- # "Helper method" to merge all groups of size g
132
-
159
+ # A helper method called from +msort+, used to merge adjacent groups of size +gs+
160
+ # in array +a+.
161
+ #--
133
162
  # :begin :merge_groups
134
163
  def merge_groups(a, gs)
135
164
  i = 0 # first group starts here
@@ -141,11 +170,12 @@ def merge_groups(a, gs)
141
170
  end
142
171
  # :end :merge_groups
143
172
 
144
- # "Helper method" to merge two blocks. A call of the form merge(a, i, n) creates
145
- # a new list by merging n-element lists starting at a[i] and a[i+n].
146
-
173
+ # A helper method called from <tt>merge_groups</tt>. A call of the form
174
+ # <tt>merge(a, i, n)</tt> creates a list formed by merging +n+-element
175
+ # lists starting at <tt>a[i]</tt> and <tt>a[i+n]</tt>.
176
+ #--
147
177
  # :begin :merge
148
- def merge(a, i, gs) # :nodoc:
178
+ def merge(a, i, gs)
149
179
  ix = j = min(i + gs, a.length)
150
180
  jx = min(j + gs, a.length)
151
181
  res = []
@@ -162,74 +192,101 @@ end
162
192
  end
163
193
  # :end :merge
164
194
 
195
+ # A copy of the +less+ method from iterationlab.rb, reimplemented here so
196
+ # students can attach probes without loading IterationLab
197
+ #--
165
198
  # :begin :less
166
- def less(x, y)
199
+ def less(x, y) # :nodoc:
167
200
  return x < y
168
201
  end
169
202
  # :end :less
170
203
 
171
- # Helper function to print brackets during merge sort
172
-
173
- def msort_brackets(a, n) # :nodoc:
174
- # return " [" + a[i..i+2*n-1].join(" ") + "] "
175
- res = []
176
- i = 0
177
- while i < a.length
178
- res << a[i..((i+n)-1)].join(" ")
179
- i += n
180
- end
181
- return "[" + res.join("] [") + "]"
204
+ # A method designed to be used in experiments with the merge sort algorithm.
205
+ # A call of the form <tt>msort_brackets(a, n)</tt> will create a string with
206
+ # every item from +a+, with brackets around each group of size +n+.
207
+ #
208
+ # Example:
209
+ # >> a = TestArray.new(16)
210
+ # => [45, 10, 33, 41, 57, 84, 7, 96, 18, 44, 89, 94, 36, 90, 87, 97]
211
+ # >> puts msort_brackets(a, 4)
212
+ # [45 10 33 41] [57 84 7 96] [18 44 89 94] [36 90 87 97]
213
+ # => nil
214
+ #
215
+ # :call-seq:
216
+ # msort_brackets(a,n) => String
217
+ #
218
+ def msort_brackets(a, n)
219
+ res = []
220
+ i = 0
221
+ while i < a.length
222
+ res << a[i..((i+n)-1)].join(" ")
223
+ i += n
224
+ end
225
+ return "[" + res.join("] [") + "]"
182
226
  end
183
-
184
- =begin rdoc
185
-
186
- QuickSort, based on the description from Cormen et al. Sorts the input array in place.
187
- The parameters (using notation from Cormen et al): +p+ is the left boundary of the region
188
- to sort, and +r+ is right boundary. The call to +partition+ sets +q+, the new boundary
189
- between two sub-regions.
190
-
191
- =end
192
-
227
+
228
+ # Return a sorted copy of +a+, sorted using the QuickSort algorithm.
229
+ # The parameters +p+ and +q+ represent the left and right boundaries of
230
+ # the region to sort. The top level call to +qsort+ should not include
231
+ # values for +p+ and +q+, so they are initially set to the the beginning
232
+ # and ending locations in the array. Recursive calls will pass values for
233
+ # +p+ and +q+ that define successively smaller regions to sort.
234
+ #
235
+ # Example:
236
+ # >> a = TestArray.new(10)
237
+ # => [58, 94, 62, 63, 85, 22, 64, 45, 30, 77]
238
+ # >> qsort(a)
239
+ # => [22, 30, 45, 58, 62, 63, 64, 77, 85, 94]
240
+ #
241
+ # Based on the specification in Introduction to Algorithms, by Cormen et al.
242
+ #
243
+ #--
193
244
  # :begin :qsort :partition
194
- def qsort(a, p = 0, r = a.length-1) # sort the region bounded by p and r
195
- a = a.dup if p == 0 && r == a.length-1 # don't modify the input array (top level only)
196
- if p < r
197
- q = partition(a, p, r) # q is boundary between small items and large items
198
- qsort(a, p, q) # sort small items (range from p to q)
199
- qsort(a, q+1, r) # sort large items (range from q+1 to r)
200
- end
201
- return a
202
- end
245
+ def qsort(a, p = 0, r = a.length-1) # sort the region bounded by p and r
246
+ a = a.dup if p == 0 && r == a.length-1 # don't modify the input array (top level only)
247
+ if p < r
248
+ q = partition(a, p, r) # q is boundary between small items and large items
249
+ qsort(a, p, q) # sort small items (range from p to q)
250
+ qsort(a, q+1, r) # sort large items (range from q+1 to r)
251
+ end
252
+ return a
253
+ end
203
254
  # :end :qsort
204
255
 
256
+ # Helper method for +qsort+. Rearrange array +a+ so that all the items in
257
+ # the region between <tt>a[p]</tt> and <tt>a[r]</tt> are divided into two
258
+ # groups, such that every item in the left group is smaller than any item
259
+ # in the right group. The return value is the location of the boundary
260
+ # between the groups.
261
+ #
262
+ #--
205
263
  # :begin :partition
206
- def partition(a, p, r) # partition the region bounded by p and r
207
- x = a[p] # x is the pivot value
208
- i = p - 1
209
- j = r + 1
210
- while true # squeeze i, j until they point at items to exchange
211
- loop { j = j - 1; break if a[j] <= x }
212
- loop { i = i + 1; break if a[i] >= x }
213
- if i < j
214
- a[i], a[j] = a[j], a[i] # exchange items at locations i and j
215
- else
216
- return j # no more exchanges; return location that separates regions
217
- end
218
- end
219
- end
264
+ def partition(a, p, r) # partition the region bounded by p and r
265
+ x = a[p] # x is the pivot value
266
+ i = p - 1
267
+ j = r + 1
268
+ while true # squeeze i, j until they point at items to exchange
269
+ loop { j = j - 1; break if a[j] <= x }
270
+ loop { i = i + 1; break if a[i] >= x }
271
+ if i < j
272
+ a[i], a[j] = a[j], a[i] # exchange items at locations i and j
273
+ else
274
+ return j # no more exchanges; return location that separates regions
275
+ end
276
+ end
277
+ end
220
278
  # :end :partition
221
279
 
222
- # Helper procedure used to trace the execution of qsort
223
-
224
- def qsort_brackets(a, left, right) # :nodoc:
225
- tmp = []
226
- tmp += a[ 0 .. (left-1) ] if left > 0
227
- tmp << "["
228
- tmp += a[ left .. right ] if right >= 0
229
- tmp << "]"
230
- tmp += a[ (right+1) .. (a.length-1) ] if right < a.length
231
- return tmp.join(" ")
232
- end
280
+ # Helper procedure used to trace the execution of qsort.
281
+ def qsort_brackets(a, left, right)
282
+ tmp = []
283
+ tmp += a[ 0 .. (left-1) ] if left > 0
284
+ tmp << "["
285
+ tmp += a[ left .. right ] if right >= 0
286
+ tmp << "]"
287
+ tmp += a[ (right+1) .. (a.length-1) ] if right < a.length
288
+ return tmp.join(" ")
289
+ end
233
290
 
234
291
  end # RecursionLab
235
292