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.
Files changed (58) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +9 -0
  3. data/Rakefile +63 -0
  4. data/VERSION +1 -0
  5. data/bin/bb.rb +12 -0
  6. data/bin/statistics2-0.53/ext/extconf.rb +11 -0
  7. data/bin/statistics2-0.53/ext/show.rb +11 -0
  8. data/bin/statistics2-0.53/ext/t.rb +46 -0
  9. data/bin/statistics2-0.53/mklist.rb +26 -0
  10. data/bin/statistics2-0.53/sample-tbl.rb +129 -0
  11. data/bin/statistics2-0.53/statistics2.rb +532 -0
  12. data/bin/statistics2-0.53/t-inv.rb +54 -0
  13. data/bin/statistics2.rb +532 -0
  14. data/data/aafreq.txt +20 -0
  15. data/data/cars.txt +50 -0
  16. data/data/century.txt +1 -0
  17. data/data/colors.txt +64 -0
  18. data/data/earth.yaml +15 -0
  19. data/data/fruit.txt +45 -0
  20. data/data/hacodes.txt +35 -0
  21. data/data/hafreq.txt +16 -0
  22. data/data/hvfreq.txt +5 -0
  23. data/data/nbody.R +23 -0
  24. data/data/nbody.out +731 -0
  25. data/data/nbody.pdf +3111 -0
  26. data/data/nbody.png +0 -0
  27. data/data/nbody3d.pdf +3201 -0
  28. data/data/outer.pdf +182785 -0
  29. data/data/solar.dat +36501 -0
  30. data/data/solarsystem.txt +17 -0
  31. data/data/suits.txt +1 -0
  32. data/data/wordlist.txt +210653 -0
  33. data/lib/bitlab.rb +624 -0
  34. data/lib/elizalab.rb +523 -0
  35. data/lib/encryptionlab.rb +42 -0
  36. data/lib/hashlab.rb +224 -0
  37. data/lib/introlab.rb +14 -0
  38. data/lib/iterationlab.rb +130 -0
  39. data/lib/randomlab.rb +294 -0
  40. data/lib/recursionlab.rb +228 -0
  41. data/lib/rubylabs.rb +507 -0
  42. data/lib/sievelab.rb +58 -0
  43. data/lib/sortlab.rb +213 -0
  44. data/lib/spherelab.rb +352 -0
  45. data/lib/temps.rb +41 -0
  46. data/lib/tsplab.rb +416 -0
  47. data/lib/viewer.rb +65 -0
  48. data/test/bit_test.rb +175 -0
  49. data/test/encryption_test.rb +20 -0
  50. data/test/iteration_test.rb +40 -0
  51. data/test/random_test.rb +64 -0
  52. data/test/recursion_test.rb +47 -0
  53. data/test/rubylabs_test.rb +18 -0
  54. data/test/sieve_test.rb +28 -0
  55. data/test/sphere_test.rb +130 -0
  56. data/test/temps_test.rb +24 -0
  57. data/test/test_helper.rb +18 -0
  58. 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