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/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