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/README.rdoc +15 -6
- data/Rakefile +3 -0
- data/VERSION +1 -1
- data/lib/bitlab.rb +593 -328
- data/lib/demos.rb +20 -9
- data/lib/elizalab.rb +660 -507
- data/lib/hashlab.rb +289 -192
- data/lib/introlab.rb +33 -38
- data/lib/iterationlab.rb +117 -61
- data/lib/marslab.rb +608 -475
- data/lib/randomlab.rb +227 -121
- data/lib/recursionlab.rb +197 -140
- data/lib/rubylabs.rb +936 -390
- data/lib/sievelab.rb +32 -24
- data/lib/spherelab.rb +308 -220
- data/lib/tsplab.rb +634 -312
- data/test/bit_test.rb +4 -4
- data/test/tsp_test.rb +18 -0
- metadata +2 -2
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
155
|
+
return a
|
128
156
|
end
|
129
157
|
# :end :msort
|
130
158
|
|
131
|
-
#
|
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
|
-
#
|
145
|
-
# a
|
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)
|
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
|
-
#
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
|