rubylabs 0.5.4
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/LICENSE +20 -0
- data/README.rdoc +9 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/bin/bb.rb +12 -0
- data/bin/statistics2-0.53/ext/extconf.rb +11 -0
- data/bin/statistics2-0.53/ext/show.rb +11 -0
- data/bin/statistics2-0.53/ext/t.rb +46 -0
- data/bin/statistics2-0.53/mklist.rb +26 -0
- data/bin/statistics2-0.53/sample-tbl.rb +129 -0
- data/bin/statistics2-0.53/statistics2.rb +532 -0
- data/bin/statistics2-0.53/t-inv.rb +54 -0
- data/bin/statistics2.rb +532 -0
- data/data/aafreq.txt +20 -0
- data/data/cars.txt +50 -0
- data/data/century.txt +1 -0
- data/data/colors.txt +64 -0
- data/data/earth.yaml +15 -0
- data/data/fruit.txt +45 -0
- data/data/hacodes.txt +35 -0
- data/data/hafreq.txt +16 -0
- data/data/hvfreq.txt +5 -0
- data/data/nbody.R +23 -0
- data/data/nbody.out +731 -0
- data/data/nbody.pdf +3111 -0
- data/data/nbody.png +0 -0
- data/data/nbody3d.pdf +3201 -0
- data/data/outer.pdf +182785 -0
- data/data/solar.dat +36501 -0
- data/data/solarsystem.txt +17 -0
- data/data/suits.txt +1 -0
- data/data/wordlist.txt +210653 -0
- data/lib/bitlab.rb +624 -0
- data/lib/elizalab.rb +523 -0
- data/lib/encryptionlab.rb +42 -0
- data/lib/hashlab.rb +224 -0
- data/lib/introlab.rb +14 -0
- data/lib/iterationlab.rb +130 -0
- data/lib/randomlab.rb +294 -0
- data/lib/recursionlab.rb +228 -0
- data/lib/rubylabs.rb +507 -0
- data/lib/sievelab.rb +58 -0
- data/lib/sortlab.rb +213 -0
- data/lib/spherelab.rb +352 -0
- data/lib/temps.rb +41 -0
- data/lib/tsplab.rb +416 -0
- data/lib/viewer.rb +65 -0
- data/test/bit_test.rb +175 -0
- data/test/encryption_test.rb +20 -0
- data/test/iteration_test.rb +40 -0
- data/test/random_test.rb +64 -0
- data/test/recursion_test.rb +47 -0
- data/test/rubylabs_test.rb +18 -0
- data/test/sieve_test.rb +28 -0
- data/test/sphere_test.rb +130 -0
- data/test/temps_test.rb +24 -0
- data/test/test_helper.rb +18 -0
- metadata +112 -0
data/lib/tsplab.rb
ADDED
@@ -0,0 +1,416 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
|
3
|
+
== Traveling Salesman Lab
|
4
|
+
|
5
|
+
=end
|
6
|
+
|
7
|
+
=begin
|
8
|
+
TODO test predigree counts
|
9
|
+
TODO add "phylogeny" links -- keep refs to parent(s)
|
10
|
+
TODO test: selection only, no mutations
|
11
|
+
TODO test: random draws only, no selection
|
12
|
+
TODO plot space: get all 180K costs, order lexicographically, plot (smoothed)
|
13
|
+
=end
|
14
|
+
|
15
|
+
require 'set'
|
16
|
+
|
17
|
+
=begin rdoc
|
18
|
+
|
19
|
+
Objects of the CityList class store city names and distances between pairs of
|
20
|
+
cities. The instance variables are a hash associating a key with a
|
21
|
+
city name and a matrix of distances. The matrix is a hash of
|
22
|
+
hashes, indexed by city, where distances are driving times in
|
23
|
+
minutes.
|
24
|
+
|
25
|
+
=end
|
26
|
+
|
27
|
+
# Public methods:
|
28
|
+
# new(f) initializes an object using names and distances in file f
|
29
|
+
# size() returns number of cities
|
30
|
+
# keys() returns an array of keys
|
31
|
+
# values() returns an array of city names
|
32
|
+
# [x] returns the name of the city with key x
|
33
|
+
# distance(x,y) returns the distance between cities with keys x and y
|
34
|
+
|
35
|
+
|
36
|
+
class CityList
|
37
|
+
|
38
|
+
=begin rdoc
|
39
|
+
The lines in the input file passed to the constructor come from a
|
40
|
+
program that uses Google Maps to look up dirving times, so there are
|
41
|
+
3 distance fields: days, hours, minutes. The input record format is
|
42
|
+
city 1, city 2, days, hours, minutes, all separated by tabs. Each
|
43
|
+
city is assigned a one-letter key.
|
44
|
+
=end
|
45
|
+
|
46
|
+
def initialize(fn)
|
47
|
+
raise "CityList.new: must specify city file" unless fn != nil
|
48
|
+
|
49
|
+
@key = ?A
|
50
|
+
@cities = Hash.new # initially a map from name string to one-letter key
|
51
|
+
@matrix = Hash.new
|
52
|
+
|
53
|
+
File.open(fn).each do |line|
|
54
|
+
next if line.chomp.length == 0
|
55
|
+
rec = line.split(/\t/)
|
56
|
+
i = getKey(rec[0])
|
57
|
+
j = getKey(rec[1])
|
58
|
+
dist = ((24 * getValue(rec[2]) + getValue(rec[3])) * 60) + getValue(rec[4])
|
59
|
+
assign(i,j,dist)
|
60
|
+
assign(j,i,dist)
|
61
|
+
end
|
62
|
+
|
63
|
+
@cities = @cities.invert # from now on a map from key to name string
|
64
|
+
|
65
|
+
errs = 0
|
66
|
+
(?A..@key-1).each do |i|
|
67
|
+
(i+1..@key-1).each do |j|
|
68
|
+
unless @matrix[i][j] && @matrix[j][i]
|
69
|
+
errs += 1
|
70
|
+
puts "CityList.new: missing distance between #{@cities[i]} and #{@cities[j]}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
raise "CityList.new: errors in input file" if errs > 0
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def inspect()
|
79
|
+
n = @matrix.length
|
80
|
+
return "List of #{n} cities"
|
81
|
+
end
|
82
|
+
|
83
|
+
def size()
|
84
|
+
return @key - ?A
|
85
|
+
end
|
86
|
+
|
87
|
+
def keys()
|
88
|
+
return @cities.keys.sort.map{|x| x.chr}
|
89
|
+
end
|
90
|
+
|
91
|
+
def values()
|
92
|
+
return @cities.keys.sort.collect{|x| @cities[x]}
|
93
|
+
end
|
94
|
+
|
95
|
+
def [](x)
|
96
|
+
x = x[0] if x.class == String
|
97
|
+
raise "unknown key: #{x}" unless x >= ?A && x < @key
|
98
|
+
return @cities[x]
|
99
|
+
end
|
100
|
+
|
101
|
+
def distance(x,y)
|
102
|
+
x = x[0] if x.class == String
|
103
|
+
y = y[0] if y.class == String
|
104
|
+
raise "unknowns key: #{x}" unless x >= ?A && x < @key
|
105
|
+
raise "unknown key: #{y}" unless y >= ?A && y < @key
|
106
|
+
return @matrix[x][y]
|
107
|
+
end
|
108
|
+
|
109
|
+
# private methods -- return the key for a city (make a new one if necessary),
|
110
|
+
# get a time value from an input line, save a distance
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def getKey(s)
|
115
|
+
if @cities[s] == nil
|
116
|
+
@cities[s] = @key
|
117
|
+
@key += 1
|
118
|
+
end
|
119
|
+
return @cities[s]
|
120
|
+
end
|
121
|
+
|
122
|
+
def getValue(s)
|
123
|
+
a = s.scan(/(\d+)/)
|
124
|
+
if a.length == 1
|
125
|
+
return a[0][0].to_i
|
126
|
+
else
|
127
|
+
return 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def assign(i,j,v)
|
132
|
+
if @matrix[i] == nil
|
133
|
+
@matrix[i] = Hash.new
|
134
|
+
end
|
135
|
+
@matrix[i][j] = v
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
class String
|
141
|
+
|
142
|
+
=begin rdoc
|
143
|
+
Call <tt>s.permute</tt> to scramble the characters in string +s+.
|
144
|
+
=end
|
145
|
+
|
146
|
+
def permute
|
147
|
+
for i in 0..length-2
|
148
|
+
r = rand(length-i) + i # i <= r < length
|
149
|
+
self[i],self[r] = self[r],self[i]
|
150
|
+
end
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
=begin rdoc
|
156
|
+
Call <tt>s.rotate</tt> to "rotate" the characters in string +s+, i.e. detach
|
157
|
+
the last character in +s+ and reinsert it at the front.
|
158
|
+
=end
|
159
|
+
|
160
|
+
def rotate(n)
|
161
|
+
if n > 0
|
162
|
+
tail = self[n..-1]
|
163
|
+
self[n..-1] = ""
|
164
|
+
self.insert(0,tail)
|
165
|
+
end
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
=begin rdoc
|
172
|
+
|
173
|
+
Each Tour object is a string where each letter represents a city, along with a cost
|
174
|
+
defined by a CityList matrix that must be specified when the tour is created. If
|
175
|
+
an initial string is given verify it contains only valid keys. If no initial
|
176
|
+
string is given make a tour with a random permutation of all city keys.
|
177
|
+
|
178
|
+
The +nm+ and +nx+ attributes keep track of a tour's "pedigree." The counters are
|
179
|
+
inherited from a parent, and incremented whenever a mutation or crossover is applied.
|
180
|
+
|
181
|
+
=end
|
182
|
+
|
183
|
+
|
184
|
+
class Tour
|
185
|
+
attr_reader :id, :path, :cost, :matrix, :nm, :nx
|
186
|
+
|
187
|
+
@@id = 0
|
188
|
+
|
189
|
+
def initialize(m, s = nil, nm = 0, nx = 0)
|
190
|
+
if s == nil
|
191
|
+
@path = m.keys.to_s.permute
|
192
|
+
else
|
193
|
+
skeys = Set.new(m.keys)
|
194
|
+
used = Set.new
|
195
|
+
s.each_byte do |x|
|
196
|
+
raise "Tour.new: invalid character in tour: #{x}" unless skeys.member?(x.chr)
|
197
|
+
raise "Tour.new: duplicate character in tour: #{x.chr}" if used.member?(x)
|
198
|
+
used.add(x)
|
199
|
+
end
|
200
|
+
@path = s.clone
|
201
|
+
end
|
202
|
+
@cost = pathcost(m,@path)
|
203
|
+
@matrix = m # need matrix to update cost after mutation...
|
204
|
+
@id = @@id
|
205
|
+
@nm = nm
|
206
|
+
@nx = nx
|
207
|
+
@@id += 1
|
208
|
+
end
|
209
|
+
|
210
|
+
# An alterntaive constructor -- either copy a single parent and add
|
211
|
+
# a point mutation, or cross two parents
|
212
|
+
|
213
|
+
def Tour.reproduce(x, y = nil, trace = nil)
|
214
|
+
if y.nil? || y == :trace
|
215
|
+
i = rand(x.path.length)
|
216
|
+
j = (i+1) % x.path.length
|
217
|
+
trace = :trace if y == :trace # handle calls like reproduce(x, :trace)
|
218
|
+
x.clone.mutate(i, j, trace)
|
219
|
+
else
|
220
|
+
i = rand(x.path.length)
|
221
|
+
loop { j = rand(x.path.length); break if j != i }
|
222
|
+
i, j = j, i if i > j
|
223
|
+
x.clone.cross(y, i, j, trace)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def to_s()
|
228
|
+
return "\##{@id}: #{@path} / #{@cost}"
|
229
|
+
end
|
230
|
+
|
231
|
+
def inspect
|
232
|
+
return to_s
|
233
|
+
end
|
234
|
+
|
235
|
+
# Reset the id to 0 (e.g. to count number of objects)
|
236
|
+
|
237
|
+
def Tour.reset
|
238
|
+
@@id = 0
|
239
|
+
end
|
240
|
+
|
241
|
+
def Tour.count
|
242
|
+
@@id
|
243
|
+
end
|
244
|
+
|
245
|
+
# A copy of a tour needs its own new id and copy of the path and pedigree
|
246
|
+
|
247
|
+
def clone
|
248
|
+
return Tour.new(@matrix, @path, @nm, @nx)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Exchange mutation (called EM by Larranaga et al). Simply swaps two
|
252
|
+
# cities at the specified locations.
|
253
|
+
|
254
|
+
def mutate(i, j, trace = nil)
|
255
|
+
puts "mutate: #{i} <-> #{j}" if ! trace.nil?
|
256
|
+
@path[i], @path[j] = @path[j], @path[i]
|
257
|
+
@cost = pathcost(@matrix,@path)
|
258
|
+
@nm += 1
|
259
|
+
self
|
260
|
+
end
|
261
|
+
|
262
|
+
# Order cross-over (called OX1 by Larranaga et al). Save a chunk of the
|
263
|
+
# current tour, then copy the remaining cities in the order they occur in
|
264
|
+
# tour t.
|
265
|
+
|
266
|
+
def cross(t, i, j, trace = nil)
|
267
|
+
puts "cross: copy #{i}..#{j}" if ! trace.nil?
|
268
|
+
@path = @path[i..j]
|
269
|
+
t.path.each_byte do |c|
|
270
|
+
@path << c unless @path.include?(c)
|
271
|
+
end
|
272
|
+
@cost = pathcost(@matrix,@path)
|
273
|
+
@nx += 1 + t.nx
|
274
|
+
@nm += t.nm
|
275
|
+
self
|
276
|
+
end
|
277
|
+
|
278
|
+
# private methods -- compute the cost of a path, given a matrix; apply
|
279
|
+
# mutations (point mutations, cross-overs)
|
280
|
+
|
281
|
+
private
|
282
|
+
|
283
|
+
def pathcost(m,s)
|
284
|
+
sum = m.distance(s[0],s[-1]) # link from end to start
|
285
|
+
for i in 0..s.length-2
|
286
|
+
sum += m.distance(s[i],s[i+1])
|
287
|
+
end
|
288
|
+
return sum
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
# Make a population of size n -- n instances of tours made from a set
|
294
|
+
# of cities
|
295
|
+
|
296
|
+
def initPopulation(n,m)
|
297
|
+
a = Array.new
|
298
|
+
|
299
|
+
n.times do
|
300
|
+
a.push(Tour.new(m))
|
301
|
+
end
|
302
|
+
|
303
|
+
a.sort! { |x,y| x.cost <=> y.cost }
|
304
|
+
|
305
|
+
return a
|
306
|
+
end
|
307
|
+
|
308
|
+
# Evolve a population. Sort by fitness, then remove individuals with
|
309
|
+
# a probability based on their ranking (the ith individual survives
|
310
|
+
# with p = (n-i)/n). Then Build back up to the original population
|
311
|
+
# size via copies with mutations or crossovers. An optional second
|
312
|
+
# argument is a collection of parameters that can override various
|
313
|
+
# options (e.g. the probability of survival).
|
314
|
+
|
315
|
+
def evolve(p, param={})
|
316
|
+
debug = param[:trace] ? true : false
|
317
|
+
pcross = param[:pcross] ? param[:pcross] : 0.25
|
318
|
+
plotci = param[:plotci]
|
319
|
+
|
320
|
+
p.sort! { |x,y| x.cost <=> y.cost }
|
321
|
+
i = 0
|
322
|
+
n = p.length
|
323
|
+
|
324
|
+
if plotci
|
325
|
+
m = 0.0
|
326
|
+
p.each { |t| m += t.cost }
|
327
|
+
m = m / p.length
|
328
|
+
printf "%.2f %.2f %.2f\n", m, p[0].cost, p[-1].cost
|
329
|
+
end
|
330
|
+
|
331
|
+
# phase 1 -- delete random tours
|
332
|
+
|
333
|
+
while i < p.length
|
334
|
+
pk = (n-i).to_f / n
|
335
|
+
if rand < pk
|
336
|
+
puts "keep #{p[i]}" if debug
|
337
|
+
i += 1 # keep this tour, skip to next one
|
338
|
+
else
|
339
|
+
puts "zap #{p[i]}" if debug
|
340
|
+
p.delete_at(i) # zap this one; keep i at same value
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# phase 2 -- build back up to n tours; prev is the index of the last
|
345
|
+
# tour from the previous generation (candidates for cloning/crossing)
|
346
|
+
|
347
|
+
best = p[0]
|
348
|
+
prev = p.length
|
349
|
+
|
350
|
+
while p.length < n
|
351
|
+
mom = p[rand(prev)]
|
352
|
+
if rand < pcross
|
353
|
+
dad = p[rand(prev)]
|
354
|
+
kid = Tour.reproduce(mom,dad)
|
355
|
+
puts "#{mom} x #{dad} => #{kid}" if debug
|
356
|
+
else
|
357
|
+
kid = Tour.reproduce(mom)
|
358
|
+
puts "#{mom} => #{kid}" if debug
|
359
|
+
end
|
360
|
+
p.push(kid)
|
361
|
+
best = kid if kid.cost < best.cost
|
362
|
+
end
|
363
|
+
|
364
|
+
return best
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
# High level interface -- make a population for a set of cities, then
|
369
|
+
# call evolve() until the best tour doesn't change after some number of
|
370
|
+
# iterations (also passed as a parameter). Print the tour when done.
|
371
|
+
|
372
|
+
def bestTour(matrix, param={})
|
373
|
+
popsize = param[:popsize] ? param[:popsize] : 10
|
374
|
+
maxgen = param[:maxgen] ? param[:maxgen] : 25
|
375
|
+
maxstatic = param[:maxstatic] ? param[:maxstatic] : maxgen/2
|
376
|
+
|
377
|
+
p = initPopulation(popsize,matrix)
|
378
|
+
best = p[0]
|
379
|
+
nstatic = 0
|
380
|
+
ngen = 0
|
381
|
+
|
382
|
+
if param[:verbose]
|
383
|
+
puts "popsize: #{popsize}"
|
384
|
+
puts "maxgen: #{maxgen}"
|
385
|
+
puts "maxstatic: #{maxstatic}"
|
386
|
+
puts "pcross: #{param[:pcross]}"
|
387
|
+
end
|
388
|
+
|
389
|
+
while ngen < maxgen && nstatic < maxstatic
|
390
|
+
puts "best tour: #{p[0]}" if param[:verbose]
|
391
|
+
evolve(p, param)
|
392
|
+
if p[0].cost < best.cost
|
393
|
+
best = p[0]
|
394
|
+
nstatic = 0
|
395
|
+
else
|
396
|
+
nstatic += 1
|
397
|
+
end
|
398
|
+
ngen += 1
|
399
|
+
end
|
400
|
+
|
401
|
+
# puts "#{ngen} generations (#{nstatic} at best value)"
|
402
|
+
puts "#{ngen} generations"
|
403
|
+
|
404
|
+
if param[:verbose]
|
405
|
+
puts "best cost: #{best.cost}"
|
406
|
+
puts "pedigree: #{best.nx} cross #{best.nm} mutate"
|
407
|
+
puts "best tour:"
|
408
|
+
best.path.each_byte do |x|
|
409
|
+
puts " " + matrix[x]
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
return best
|
414
|
+
end
|
415
|
+
|
416
|
+
|
data/lib/viewer.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
=begin rdoc
|
3
|
+
|
4
|
+
== Viewer
|
5
|
+
|
6
|
+
A module for displaying graphical views of objects created in lab projects. A view
|
7
|
+
is a simple drawing canvas with no controls -- objects are drawn when students evaluate
|
8
|
+
expressions in an IRB session.
|
9
|
+
|
10
|
+
The communication between IRB and the canvas is mediated by a Linda tuple-space. When
|
11
|
+
this module is included in an IRB session it launches a Ruby program named 'bb.rb', which
|
12
|
+
implements a Rinda tuplespace, and then launches a viewer program that will extract
|
13
|
+
tuples that describe objects and draw representations of those objects. There are
|
14
|
+
different viewers for each lab, e.g. nbview.rb is the N-body project viewer that draws
|
15
|
+
circles to show the positions of planets.
|
16
|
+
|
17
|
+
This organization is pretty cumbersome, but it appears to be the best/only way to control
|
18
|
+
a graphical interface from an IRB session. Ideally IRB could create a canvas from a GUI
|
19
|
+
library like Tk or Wx, but the problem is that these libraries only update their widgets
|
20
|
+
from a top-level event loop. In Ruby 1.8, without native threads, the event loop doesn't
|
21
|
+
give control back to IRB. Launching a Wx-based viewer as a separate application and
|
22
|
+
communicating with it via Linda is working, but is not very responsive. For one-way
|
23
|
+
communication it seems to be adequate.
|
24
|
+
|
25
|
+
The code assumes the module is loaded by a require statement in a lab method that is called
|
26
|
+
to initialize a view for that lab. Since the code is loaded by a require the module is
|
27
|
+
loaded only once. Statements in the main body of the module set up the bulletin board
|
28
|
+
and do other one-time intializations.
|
29
|
+
|
30
|
+
=end
|
31
|
+
|
32
|
+
require 'drb/drb'
|
33
|
+
require 'rinda/tuplespace'
|
34
|
+
|
35
|
+
module RubyLabs
|
36
|
+
|
37
|
+
module Viewer
|
38
|
+
|
39
|
+
# Code to execute when the module is loaded (assumed to happen only once):
|
40
|
+
|
41
|
+
raise "for interactive use only" unless defined? IRB
|
42
|
+
|
43
|
+
DRb.start_service # ? was after launch of bb.rb
|
44
|
+
|
45
|
+
@@uri = "druby://localhost:53783"
|
46
|
+
@@bindir = File.join(File.dirname(__FILE__), '..', 'bin')
|
47
|
+
@@bb = IO.popen("#{@@bindir}/bb.rb")
|
48
|
+
@@ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, @@uri))
|
49
|
+
|
50
|
+
at_exit do
|
51
|
+
Process.kill(9, @@bb.pid)
|
52
|
+
Process.kill(9, @@viewer.pid)
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_tuple(tag, args)
|
56
|
+
puts "RubyLabs::Viewer::write_tuple #{tag} #{args} #{@@uri}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def launch_viewer(name)
|
60
|
+
@@viewer = IO.popen("#{@@bindir}/#{name} #{@@uri}")
|
61
|
+
end
|
62
|
+
|
63
|
+
end # Viewer
|
64
|
+
|
65
|
+
end # RubyLabs
|
data/test/bit_test.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
include BitLab
|
5
|
+
|
6
|
+
class TestBits < Test::Unit::TestCase
|
7
|
+
|
8
|
+
# Make some Code objects
|
9
|
+
|
10
|
+
def test_01_codes
|
11
|
+
print "\n codes"
|
12
|
+
|
13
|
+
c0 = 10.code
|
14
|
+
assert_equal c0.to_s, "1010"
|
15
|
+
assert_equal c0.length, 4
|
16
|
+
assert_equal c0.value, 10
|
17
|
+
assert_equal c0.base, :binary
|
18
|
+
|
19
|
+
c1 = 10.code(6)
|
20
|
+
assert_equal c1.to_s, "001010"
|
21
|
+
assert_equal c1.length, 6
|
22
|
+
assert_equal c1.value, 10
|
23
|
+
assert_equal c1.base, :binary
|
24
|
+
|
25
|
+
c2 = 10.code(:hex)
|
26
|
+
assert_equal c2.to_s, "A"
|
27
|
+
assert_equal c2.length, 4
|
28
|
+
assert_equal c2.value, 10
|
29
|
+
assert_equal c2.base, :hex
|
30
|
+
|
31
|
+
c3 = 10.code(:hex, 2)
|
32
|
+
assert_equal c3.to_s, "0A"
|
33
|
+
assert_equal c3.length, 8
|
34
|
+
assert_equal c3.value, 10
|
35
|
+
assert_equal c3.base, :hex
|
36
|
+
end
|
37
|
+
|
38
|
+
# Parity bit tests
|
39
|
+
|
40
|
+
def test_02_parity
|
41
|
+
print "\n parity"
|
42
|
+
|
43
|
+
a = ?A.code(8)
|
44
|
+
assert_equal a.chr, "A"
|
45
|
+
assert a.even_parity?
|
46
|
+
assert_equal a.parity_bit, 0
|
47
|
+
|
48
|
+
a.add_parity_bit
|
49
|
+
assert_equal a.length, 9
|
50
|
+
assert a.even_parity?
|
51
|
+
|
52
|
+
c = ?C.code(8)
|
53
|
+
assert_equal c.chr, "C"
|
54
|
+
assert ! c.even_parity?
|
55
|
+
assert_equal c.parity_bit, 1
|
56
|
+
|
57
|
+
c.add_parity_bit
|
58
|
+
assert_equal c.length, 9
|
59
|
+
assert c.even_parity?
|
60
|
+
|
61
|
+
c.flip(7)
|
62
|
+
assert_equal c.to_s, "010000101"
|
63
|
+
assert ! c.even_parity?
|
64
|
+
end
|
65
|
+
|
66
|
+
# Make an encoding scheme for 5 items -- we should get 5 binary codes from 000 to 100
|
67
|
+
|
68
|
+
def test_03_make_codes
|
69
|
+
print "\n make_codes"
|
70
|
+
|
71
|
+
a = RandomArray.new(:cars, 5)
|
72
|
+
assert_equal a.length, 5
|
73
|
+
|
74
|
+
c = make_codes(a)
|
75
|
+
assert_equal c.length, 5
|
76
|
+
|
77
|
+
codes = c.values.sort
|
78
|
+
assert_equal codes[0].length, 3
|
79
|
+
assert_equal codes[0].value, 0
|
80
|
+
assert_equal codes[-1].value, 4
|
81
|
+
end
|
82
|
+
|
83
|
+
# Make some Message objects
|
84
|
+
|
85
|
+
def test_04_messages
|
86
|
+
print "\n messages"
|
87
|
+
|
88
|
+
s = "hello"
|
89
|
+
|
90
|
+
msg1 = encode(s, :ascii)
|
91
|
+
assert_equal msg1.length, 8 * s.length
|
92
|
+
|
93
|
+
msg2 = encode(s, :parity)
|
94
|
+
assert_equal msg2.length, 9 * s.length
|
95
|
+
|
96
|
+
bang = ?!.code(8)
|
97
|
+
msg1 << bang
|
98
|
+
assert_equal decode(msg1, :ascii), s + "!"
|
99
|
+
|
100
|
+
bang.add_parity_bit
|
101
|
+
msg2 << bang
|
102
|
+
assert_equal decode(msg2, :parity), s + "!"
|
103
|
+
|
104
|
+
msg3 = msg1.copy
|
105
|
+
assert_not_equal msg3.object_id, msg1.object_id
|
106
|
+
msg3 << bang
|
107
|
+
assert msg3.length > msg1.length
|
108
|
+
end
|
109
|
+
|
110
|
+
# Tree nodes
|
111
|
+
|
112
|
+
def test_05_nodes
|
113
|
+
print "\n nodes"
|
114
|
+
|
115
|
+
n0 = Node.new("A", 0.2)
|
116
|
+
assert_equal n0.char, "A"
|
117
|
+
assert_equal n0.freq, 0.2
|
118
|
+
assert_nil n0.left
|
119
|
+
assert_nil n0.right
|
120
|
+
assert n0.leaf?
|
121
|
+
|
122
|
+
n1 = Node.new("B", 0.4)
|
123
|
+
n2 = Node.combine(n0, n1)
|
124
|
+
assert_nil n2.char
|
125
|
+
assert_equal n2.freq, n0.freq + n1.freq
|
126
|
+
assert_equal n2.left, n0
|
127
|
+
assert_equal n2.right, n1
|
128
|
+
assert ! n2.leaf?
|
129
|
+
end
|
130
|
+
|
131
|
+
# Priority queue
|
132
|
+
|
133
|
+
def test_06_priority_queue
|
134
|
+
print "\n priority queue"
|
135
|
+
|
136
|
+
n0 = Node.new("A", 0.2)
|
137
|
+
n1 = Node.new("B", 0.4)
|
138
|
+
n2 = Node.new("C", 0.3)
|
139
|
+
|
140
|
+
pq = PriorityQueue.new
|
141
|
+
[n0,n1,n2].each { |x| pq << x }
|
142
|
+
|
143
|
+
assert_equal pq.length, 3
|
144
|
+
assert_equal pq.first, n0
|
145
|
+
assert_equal pq.last, n1
|
146
|
+
|
147
|
+
pq << Node.combine(pq.shift, pq.shift)
|
148
|
+
assert_equal pq.length, 2
|
149
|
+
assert_equal pq.first, n1
|
150
|
+
assert_equal pq.last.freq, n0.freq + n2.freq
|
151
|
+
assert ! pq.last.leaf?
|
152
|
+
end
|
153
|
+
|
154
|
+
# Huffman tree and encoding
|
155
|
+
|
156
|
+
def test_07_huffman
|
157
|
+
print "\n Huffman tree"
|
158
|
+
|
159
|
+
data = File.join(File.dirname(__FILE__), '..', 'data')
|
160
|
+
freq = read_frequencies("#{data}/hafreq.txt")
|
161
|
+
|
162
|
+
tree = build_tree(freq)
|
163
|
+
assert_equal tree.freq, 1.0
|
164
|
+
|
165
|
+
codes = assign_codes(tree)
|
166
|
+
assert_equal codes["A"].to_s, "10"
|
167
|
+
assert_equal codes["W"].to_s, "110010"
|
168
|
+
|
169
|
+
msg = encode("ALOHA", tree)
|
170
|
+
|
171
|
+
assert_equal msg.to_s, "100000010000110"
|
172
|
+
assert_equal decode(msg, tree), "ALOHA"
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
include EncryptionLab
|
5
|
+
|
6
|
+
class TestEncryption < Test::Unit::TestCase
|
7
|
+
|
8
|
+
# Caesar cypher
|
9
|
+
|
10
|
+
def test_01_caesar
|
11
|
+
print "\n Caesar cypher"
|
12
|
+
assert_equal caesar("abcdefghijklmnopqrstuvwxyz"), "defghijklmnopqrstuvwxyzabc"
|
13
|
+
assert_equal caesar("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), "DEFGHIJKLMNOPQRSTUVWXYZABC"
|
14
|
+
assert_equal caesar("abcdefghijklmnopqrstuvwxyz", 13), "nopqrstuvwxyzabcdefghijklm"
|
15
|
+
assert_equal caesar("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13), "NOPQRSTUVWXYZABCDEFGHIJKLM"
|
16
|
+
assert_equal caesar("Et tu, Brute?"), "Hw wx, Euxwh?"
|
17
|
+
assert_equal caesar("Et tu, Brute?", 13), "Rg gh, Oehgr?"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
include IterationLab
|
5
|
+
|
6
|
+
class TestIterativeAlgoritms < Test::Unit::TestCase
|
7
|
+
|
8
|
+
# Test the contains? method (our version of include?)
|
9
|
+
|
10
|
+
def test_01_contains
|
11
|
+
print "\n contains?"
|
12
|
+
a = ["fee", "fie", "foe", "fum"]
|
13
|
+
assert contains?(a, "fee")
|
14
|
+
assert contains?(a, "foe")
|
15
|
+
assert contains?(a, "fum")
|
16
|
+
assert !contains?(a, "foo")
|
17
|
+
end
|
18
|
+
|
19
|
+
# Same as above, but using the location method (our version of index)
|
20
|
+
|
21
|
+
def test_02_search
|
22
|
+
print "\n search"
|
23
|
+
a = ["fee", "fie", "foe", "fum"]
|
24
|
+
assert_equal 0, search(a, "fee")
|
25
|
+
assert_equal 2, search(a, "foe")
|
26
|
+
assert_equal 3, search(a, "fum")
|
27
|
+
assert_nil search(a, "foo")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Make some test arrays, sort them
|
31
|
+
|
32
|
+
def test_02_msort
|
33
|
+
print "\n msort"
|
34
|
+
[15, 16, 17].each do |size|
|
35
|
+
a = TestArray.new(size)
|
36
|
+
assert_equal isort(a), a.sort
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|