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/bitlab.rb CHANGED
@@ -1,23 +1,60 @@
1
+ module RubyLabs
2
+
1
3
  =begin rdoc
2
4
 
3
5
  == BitLab
4
6
 
5
- Classes used in experiments on binary representations, including Huffman trees.
6
-
7
+ The BitLab module has definitions of classes and methods used in the projects for Chapter 7
8
+ of <em>Explorations in Computing</em>. The module has methods used to experiment
9
+ with binary codes, including fixed-width codes and Huffman codes. Methods create
10
+ messages by encoding strings, decode messages back to the original text, and run experiments
11
+ that introduce errors into messages and test for errors with simple parity checks.
12
+
13
+ The file bitlab.rb also adds a method to the PriorityQueue class, which is defined in
14
+ the top level RubyLabs module. The methods that insert and remove items have a "hook" that can be called called when
15
+ when the queue is being displayed on the canvas.
16
+ The +update+ method defined here will move nodes of a Huffman tree around on the screen when
17
+ they are removed from or added to the queue.
7
18
  =end
8
19
 
9
- module RubyLabs
10
-
11
20
  module BitLab
12
21
 
13
22
  QueueView = Struct.new(:queue, :options)
14
23
  NodeView = Struct.new(:circle, :text, :ftext, :lseg, :rseg)
15
24
  NodeCoords = Struct.new(:x, :y, :leftedge, :leftdepth, :rightedge, :rightdepth)
16
25
 
17
- =begin rdoc
18
- Make a unique binary code for each item in array +a+, returning a Hash that
19
- associates each item with its code.
20
- =end
26
+ # def test_setup
27
+ # vf = read_frequencies(:hvfreq)
28
+ # pq = init_queue(vf)
29
+ # view_queue(pq)
30
+ # return pq
31
+ # end
32
+
33
+ # Options for drawing trees on the canvas:
34
+
35
+ @@unit = 24 # pixels per "tree unit"
36
+
37
+ @@queueViewOptions = {
38
+ :width => 42 * @@unit,
39
+ :height => 15 * @@unit,
40
+ :qy => 50,
41
+ :qx => 50,
42
+ }
43
+
44
+ # Data directory for BitLab:
45
+
46
+ @@bitsDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'huffman')
47
+
48
+ # Make a unique binary code for each item in array +a+, returning a Hash that
49
+ # associates each item with a unique Code object. The codes are fixed-size
50
+ # binary codes, with a number of bits that depends on the number of items in +a+.
51
+ #
52
+ # Example -- make a 2-bit encoding for each of the 4 objects in this array:
53
+ #
54
+ # >> nt_code = make_codes( ["a", "t", "c", "g"] )
55
+ # => {"a"=>00, "c"=>10, "g"=>11, "t"=>01}
56
+ # >> nt_code["c"]
57
+ # => 10
21
58
 
22
59
  def make_codes(a)
23
60
  n = log2(a.length).ceil
@@ -28,20 +65,37 @@ module BitLab
28
65
  return res
29
66
  end
30
67
 
31
- =begin rdoc
32
- Print the codes in +a+ (an associative array made by +make_codes+ or the
33
- Huffman tree +assign_codes+ method). An option specifies how to order the
34
- output, either +:by_code+ or +:by_name+.
35
- =end
36
-
68
+ # Print the codes in +a+, which should be an associative array made by +make_codes+ or
69
+ # +assign_codes+, the method that creates binary codes for a Huffman tree.
70
+ # An option specifies how to order the
71
+ # output, either <tt>:by_code</tt> (the default) or <tt>:by_name</tt>.
72
+ #
73
+ # Example:
74
+ # >> nt_code
75
+ # => {"a"=>00, "c"=>10, "g"=>11, "t"=>01}
76
+ #
77
+ # >> print_codes(nt_code)
78
+ # 00 a
79
+ # 01 t
80
+ # 10 c
81
+ # 11 g
82
+ # => true
83
+ #
84
+ # >> print_codes(nt_code, :by_name)
85
+ # a 00
86
+ # c 10
87
+ # g 11
88
+ # t 01
89
+ # => true
90
+
37
91
  def print_codes(a, mode = :by_code)
38
92
  if mode == :by_code
39
93
  a.sort { |x,y| x[1] <=> y[1] }.each do |sym, code|
40
94
  printf "%s %s\n", code, sym
41
95
  end
42
96
  elsif mode == :by_name
43
- width = a.keys.map{ |x| x.length }.max
44
- a.keys.sort.each do |x|
97
+ width = a.keys.map{ |x| x.to_s.length }.max
98
+ a.keys.sort { |x,y| x.to_s <=> y.to_s }.each do |x|
45
99
  printf "%-#{width}s %s\n", x, a[x]
46
100
  end
47
101
  else
@@ -50,15 +104,21 @@ module BitLab
50
104
  return true
51
105
  end
52
106
 
53
- =begin rdoc
54
- Make a Message object for the characters in string s. The second
55
- parameter determines the code to use. It can be :ascii, :parity, a hash
56
- object that has code mappings for letters, or a Huffman tree. If a hash
57
- or tree is passed, the message type is set to :packed, otherwise it's :unpacked.
58
- The encoding type is saved so it can be used later in decoding (but see note in
59
- documentation of +decode+).
60
- =end
61
-
107
+ # Make a Message object for the characters in string +s+. The second
108
+ # parameter determines the code to use. It can be <tt>:ascii</tt>, <tt>:parity</tt>, a hash
109
+ # object that has code mappings for letters, or a Huffman tree. If a hash
110
+ # or tree is passed, the message type is set to <tt>:packed</tt>, otherwise it's <tt>:unpacked</tt>.
111
+ # The encoding type is saved so it can be used later in decoding.
112
+ #
113
+ # Example:
114
+ # >> encode("atg", :ascii)
115
+ # => 01100001 01110100 01100111
116
+ #
117
+ # >> nt_code
118
+ # => {"a"=>00, "c"=>10, "g"=>11, "t"=>01}
119
+ # >> encode("atg", nt_code)
120
+ # => 000111
121
+
62
122
  def encode(s, type, opt = nil)
63
123
  if (type.class == Hash || type.class == Node)
64
124
  code = (type.class == Node) ? assign_codes(type) : type
@@ -76,33 +136,41 @@ module BitLab
76
136
  printf("%s: %s\n", ch.chr, code) if opt == :trace
77
137
  end
78
138
  end
79
- msg.encoding = type
80
139
  return msg
81
140
  end
82
141
 
83
- =begin rdoc
84
- Decode a message using the specified decoding scheme.
85
-
86
- Note: see also the +decode+ method in the Message class, which assembles
87
- a string using the coding scheme specified when the message was created.
88
-
89
- +Message#decode+ always gives the right result -- the point of the method
90
- here is to show what happens if the decoding scheme does not match the
91
- encoding scheme.
92
- =end
93
-
94
- def decode(m, type)
142
+ # Decode a sequence of bits (represented by Message object +m+) using the
143
+ # specified decoding scheme.
144
+ #
145
+ # Note: if the decoding scheme is not the same as the one used to create
146
+ # the message the results are unpredictable.
147
+ #
148
+ # See also Message#decode, which generates a string using the coding scheme
149
+ # specified when the message was created. Message#decode always gives the
150
+ # right result -- the point of this stand-alone decode method
151
+ # is to show what happens if the decoding scheme does not match the
152
+ # encoding scheme.
153
+ #
154
+ # Example:
155
+ # >> msg = encode("aloha", :ascii)
156
+ # => 01100001 01101100 01101111 01101000 01100001
157
+ # >> decode(msg, :ascii)
158
+ # => "aloha"
159
+ # >> decode(msg, :parity)
160
+ # => "?67??"
161
+
162
+ def decode(m, scheme)
95
163
  raise "not a message" unless m.class == Message
96
164
  res = ""
97
- if type.class == Node # weird -- it appears case labels can't be class names...
98
- res = huffman_decode(m, type)
99
- elsif type.class == Code
165
+ if scheme.class == Node
166
+ res = huffman_decode(m, scheme)
167
+ elsif scheme.class == Hash
100
168
  raise "packed decode not implemented"
101
- elsif type == :ascii
169
+ elsif scheme == :ascii
102
170
  m.array.each do |x|
103
171
  res << x.value.chr
104
172
  end
105
- elsif type == :parity
173
+ elsif scheme == :parity
106
174
  m.array.each do |x|
107
175
  if x.even_parity?
108
176
  res << (x.value >> 1).chr
@@ -113,16 +181,23 @@ module BitLab
113
181
  end
114
182
  end
115
183
  else
116
- raise "unknown option: #{type}"
184
+ raise "unknown scheme: #{scheme}"
117
185
  end
118
186
  return res
119
187
  end
120
188
 
121
- =begin rdoc
122
- Simulate transmission of message +m+, adding +n+ random errors. Returns a
123
- copy of the Message object after making +n+ calls to the +flip+ method.
124
- =end
125
-
189
+ # Simulate noisy transmission of a message represented by a Message object +m+.
190
+ # Returns a copy of +m+ after making +n+ calls to the +flip+ method (which changes
191
+ # a random bit in the message).
192
+ #
193
+ # Example:
194
+ # >> msg = encode("hola", :ascii)
195
+ # => 01101000 01101111 01101100 01100001
196
+ # >> recvd = garbled(msg, 3)
197
+ # => 01001000 01000111 01101100 01100001
198
+ # >> decode(recvd, :ascii)
199
+ # => "HGla"
200
+
126
201
  def garbled(m, n)
127
202
  res = m.copy
128
203
  n.times do
@@ -132,16 +207,20 @@ module BitLab
132
207
  return res
133
208
  end
134
209
 
135
-
136
- =begin rdoc
137
- Huffman tree interface: Read letters and frequencies from file +fn+, save them in a hash indexed
138
- by letter name
139
- =end
210
+ # Read letters and frequencies from file +fn+, save them in a hash indexed by letter name.
211
+ # The hash can be passed to build_tree, the method that creates a Huffman tree from
212
+ # a set of letter frequencies. If +fn+ is a symbol it should be the name of one of the
213
+ # letter frequency files included with RubyLabs; if it's a string is should be the name
214
+ # of a file in the current working directory.
215
+ #
216
+ # Example:
217
+ # >> read_frequencies( :hvfreq )
218
+ # => {"A"=>0.45, "O"=>0.18, "E"=>0.12, "I"=>0.15, "U"=>0.1}
140
219
 
141
220
  def read_frequencies(fn)
142
221
  a = Hash.new
143
- if fn.class == Symbol
144
- fn = File.join(@@bitsDirectory, fn.to_s + ".txt")
222
+ if fn.class == Symbol
223
+ fn = File.join(@@bitsDirectory, fn.to_s + ".txt")
145
224
  end
146
225
  File.open(fn).each do |line|
147
226
  line.chomp!
@@ -154,11 +233,18 @@ module BitLab
154
233
  return a
155
234
  end
156
235
 
157
- =begin rdoc
158
- Huffman tree interface: Build a tree using frequencies in Hash +f+.
159
- =end
160
-
161
- # :begin :build_tree
236
+ # Build a Huffane tree using frequencies in hash +f+ (typically created
237
+ # by calling read_frequencies). The return value is a Node object that
238
+ # represents the root of the tree.
239
+ #
240
+ # Example:
241
+ # >> f = read_frequencies( :hvfreq )
242
+ # => {"A"=>0.45, "O"=>0.18, "E"=>0.12, "I"=>0.15, "U"=>0.1}
243
+ # >> build_tree(f)
244
+ # => ( 1.000 ( A: 0.450 ) ( ... ) )
245
+ #
246
+ #--
247
+ # :begin :build_tree
162
248
  def build_tree(f)
163
249
  pq = init_queue(f)
164
250
 
@@ -170,18 +256,45 @@ module BitLab
170
256
 
171
257
  return pq[0]
172
258
  end
173
- # :end :build_tree
174
-
175
- =begin rdoc
176
- Huffman tree helper procedure: Traverse +tree+, making a +Code+ object for each
177
- leaf node, returning the codes in a Hash object. On recursive calls the
178
- +prefix+ parameter is the set of codes on the path so far.
179
-
180
- Students should pass a tree to +encode+, which will call this method to make the code.
181
- The same tree should also be passed to +decode+ when they want to decode a message.
182
- =end
183
-
184
- # :begin :assign_codes
259
+ # :end :build_tree
260
+
261
+ # Helper method for build_tree: initialize a priority queue with Node
262
+ # objects for each letter in hash +a+, returning the queue as the result
263
+ # of the call.
264
+ #
265
+ # Example:
266
+ # >> f
267
+ # => {"A"=>0.45, "O"=>0.18, "E"=>0.12, "I"=>0.15, "U"=>0.1}
268
+ # >> init_queue(f)
269
+ # => [( U: 0.100 ), ( E: 0.120 ), ( I: 0.150 ), ( O: 0.180 ), ( A: 0.450 )]
270
+ #--
271
+ # :begin :init_queue
272
+ def init_queue(a)
273
+ q = PriorityQueue.new
274
+ a.each do |x,f|
275
+ q << Node.new(x,f)
276
+ end
277
+ return q
278
+ end
279
+ # :end :init_queue
280
+
281
+ # Traverse a Huffman tree to make a Code object for each
282
+ # leaf node, returning the codes in a Hash object. When users call this
283
+ # method, they should pass only one argument (+tree+). On recursive calls
284
+ # the other two arguments will be the set of codes defined so far and the
285
+ # binary prefix for the path to the current node.
286
+ #
287
+ # Users normally pass a tree to +encode+, which will call this method to make the code.
288
+ # The same tree should also be passed to +decode+ to decode the message.
289
+ #
290
+ # Example:
291
+ # >> t = build_tree(f)
292
+ # => ( 1.000 ( A: 0.450 ) ( ... ) )
293
+ # >> assign_codes(t)
294
+ # => {"A"=>0, "O"=>111, "E"=>101, "I"=>110, "U"=>100}
295
+ #
296
+ #--
297
+ # :begin :assign_codes
185
298
  def assign_codes(tree, code = {}, prefix = Code.new(0,0))
186
299
  if tree.char != nil
187
300
  code[tree.char] = prefix
@@ -191,33 +304,15 @@ module BitLab
191
304
  end
192
305
  return code
193
306
  end
194
- # :end :assign_codes
195
-
196
- =begin rdoc
197
- Huffman tree helper procedure: initialize a priority queue with Node
198
- objects for each letter in Hash +a+.
199
- =end
200
-
201
- # :begin :init_queue
202
- def init_queue(a)
203
- q = PriorityQueue.new
204
- a.each do |x,f|
205
- q << Node.new(x,f)
206
- end
207
- return q
208
- end
209
- # :end :init_queue
307
+ # :end :assign_codes
210
308
 
211
- =begin rdoc
212
- Huffman tree helper procedure: decode the binary codes in message +m+, using +tree+ as a guide.
213
- Not intended to be called by students; they will use the top level +decode+ method
214
- or the +decode+ method defined in the Message class.
215
- =end
309
+ # Helper method used by decode and Message#decode: decode the binary codes in message +m+,
310
+ # using +tree+ as a guide.
216
311
 
217
312
  def huffman_decode(m, tree)
218
313
  res = ""
219
314
  path = tree
220
- m.each do |bit|
315
+ m.each_bit do |bit|
221
316
  if path.leaf?
222
317
  res << path.char
223
318
  path = tree
@@ -228,10 +323,17 @@ module BitLab
228
323
  return res
229
324
  end
230
325
 
231
- =begin rdoc
232
- Return an array of binary codes (so students
233
- can text decoding skills)
234
- =end
326
+ # The data directory BitLabs has a file named "testcodes.txt" that contains a set
327
+ # of binary encodings of Hawaiian words. Call this method to read the encodings and return
328
+ # and an array of Message objects, one for each encoding. Users can try decoding the messages
329
+ # by hand before verifying their answer by passing them to decode. The messages are ordered
330
+ # from shortest to longest.
331
+ #
332
+ # Example (assuming +t+ is the Huffman tree for the full Hawaiian alphabet):
333
+ # >> msgs = read_codes
334
+ # => [011010, ... 000101101100001100001011011000011011100110001011011100110001011010110011011010011110]
335
+ # >> decode(msgs[-1], t)
336
+ # => "HUMUHUMUNUKUNUKUAPUA'A"
235
337
 
236
338
  def read_codes
237
339
  codes = Array.new
@@ -249,65 +351,119 @@ module BitLab
249
351
  return codes
250
352
  end
251
353
 
252
- =begin rdoc
253
- Return an array of binary codes (so students
254
- can text decoding skills)
255
- =end
256
-
354
+ # The data directory for BitLabs has a file named "testwords.txt" with a few Hawaiian words.
355
+ # Call this method to read the file and return a list of strings for experiments with encoding
356
+ # Hawaiian words with a Huffman code.
357
+
257
358
  def read_words
258
359
  fn = File.join(@@bitsDirectory, "testwords.txt")
259
360
  words = File.open(fn).readlines
260
361
  return words.map { |x| x.chomp }
261
362
  end
262
363
 
263
-
264
364
  =begin rdoc
265
- Huffman tree utility: generate a random string of length +n+ using the letter frequencies +f+.
365
+ Initialize the canvas with a drawing of a priority queue.
266
366
  =end
267
367
 
268
- def generate_string(n, f)
269
- s = ""
270
- n.times do
271
- r = rand
272
- sum = 0
273
- f.each do |ch,x|
274
- sum += x
275
- if r < sum
276
- s += ch
277
- break
278
- end
279
- end
368
+ # Initialize the RubyLabs Canvas and draw a picture of priority queue +pq+.
369
+ # Future calls to the queue's <tt><<</tt> and <tt>shift</tt> methods will update the drawing.
370
+
371
+ def view_queue(pq, userOptions = {} )
372
+ options = @@queueViewOptions.merge(userOptions)
373
+ Canvas.init(options[:width], options[:height], "BitLab")
374
+ @@drawing = QueueView.new(pq, options)
375
+ options[:nodefill] = "lightgray"
376
+ options[:freqfont] = Canvas::Font.new('freqfont', :family => 'Helvetica', :size => 10)
377
+ pq.on_canvas = true
378
+ x = options[:qx]
379
+ pq.each_with_index do |node, i|
380
+ draw_node(node, x, options[:qy])
381
+ x += 3 * @@unit
280
382
  end
281
- return s
383
+ return true
282
384
  end
283
385
 
386
+ def move_tree(tree, dx, dy) # :nodoc:
387
+ tree.coords.x += dx
388
+ tree.coords.y += dy
389
+ Canvas.move(tree.drawing.circle, dx, dy)
390
+ Canvas.move(tree.drawing.text, dx, dy)
391
+ Canvas.move(tree.drawing.ftext, dx, dy) if tree.drawing.ftext
392
+ if tree.left
393
+ Canvas.move(tree.drawing.lseg, dx, dy)
394
+ move_tree(tree.left, dx, dy)
395
+ end
396
+ if tree.right
397
+ Canvas.move(tree.drawing.rseg, dx, dy)
398
+ move_tree(tree.right, dx, dy)
399
+ end
400
+ end
401
+
402
+ def draw_root(node, left, right) # :nodoc:
403
+ opts = @@drawing.options
404
+ x = (left.coords.x + right.coords.x) / 2
405
+ y = left.coords.y - 2 * @@unit
406
+ draw_node(node, x, y)
407
+ node.drawing.lseg = Canvas::Line.new(x, y, left.coords.x, left.coords.y)
408
+ node.drawing.rseg = Canvas::Line.new(x, y, right.coords.x, right.coords.y)
409
+ node.drawing.lseg.lower
410
+ node.drawing.rseg.lower
411
+ # [left, right].each do |desc|
412
+ # desc.drawing.ftext.delete
413
+ # desc.drawing.ftext = nil
414
+ # end
415
+ end
416
+
417
+ def draw_node(node, x, y) # :nodoc:
418
+ return nil unless @@drawing
419
+ opts = @@drawing.options
420
+ d = @@unit
421
+ circ = Canvas::Circle.new( x, y, d / 2, :fill => opts[:nodefill] )
422
+ text = Canvas::Text.new( node.char, x, y, :anchor => :center )
423
+ ftext = Canvas::Text.new( node.freq.to_s, x, y-d, {:font => opts[:freqfont].name, :anchor => :center} )
424
+ node.drawing = NodeView.new(circ, text, ftext, nil, nil)
425
+ node.coords = NodeCoords.new(x, y, x, 0, x, 0)
426
+ end
427
+
428
+
284
429
  =begin rdoc
285
- Class for nodes of a Huffman tree. All nodes have a frequency (+freq+) used to
286
- determine its place in a priority queue. Leaf nodes have a character (+char+).
287
- Interior nodes have a +nil+ character value, in which case +left+ and +right+
288
- are Nodes for the roots of subtrees.
289
-
290
- Use +new+ to create a new leaf node. Call the class method +combine+ (an alternative
291
- constructor) to make a new interior node from two existing nodes. If the tree is
292
- being displayed on the RubyLabs canvas, make a graphic for the new interior node, too.
293
-
294
- The +<+ method allows Nodes to be compared so they can be ordered in a priority
295
- queue.
430
+
431
+ == Node
432
+
433
+ A Node object represents a node of a Huffman tree. All nodes have an
434
+ attribute named +freq+, the frequency used to
435
+ determine the node's place in a priority queue as the tree is built.
436
+ The +char+ attribute is +nil+ for interior nodes, or the character stored at
437
+ a leaf node.
438
+ Descendants of a node can be found by calling the +left+ or +right+ accessor methods
439
+ (which return +nil+ for leaf nodes). The remaining attributes (+drawing+, +coords+, etc)
440
+ are used by methods that display a tree on the RubyLabs Canvas.
441
+
442
+ Use +new+ to create a new leaf node. Call the class method +combine+ (an alternative
443
+ constructor) to make a new interior node from two existing nodes. If the tree is
444
+ being displayed on the Canvas, a call to +combine+ will
445
+ make a graphic for the new interior node directly above its two descandants.
446
+
296
447
  =end
297
448
 
298
449
  class Node
299
450
 
300
451
  attr_accessor :freq, :char, :left, :right, :drawing, :coords, :lfchain, :rfchain, :depth
301
452
 
453
+ # Create a new leaf node for +char+ with frequency +freq+.
454
+
302
455
  def initialize(char,freq)
303
456
  @char = char
304
457
  @freq = freq
305
- @left = @right = nil
306
- @drawing = @coords = nil
307
- @lfchain = @rfchain = self
308
- @depth = 0
458
+ @left = @right = nil
459
+ @drawing = @coords = nil
460
+ @lfchain = @rfchain = self
461
+ @depth = 0
309
462
  end
310
463
 
464
+ # Create a new interior node with descendants +leftdesc+ and +rightdesc+. If the
465
+ # tree is being displayed, draw the new node above and between its two descendants.
466
+ #--
311
467
  # todo -- need to follow chains to end when updating shallower tree
312
468
 
313
469
  def Node.combine(leftdesc, rightdesc)
@@ -326,78 +482,116 @@ module BitLab
326
482
  return node
327
483
  end
328
484
 
485
+ # Compare this node and node +x+ according to their frequency values
486
+ # so they can be ordered in a priority queue.
487
+
329
488
  def <(x)
330
489
  x.class == Node && @freq < x.freq
331
490
  end
491
+
492
+ # Return +true+ if this node is a leaf (i.e. it has no descendants).
332
493
 
333
494
  def leaf?
334
495
  return @char != nil
335
496
  end
497
+
498
+ # Return a String with a concise representation of this node, including its
499
+ # character and frequency if it's a leaf, or it's frequency and two descendants
500
+ # if it's an interior node.
336
501
 
337
- def inspect
338
- if leaf?
339
- sprintf "( %s: %.3f )", @char, @freq
340
- else
341
- sprintf "( %.3f %s %s )", @freq, @left.to_s, @right.to_s
342
- end
343
- end
502
+ def inspect
503
+ if leaf?
504
+ sprintf "( %s: %.3f )", @char, @freq
505
+ else
506
+ sprintf "( %.3f %s %s )", @freq, @left.to_s, @right.to_s
507
+ end
508
+ end
344
509
 
345
510
  alias to_s inspect
346
511
 
347
512
  end # Node
348
513
 
349
514
  =begin rdoc
350
- Code objects are variable-length binary numbers representing individual letters or members
351
- of a set. The main reason to create a Code object is so to have the value of the integer
352
- displayed in binary or hex. To make it easier for advanced students to understand the
353
- Huffman tree "assign_codes" method this class defines a method that attaches a bit to the
354
- end of a code and returns a new Code object.
355
-
356
- Students will not create Code objects directly -- instead they are created by methods in
357
- other classes, e.g. the +code+ method added to Fixnum or the top level encode method.
358
-
359
- There are two methods for attaching bits: << appends a bit to a code (used by the method
360
- that makes a parity-encoded message) and +, which returns a copy of a code with the bit
361
- added (used by the recursive method that assigns Huffman codes).
362
-
363
- << can also be used to extend a code by appending a second code.
364
515
 
365
- The index operator for Fixnums orders bits from right to left, consistent with standard
366
- usage but the opposite of Strings and Arrays. In this module bits will be ordered from
367
- left to right to be consistent with Strings and Arrays. The modified ordering is used
368
- in the index operator and the +flip+ method.
369
- =end
516
+ == Code
517
+
518
+ Code objects are variable-length binary numbers representing integers, letters, or members
519
+ of a set.
520
+
521
+ In the projects described in the text readers do not create Code objects directly --
522
+ instead Codes are created by methods in
523
+ other classes, e.g. the +code+ method added to the Fixnum class or the top level +encode+ method
524
+ defined in the BitLab module.
370
525
 
371
- # TBD allow + and << on hex codes; they should have same internal rep, as binary codes, just show as hex when displaying
526
+ See also HexCode, a derived class that has the same operations and attributes but displays
527
+ values with hexadecimal (base 16) digits.
372
528
 
373
- # Way cool -- this class used to have a maxcodesize (set to 60) to make sure codes fit within
374
- # a single 64-bit word. But a typo during one experiment created an 80-bit code. Turns out
375
- # indexing etc work just fine on Bignums:
376
- #
377
- # >> c1 = s[1].code(80)
378
- # => 00000000000000000000000000000000000000000000000000000000000000000000000001101000
379
- # >> c1.flip(0)
380
- # => 10000000000000000000000000000000000000000000000000000000000000000000000001101000
529
+ #--
530
+ Way cool -- this class used to have a maxcodesize (set to 60) to make sure codes fit within
531
+ a single 64-bit word. But a typo during one experiment created an 80-bit code. Turns out
532
+ indexing etc work just fine on Bignums:
533
+
534
+ >> c1 = s[1].code(80)
535
+ => 00000000000000000000000000000000000000000000000000000000000000000000000001101000
536
+ >> c1.flip(0)
537
+ => 10000000000000000000000000000000000000000000000000000000000000000000000001101000
538
+ =end
381
539
 
382
540
  class Code
383
- attr_accessor :value, :length, :base
541
+ attr_accessor :value, :length
384
542
 
385
- def initialize(value, length = 0, base = :binary)
386
- raise "Code: unknown base: #{base}" unless [:binary, :hex].include?(base)
387
- @value = value
543
+ # Create a new code for the number +x+. An optional argument specifies the number
544
+ # of bits in the code, in which case the code will be padded with leading 0s (if the
545
+ # value is small enough to fit in that number of bits) or truncated (if the number
546
+ # is too big to fit in that number of bits).
547
+ #
548
+ # Examples:
549
+ # >> Code.new(26)
550
+ # => 11010
551
+ # >> Code.new(26, 8)
552
+ # => 00011010
553
+ # >> Code.new(26,4)
554
+ # => 1010
555
+
556
+ def initialize(x, length = log2(x+1).ceil)
557
+ @value = x % (1 << length)
388
558
  @length = length
389
- @base = base
390
559
  @has_parity_bit = false
391
560
  end
392
-
561
+
562
+ # Create a new code by copying this code and extending it by one bit. The extra bit is appended
563
+ # on the right. The argument on the right side of the <tt>+</tt> operator should be an integer;
564
+ # the bit appended to the code is the least significant bit of this number.
565
+ #
566
+ # Examples:
567
+ # >> x = Code.new(4)
568
+ # => 100
569
+ # >> x + 0
570
+ # => 1000
571
+ # >> x + 1
572
+ # => 1001
573
+
393
574
  def +(bit)
394
- raise "operation not defined for hex codes" unless @base == :binary
395
575
  val = (@value << 1) | bit[0]
396
576
  return Code.new(val, @length+1)
397
577
  end
398
578
 
579
+ # Extend this code by attaching bits in +x+ to the end of this code. The argument
580
+ # +x+ can either be an integer, in which case one bit, corresponding to the least significant
581
+ # bit of +x+, is added to the end of the this code, or another Code object, in which case
582
+ # all the bits in +x+ are added to the end of the this code.
583
+ #
584
+ # Examples:
585
+ # >> x = Code.new(4)
586
+ # => 100
587
+ # >> y = Code.new(5)
588
+ # => 101
589
+ # >> x << 1
590
+ # => 1001
591
+ # >> x << y
592
+ # => 1001101
593
+
399
594
  def <<(x)
400
- raise "operation not defined for hex codes" unless @base == :binary
401
595
  if x.class == Code
402
596
  val = x.value # val known to fit in x.length bits
403
597
  n = x.length
@@ -410,7 +604,25 @@ module BitLab
410
604
  @length += n
411
605
  return self
412
606
  end
413
-
607
+
608
+ # Return one or more bits from this code.
609
+ # If the argument passed to this method is an integer, the method returns a single integer,
610
+ # either 0 or 1.
611
+ # If the argument is a Range, the method returns a new Code object with the specified bits
612
+ # from this code.
613
+ #
614
+ # Note: The index operator for Fixnums orders bits from right to left, consistent with standard
615
+ # usage but the opposite of Strings and Arrays. In this module bits are ordered from
616
+ # left to right to be consistent with Strings and Arrays.
617
+ #
618
+ # Example:
619
+ # >> x = Code.new(117)
620
+ # => 1110101
621
+ # >> x[1]
622
+ # => 1
623
+ # >> x[1..3]
624
+ # => 110
625
+
414
626
  def [](i)
415
627
  if i.class == Range
416
628
  res = 0
@@ -421,7 +633,20 @@ module BitLab
421
633
  return @value[@length-i-1]
422
634
  end
423
635
  end
424
-
636
+
637
+ # Return the bit value that would give this code an even parity, i.e. if this code
638
+ # already has an even number of 1s return 0, if it has an odd number of 1s return 1.
639
+ #
640
+ # Example:
641
+ # >> x = Code.new(17)
642
+ # => 10001
643
+ # >> x.parity_bit
644
+ # => 0
645
+ # >> x << 1
646
+ # => 100011
647
+ # >> x.parity_bit
648
+ # => 1
649
+
425
650
  def parity_bit
426
651
  bit = 0
427
652
  for i in 0...@length
@@ -430,22 +655,47 @@ module BitLab
430
655
  return bit
431
656
  end
432
657
 
658
+ # Extend this code by adding a new bit, chosen so that this code will now have even parity.
659
+ #
660
+ # Example:
661
+ # >> x = Code.new(17)
662
+ # => 10001
663
+ # >> x.add_parity_bit
664
+ # => 100010
665
+
433
666
  def add_parity_bit
434
667
  @has_parity_bit = true
435
668
  return self << parity_bit
436
669
  end
437
670
 
671
+ # Return +true+ or +false+, depending on whether this code has an even or odd number of 1 bits.
672
+
438
673
  def even_parity?
439
674
  return parity_bit() == 0
440
675
  end
676
+
677
+ # Invert bit +i+ of this code object, where bit 0 is the leftmost bit
678
+ # (see the note about bit order in the documentation for the [] operator
679
+ # for Code objects).
680
+ #
681
+ # Example:
682
+ # >> x = Code.new(17)
683
+ # => 10001
684
+ # >> x.flip(3)
685
+ # => 10011
441
686
 
442
687
  def flip(i)
443
688
  raise "bit index out of range" unless i < @length
444
689
  @value ^= (1 << (@length - i - 1))
445
690
  return self
446
691
  end
692
+
693
+ # Return a one-letter string containing the character encoded by this Code object,
694
+ # which must have fewer than 8 bits.
695
+ # Ignores the parity bit if it has been attached.
447
696
 
448
697
  def chr
698
+ raise "code must have fewer than 8 bits" unless @value < 256
449
699
  if @has_parity_bit
450
700
  if even_parity?
451
701
  return (@value >> 1).chr
@@ -457,43 +707,94 @@ module BitLab
457
707
  end
458
708
  end
459
709
 
710
+ # Compare the numeric values of this object and Code object +x+.
711
+
460
712
  def <=>(x)
461
- return @value <=> x.value
713
+ return x.class == Code && @value <=> x.value
462
714
  end
715
+
716
+ # Return a string with the binary digits of the value represented by this Code object.
463
717
 
464
718
  def inspect
465
- return "" if @length == 0
466
- case @base
467
- when :binary
719
+ if @length == 0
720
+ return ""
721
+ else
468
722
  return sprintf "%0#{@length}b", @value
469
- when :hex
470
- return sprintf "%0#{@length/4}X", @value
471
723
  end
472
724
  end
473
725
 
474
726
  alias to_s inspect
475
727
 
476
728
  end # Code
477
-
729
+
478
730
  =begin rdoc
479
- Message objects are arrays of Codes. New codes are added by a call to <<.
480
- If the message is unpacked, each code is stored in its own array element
481
- (use this for :ascii or :parity encodings). If the message is packed,
482
- codes are repackaged into 8-bit codes, and individual codes may cross
483
- array item boundaries.
484
-
485
- The +each+ method iterates over each bit in a message. +copy+ makes a
486
- deep copy, copying each code object (but sharing a reference to +encoding+).
731
+
732
+ == HexCode
733
+
734
+ HexCodes are Code objects that are printed in hexadecimal. All of the attributes
735
+ and methods of the Code class are applicable to HexCodes -- the only differences
736
+ are in <tt>to_s</tt>, which generates the string of digits used to print a number,
737
+ and in the method that compares objects (Codes can only be compared to other Codes,
738
+ HexCodes to other HexCodes).
739
+
487
740
  =end
741
+
742
+ class HexCode < Code
743
+
744
+ # Create a new HexCode object for the number +x+. The optional second
745
+ # argument specifies the number of bits to use in the encoding.
746
+
747
+ def initialize(x, length = log2(x+1).ceil)
748
+ super
749
+ end
750
+
751
+ # Compare the numeric values of this object and HexCode object +x+.
752
+
753
+ def <=>(x)
754
+ return x.class == HexCode && @value <=> x.value
755
+ end
756
+
757
+ # Return a string with the hexadecimal digits of the value represented by this Code object.
488
758
 
489
- # TODO: allow for the possibility that a code word being added to a packed code can be longer than 8 bits
759
+ def inspect
760
+ if @length == 0
761
+ return ""
762
+ else
763
+ return sprintf "%0#{@length/4}X", @value
764
+ end
765
+ end
766
+
767
+ alias to_s inspect
768
+
769
+ end
770
+
771
+ =begin rdoc
772
+
773
+ == Message
774
+
775
+ Message objects are arrays of Code objects.
776
+ There are two types of messages: packed and unpacked.
777
+ If the message is unpacked, each code is stored in its own array element.
778
+ This is the standard form for messages created with the
779
+ <tt>:ascii</tt> or <tt>:parity</tt> encodings.
780
+ If the message is packed,
781
+ codes are repackaged into 8-bit bytes, and individual codes may cross
782
+ array item boundaries. This form is used by the method that uses a
783
+ Huffman code to encode a string.
784
+ #--
785
+ TODO: allow for the possibility that a code word being added to a packed code can be longer than 8 bits
786
+
787
+ =end
490
788
 
491
789
  class Message
492
790
 
493
- attr_accessor :packed, :array, :encoding
791
+ attr_accessor :packed, :array
494
792
 
495
793
  @@packsize = 8 # number of bits to pack into single code
496
794
 
795
+ # Create a new Message of the specified type (<tt>:packed</tt> or <tt>:unpacked</tt>).
796
+ # The message is initially empty; new characters are added by with the <tt><<</tt> operator.
797
+
497
798
  def initialize(type)
498
799
  raise "Message: unknown type" unless [:packed, :unpacked].include? type
499
800
  if type == :packed
@@ -505,14 +806,29 @@ module BitLab
505
806
  end
506
807
  end
507
808
 
809
+ # Create a new Message object that is a copy of this message.
810
+
508
811
  def copy
509
- dup = self.clone # copies @packed, @encoding
812
+ dup = self.clone # copies @packed
510
813
  dup.array = Array.new
511
814
  @array.each { |x| dup.array << x.clone } # deep copy of @array
512
815
  return dup
513
816
  end
514
817
 
515
- def each
818
+ # Iterate over each bit in a message, without regard for code boundaries.
819
+ #
820
+ # Example:
821
+ # >> m
822
+ # => 10011
823
+ # >> m.each_bit { |b| puts b }
824
+ # 1
825
+ # 0
826
+ # 0
827
+ # 1
828
+ # 1
829
+ # => 10011
830
+
831
+ def each_bit
516
832
  @array.each do |byte|
517
833
  for i in 0...byte.length
518
834
  yield(byte[i])
@@ -520,6 +836,16 @@ module BitLab
520
836
  end
521
837
  return self
522
838
  end
839
+
840
+ # Append the bits in Code object +x+ to the end of this message.
841
+ #
842
+ # Example:
843
+ # >> m = Message.new(:packed)
844
+ # =>
845
+ # >> m << Code.new(4)
846
+ # => 100
847
+ # >> m << Code.new(3)
848
+ # => 10011
523
849
 
524
850
  def <<(x)
525
851
  raise "Message#<<: not a code" unless x.class == Code
@@ -538,32 +864,34 @@ module BitLab
538
864
  return self
539
865
  end
540
866
 
541
- def decode
542
- res = ""
543
- if @encoding.class == Symbol
544
- @array.each do |code|
545
- res << code.chr
546
- end
547
- elsif @encoding.class == Node
548
- res = huffman_decode(self, @encoding)
549
- else
550
- res = unpack
551
- end
552
- return res
553
- end
867
+ # Deprecated -- use standalone BitLab#decode instead
868
+
869
+ # def decode :nodoc:
870
+ # res = ""
871
+ # if @encoding.class == Symbol
872
+ # @array.each do |code|
873
+ # res << code.chr
874
+ # end
875
+ # elsif @encoding.class == Node
876
+ # res = huffman_decode(self, @encoding)
877
+ # else
878
+ # res = unpack
879
+ # end
880
+ # return res
881
+ # end
554
882
 
555
- def unpack
556
- "unpack: not implemented"
557
- end
883
+ # def unpack
884
+ # "unpack: not implemented"
885
+ # end
558
886
 
887
+ # Return the length (in bits) of this message.
888
+
559
889
  def length
560
890
  @array.inject(0) { |sum, code| sum += code.length }
561
891
  end
562
892
 
563
- def print(option)
564
- "a message"
565
- end
566
-
893
+ # Create a string of binary digits representing the Codes in this message.
894
+
567
895
  def inspect
568
896
  if @packed
569
897
  return @array.join("")
@@ -575,106 +903,21 @@ module BitLab
575
903
  alias to_s inspect
576
904
 
577
905
  end # Message
578
-
579
- def move_tree(tree, dx, dy)
580
- tree.coords.x += dx
581
- tree.coords.y += dy
582
- Canvas.move(tree.drawing.circle, dx, dy)
583
- Canvas.move(tree.drawing.text, dx, dy)
584
- Canvas.move(tree.drawing.ftext, dx, dy) if tree.drawing.ftext
585
- if tree.left
586
- Canvas.move(tree.drawing.lseg, dx, dy)
587
- move_tree(tree.left, dx, dy)
588
- end
589
- if tree.right
590
- Canvas.move(tree.drawing.rseg, dx, dy)
591
- move_tree(tree.right, dx, dy)
592
- end
593
- end
594
-
595
- def draw_root(node, left, right)
596
- opts = @@drawing.options
597
- x = (left.coords.x + right.coords.x) / 2
598
- y = left.coords.y - 2 * @@unit
599
- draw_node(node, x, y)
600
- node.drawing.lseg = Canvas::Line.new(x, y, left.coords.x, left.coords.y)
601
- node.drawing.rseg = Canvas::Line.new(x, y, right.coords.x, right.coords.y)
602
- node.drawing.lseg.lower
603
- node.drawing.rseg.lower
604
- # [left, right].each do |desc|
605
- # desc.drawing.ftext.delete
606
- # desc.drawing.ftext = nil
607
- # end
608
- end
609
-
610
- def draw_node(node, x, y)
611
- return nil unless @@drawing
612
- opts = @@drawing.options
613
- d = @@unit
614
- circ = Canvas::Circle.new( x, y, d / 2, :fill => opts[:nodefill] )
615
- text = Canvas::Text.new( node.char, x, y, :anchor => :center )
616
- ftext = Canvas::Text.new( node.freq.to_s, x, y-d, {:font => opts[:freqfont].name, :anchor => :center} )
617
- node.drawing = NodeView.new(circ, text, ftext, nil, nil)
618
- node.coords = NodeCoords.new(x, y, x, 0, x, 0)
619
- end
620
-
621
- =begin rdoc
622
- Initialize the canvas with a drawing of a priority queue.
623
- =end
624
-
625
- def view_queue(pq, userOptions = {} )
626
- options = @@queueViewOptions.merge(userOptions)
627
- Canvas.init(options[:width], options[:height], "BitLab")
628
- @@drawing = QueueView.new(pq, options)
629
- options[:nodefill] = "lightgray"
630
- options[:freqfont] = Canvas::Font.new('freqfont', :family => 'Helvetica', :size => 10)
631
- pq.on_canvas = true
632
- x = options[:qx]
633
- pq.each_with_index do |node, i|
634
- draw_node(node, x, options[:qy])
635
- x += 3 * @@unit
636
- end
637
- return true
638
- end
639
-
640
- # def test_setup
641
- # vf = read_frequencies(:hvfreq)
642
- # pq = init_queue(vf)
643
- # view_queue(pq)
644
- # return pq
645
- # end
646
-
647
- @@unit = 24 # pixels per "tree unit"
648
-
649
- @@queueViewOptions = {
650
- :width => 42 * @@unit,
651
- :height => 15 * @@unit,
652
- :qy => 50,
653
- :qx => 50,
654
- }
655
-
656
- @@bitsDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'huffman')
657
906
 
658
907
  end # BitLab
659
908
 
660
- end # RubyLabs
661
-
662
- =begin rdoc
663
- Add an update method to the PriorityQueue so nodes are moved around on the screen when
664
- they are removed from or added to the queue (if the queue is on the canvas).
665
- Note: the << and shift methods call this method when @on_canvas is true....
666
- =end
667
-
668
- =begin
669
- TODO make time to sleep a parameter
670
- todo add comments, rdoc
671
- todo distance in units (down, up, spacing) should also be params
672
- todo clean up def of Node -- coords not being used, combine with graphics
673
- =end
674
909
  class PriorityQueue
675
910
 
676
911
  attr_accessor :on_canvas
677
912
 
913
+ # Update the drawing of the priority queue after operation +op+ has been performed
914
+ # on +node+. Operation is either <tt>:shift</tt> or <tt><<</tt>, and +node+ is a
915
+ # reference to the Node object being removed or inserted into the queue.
916
+ #
917
+ # Calls helper methods <tt>left_edge</tt>, <tt>right_edge</tt>, and <tt>tree_sep</tt>
918
+ # to figure out how to place subtrees to use the minimum amount of horizontal space
919
+ # (see bitlab.rb).
920
+
678
921
  def update(op, node)
679
922
  if op == :shift
680
923
  move_tree(node, 0, 4 * @@unit) # move subtree rooted at node down 4 units
@@ -702,7 +945,7 @@ class PriorityQueue
702
945
  end
703
946
  end
704
947
 
705
- def left_edge(tree)
948
+ def left_edge(tree) # :nodoc:
706
949
  x = tree.coords.x
707
950
  while tree.lfchain != tree
708
951
  tree = tree.lfchain
@@ -711,7 +954,7 @@ class PriorityQueue
711
954
  return x
712
955
  end
713
956
 
714
- def right_edge(tree)
957
+ def right_edge(tree) # :nodoc:
715
958
  x = tree.coords.x
716
959
  while tree.rfchain != tree
717
960
  tree = tree.rfchain
@@ -720,7 +963,7 @@ class PriorityQueue
720
963
  return x
721
964
  end
722
965
 
723
- def tree_sep(left, right)
966
+ def tree_sep(left, right) # :nodoc:
724
967
  res = right.coords.x - left.coords.x
725
968
  while (left.rfchain != left && right.lfchain != right)
726
969
  left = left.rfchain
@@ -742,19 +985,39 @@ class PriorityQueue
742
985
 
743
986
  end
744
987
 
988
+ end # RubyLabs
989
+
745
990
  class Fixnum
746
991
 
747
992
  =begin rdoc
748
- Add a method to +Fixnum+ to make a +Code+ object showing the binary or
749
- hexadecimal representation of a number. Intended mainly to show codes for
750
- characters in +String+ objects.
751
- Usage:
752
- x.code binary, size = log2 bits
753
- x.code(n) binary, size = n bits
754
- x.code(:hex) hex, size = log2 bits
755
- x.code(:hex,n) hex, size = 4*n bits
993
+
994
+ == Fixnum
995
+
996
+ Add a method to +Fixnum+ to make a +Code+ object showing the binary or
997
+ hexadecimal representation of a number. Intended mainly to show codes for
998
+ characters in +String+ objects.
756
999
  =end
757
-
1000
+
1001
+ # Create a Code object showing the binary or hexadecimal representation of
1002
+ # this number. The two arguments, both optional, define the type of
1003
+ # representation and the number of digits:
1004
+ # x.code make a binary code for x, using as many bits as necessary
1005
+ # x.code(n) make a binary code for x, using n bits
1006
+ # x.code(:hex) make a hex code for x, using as many digits as necessary
1007
+ # x.code(:hex,n) make a hex code for x, using n digits (i.e. 4*n bits)
1008
+ #
1009
+ # Example:
1010
+ # >> x = 26
1011
+ # => 26
1012
+ # >> x.code
1013
+ # => 11010
1014
+ # >> x.code(8)
1015
+ # => 00011010
1016
+ # >> x.code(:hex)
1017
+ # => 1A
1018
+ # >> x.code(:hex, 3)
1019
+ # => 01A
1020
+
758
1021
  def code(*args)
759
1022
  if args.first == :hex
760
1023
  base = args.shift
@@ -769,11 +1032,13 @@ class Fixnum
769
1032
  else
770
1033
  raise "code: can't understand #{args}"
771
1034
  end
772
- bits = (base == :hex) ? digits * 4 : digits
773
- return Code.new(self, bits, base)
1035
+ if base == :hex
1036
+ return HexCode.new(self, 4*digits)
1037
+ else
1038
+ return Code.new(self, digits)
1039
+ end
1040
+ # bits = (base == :hex) ? digits * 4 : digits
1041
+ # return Code.new(self, bits, base)
774
1042
  end
775
1043
 
776
1044
  end
777
-
778
-
779
-