rubylabs 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/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
-