module RubyLabs
=begin rdoc
== BitLab
The BitLab module has definitions of classes and methods used in the projects for Chapter 7
of Explorations in Computing. The module has methods used to experiment
with binary codes, including fixed-width codes and Huffman codes. Methods create
messages by encoding strings, decode messages back to the original text, and run experiments
that introduce errors into messages and test for errors with simple parity checks.
The file bitlab.rb also adds a method to the PriorityQueue class, which is defined in
the top level RubyLabs module. The methods that insert and remove items have a "hook" that can be called called when
when the queue is being displayed on the canvas.
The +update+ method defined here will move nodes of a Huffman tree around on the screen when
they are removed from or added to the queue.
=end
module BitLab
QueueView = Struct.new(:queue, :options)
NodeView = Struct.new(:circle, :text, :ftext, :lseg, :rseg)
NodeCoords = Struct.new(:x, :y, :leftedge, :leftdepth, :rightedge, :rightdepth)
# def test_setup
# vf = read_frequencies(:hvfreq)
# pq = init_queue(vf)
# view_queue(pq)
# return pq
# end
# Options for drawing trees on the canvas:
@@unit = 24 # pixels per "tree unit"
@@queueViewOptions = {
:width => 42 * @@unit,
:height => 15 * @@unit,
:qy => 50,
:qx => 50,
}
# Data directory for BitLab:
@@bitsDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'huffman')
# Make a unique binary code for each item in array +a+, returning a Hash that
# associates each item with a unique Code object. The codes are fixed-size
# binary codes, with a number of bits that depends on the number of items in +a+.
#
# Example -- make a 2-bit encoding for each of the 4 objects in this array:
#
# >> nt_code = make_codes( ["a", "t", "c", "g"] )
# => {"a"=>00, "c"=>10, "g"=>11, "t"=>01}
# >> nt_code["c"]
# => 10
def make_codes(a)
n = log2(a.length).ceil
res = Hash.new
a.each_with_index do |x,i|
res[x] = i.code(n)
end
return res
end
# Print the codes in +a+, which should be an associative array made by +make_codes+ or
# +assign_codes+, the method that creates binary codes for a Huffman tree.
# An option specifies how to order the
# output, either :by_code (the default) or :by_name.
#
# Example:
# >> nt_code
# => {"a"=>00, "c"=>10, "g"=>11, "t"=>01}
#
# >> print_codes(nt_code)
# 00 a
# 01 t
# 10 c
# 11 g
# => true
#
# >> print_codes(nt_code, :by_name)
# a 00
# c 10
# g 11
# t 01
# => true
def print_codes(a, mode = :by_code)
if mode == :by_code
a.sort { |x,y| x[1] <=> y[1] }.each do |sym, code|
printf "%s %s\n", code, sym
end
elsif mode == :by_name
width = a.keys.map{ |x| x.to_s.length }.max
a.keys.sort { |x,y| x.to_s <=> y.to_s }.each do |x|
printf "%-#{width}s %s\n", x, a[x]
end
else
raise "print_codes: mode must be :by_code or :by_name"
end
return true
end
# Make a Message object for the characters in string +s+. The second
# parameter determines the code to use. It can be :ascii, :parity, a hash
# object that has code mappings for letters, or a Huffman tree. If a hash
# or tree is passed, the message type is set to :packed, otherwise it's :unpacked.
# The encoding type is saved so it can be used later in decoding.
#
# Example:
# >> encode("atg", :ascii)
# => 01100001 01110100 01100111
#
# >> nt_code
# => {"a"=>00, "c"=>10, "g"=>11, "t"=>01}
# >> encode("atg", nt_code)
# => 000111
def encode(s, type, opt = nil)
if (type.class == Hash || type.class == Node)
code = (type.class == Node) ? assign_codes(type) : type
msg = Message.new(:packed)
s.each_byte do |ch|
msg << code[ch.chr]
printf("%s: %s\n", ch.chr, code[ch.chr]) if opt == :trace
end
else
msg = Message.new(:unpacked)
s.each_byte do |ch|
code = ch.code(8)
code.add_parity_bit if type == :parity
msg << code
printf("%s: %s\n", ch.chr, code) if opt == :trace
end
end
return msg
end
# Decode a sequence of bits (represented by Message object +m+) using the
# specified decoding scheme.
#
# Note: if the decoding scheme is not the same as the one used to create
# the message the results are unpredictable.
#
# See also Message#decode, which generates a string using the coding scheme
# specified when the message was created. Message#decode always gives the
# right result -- the point of this stand-alone decode method
# is to show what happens if the decoding scheme does not match the
# encoding scheme.
#
# Example:
# >> msg = encode("aloha", :ascii)
# => 01100001 01101100 01101111 01101000 01100001
# >> decode(msg, :ascii)
# => "aloha"
# >> decode(msg, :parity)
# => "?67??"
def decode(m, scheme)
raise "not a message" unless m.class == Message
res = ""
if scheme.class == Node
res = huffman_decode(m, scheme)
elsif scheme.class == Hash
raise "packed decode not implemented"
elsif scheme == :ascii
m.array.each do |x|
res << x.value.chr
end
elsif scheme == :parity
m.array.each do |x|
if x.even_parity?
res << (x.value >> 1).chr
elsif $KCODE[0] == ?U
res << "\xe2\x80\xa2"
else
res << "?"
end
end
else
raise "unknown scheme: #{scheme}"
end
return res
end
# Simulate noisy transmission of a message represented by a Message object +m+.
# Returns a copy of +m+ after making +n+ calls to the +flip+ method (which changes
# a random bit in the message).
#
# Example:
# >> msg = encode("hola", :ascii)
# => 01101000 01101111 01101100 01100001
# >> recvd = garbled(msg, 3)
# => 01001000 01000111 01101100 01100001
# >> decode(recvd, :ascii)
# => "HGla"
def garbled(m, n)
res = m.copy
n.times do
c = res.array[ rand(res.array.length) ]
c.flip( rand(c.length) )
end
return res
end
# Read letters and frequencies from file +fn+, save them in a hash indexed by letter name.
# The hash can be passed to build_tree, the method that creates a Huffman tree from
# a set of letter frequencies. If +fn+ is a symbol it should be the name of one of the
# letter frequency files included with RubyLabs; if it's a string is should be the name
# of a file in the current working directory.
#
# Example:
# >> read_frequencies( :hvfreq )
# => {"A"=>0.45, "O"=>0.18, "E"=>0.12, "I"=>0.15, "U"=>0.1}
def read_frequencies(fn)
a = Hash.new
if fn.class == Symbol
fn = File.join(@@bitsDirectory, fn.to_s + ".txt")
end
File.open(fn).each do |line|
line.chomp!
next if line.length == 0
next if line[0] == ?#
x = line[/^./]
f = line[/\d+\.\d+/].to_f
a[x] = f
end
return a
end
# Build a Huffane tree using frequencies in hash +f+ (typically created
# by calling read_frequencies). The return value is a Node object that
# represents the root of the tree.
#
# Example:
# >> f = read_frequencies( :hvfreq )
# => {"A"=>0.45, "O"=>0.18, "E"=>0.12, "I"=>0.15, "U"=>0.1}
# >> build_tree(f)
# => ( 1.000 ( A: 0.450 ) ( ... ) )
#
#--
# :begin :build_tree
def build_tree(f)
pq = init_queue(f)
while pq.length > 1
n1 = pq.shift
n2 = pq.shift
pq << Node.combine(n1, n2)
end
return pq[0]
end
# :end :build_tree
# Helper method for build_tree: initialize a priority queue with Node
# objects for each letter in hash +a+, returning the queue as the result
# of the call.
#
# Example:
# >> f
# => {"A"=>0.45, "O"=>0.18, "E"=>0.12, "I"=>0.15, "U"=>0.1}
# >> init_queue(f)
# => [( U: 0.100 ), ( E: 0.120 ), ( I: 0.150 ), ( O: 0.180 ), ( A: 0.450 )]
#--
# :begin :init_queue
def init_queue(a)
q = PriorityQueue.new
a.each do |x,f|
q << Node.new(x,f)
end
return q
end
# :end :init_queue
# Traverse a Huffman tree to make a Code object for each
# leaf node, returning the codes in a Hash object. When users call this
# method, they should pass only one argument (+tree+). On recursive calls
# the other two arguments will be the set of codes defined so far and the
# binary prefix for the path to the current node.
#
# Users normally pass a tree to +encode+, which will call this method to make the code.
# The same tree should also be passed to +decode+ to decode the message.
#
# Example:
# >> t = build_tree(f)
# => ( 1.000 ( A: 0.450 ) ( ... ) )
# >> assign_codes(t)
# => {"A"=>0, "O"=>111, "E"=>101, "I"=>110, "U"=>100}
#
#--
# :begin :assign_codes
def assign_codes(tree, code = {}, prefix = Code.new(0,0))
if tree.char != nil
code[tree.char] = prefix
else
assign_codes(tree.left, code, prefix + 0)
assign_codes(tree.right, code, prefix + 1)
end
return code
end
# :end :assign_codes
# Helper method used by decode and Message#decode: decode the binary codes in message +m+,
# using +tree+ as a guide.
def huffman_decode(m, tree)
res = ""
path = tree
m.each_bit do |bit|
if path.leaf?
res << path.char
path = tree
end
path = (bit == 0) ? path.left : path.right
end
res << path.char if path.leaf?
return res
end
# The data directory BitLabs has a file named "testcodes.txt" that contains a set
# of binary encodings of Hawaiian words. Call this method to read the encodings and return
# and an array of Message objects, one for each encoding. Users can try decoding the messages
# by hand before verifying their answer by passing them to decode. The messages are ordered
# from shortest to longest.
#
# Example (assuming +t+ is the Huffman tree for the full Hawaiian alphabet):
# >> msgs = read_codes
# => [011010, ... 000101101100001100001011011000011011100110001011011100110001011010110011011010011110]
# >> decode(msgs[-1], t)
# => "HUMUHUMUNUKUNUKUAPUA'A"
def read_codes
codes = Array.new
fn = File.join(@@bitsDirectory, "testcodes.txt")
File.open(fn).each do |line|
line.chomp!
code = Code.new(0,0)
line.each_byte do |byte|
code << byte[0] # add least significant digit of ASCII "0" or "1"
end
msg = Message.new(:packed)
msg << code
codes << msg
end
return codes
end
# The data directory for BitLabs has a file named "testwords.txt" with a few Hawaiian words.
# Call this method to read the file and return a list of strings for experiments with encoding
# Hawaiian words with a Huffman code.
def read_words
fn = File.join(@@bitsDirectory, "testwords.txt")
words = File.open(fn).readlines
return words.map { |x| x.chomp }
end
=begin rdoc
Initialize the canvas with a drawing of a priority queue.
=end
# Initialize the RubyLabs Canvas and draw a picture of priority queue +pq+.
# Future calls to the queue's << and shift methods will update the drawing.
def view_queue(pq, userOptions = {} )
options = @@queueViewOptions.merge(userOptions)
Canvas.init(options[:width], options[:height], "BitLab")
@@drawing = QueueView.new(pq, options)
options[:nodefill] = "lightgray"
options[:freqfont] = Canvas::Font.new('freqfont', :family => 'Helvetica', :size => 10)
pq.on_canvas = true
x = options[:qx]
pq.each_with_index do |node, i|
draw_node(node, x, options[:qy])
x += 3 * @@unit
end
return true
end
def move_tree(tree, dx, dy) # :nodoc:
tree.coords.x += dx
tree.coords.y += dy
Canvas.move(tree.drawing.circle, dx, dy)
Canvas.move(tree.drawing.text, dx, dy)
Canvas.move(tree.drawing.ftext, dx, dy) if tree.drawing.ftext
if tree.left
Canvas.move(tree.drawing.lseg, dx, dy)
move_tree(tree.left, dx, dy)
end
if tree.right
Canvas.move(tree.drawing.rseg, dx, dy)
move_tree(tree.right, dx, dy)
end
end
def draw_root(node, left, right) # :nodoc:
opts = @@drawing.options
x = (left.coords.x + right.coords.x) / 2
y = left.coords.y - 2 * @@unit
draw_node(node, x, y)
node.drawing.lseg = Canvas::Line.new(x, y, left.coords.x, left.coords.y)
node.drawing.rseg = Canvas::Line.new(x, y, right.coords.x, right.coords.y)
node.drawing.lseg.lower
node.drawing.rseg.lower
# [left, right].each do |desc|
# desc.drawing.ftext.delete
# desc.drawing.ftext = nil
# end
end
def draw_node(node, x, y) # :nodoc:
return nil unless @@drawing
opts = @@drawing.options
d = @@unit
circ = Canvas::Circle.new( x, y, d / 2, :fill => opts[:nodefill] )
text = Canvas::Text.new( node.char, x, y, :anchor => :center )
ftext = Canvas::Text.new( node.freq.to_s, x, y-d, {:font => opts[:freqfont].name, :anchor => :center} )
node.drawing = NodeView.new(circ, text, ftext, nil, nil)
node.coords = NodeCoords.new(x, y, x, 0, x, 0)
end
=begin rdoc
== Node
A Node object represents a node of a Huffman tree. All nodes have an
attribute named +freq+, the frequency used to
determine the node's place in a priority queue as the tree is built.
The +char+ attribute is +nil+ for interior nodes, or the character stored at
a leaf node.
Descendants of a node can be found by calling the +left+ or +right+ accessor methods
(which return +nil+ for leaf nodes). The remaining attributes (+drawing+, +coords+, etc)
are used by methods that display a tree on the RubyLabs Canvas.
Use +new+ to create a new leaf node. Call the class method +combine+ (an alternative
constructor) to make a new interior node from two existing nodes. If the tree is
being displayed on the Canvas, a call to +combine+ will
make a graphic for the new interior node directly above its two descandants.
=end
class Node
attr_accessor :freq, :char, :left, :right, :drawing, :coords, :lfchain, :rfchain, :depth
# Create a new leaf node for +char+ with frequency +freq+.
def initialize(char,freq)
@char = char
@freq = freq
@left = @right = nil
@drawing = @coords = nil
@lfchain = @rfchain = self
@depth = 0
end
# Create a new interior node with descendants +leftdesc+ and +rightdesc+. If the
# tree is being displayed, draw the new node above and between its two descendants.
#--
# todo -- need to follow chains to end when updating shallower tree
def Node.combine(leftdesc, rightdesc)
node = Node.new(nil, leftdesc.freq + rightdesc.freq)
node.left = leftdesc
node.right = rightdesc
node.depth = 1 + max(leftdesc.depth, rightdesc.depth)
node.lfchain = leftdesc
node.rfchain = rightdesc
if leftdesc.depth > rightdesc.depth
rightdesc.rfchain = leftdesc.rfchain
elsif leftdesc.depth < rightdesc.depth
leftdesc.lfchain = rightdesc.lfchain
end
draw_root(node, leftdesc, rightdesc) if (leftdesc.drawing && rightdesc.drawing)
return node
end
# Compare this node and node +x+ according to their frequency values
# so they can be ordered in a priority queue.
def <(x)
x.class == Node && @freq < x.freq
end
# Return +true+ if this node is a leaf (i.e. it has no descendants).
def leaf?
return @char != nil
end
# Return a String with a concise representation of this node, including its
# character and frequency if it's a leaf, or it's frequency and two descendants
# if it's an interior node.
def inspect
if leaf?
sprintf "( %s: %.3f )", @char, @freq
else
sprintf "( %.3f %s %s )", @freq, @left.to_s, @right.to_s
end
end
alias to_s inspect
end # Node
=begin rdoc
== Code
Code objects are variable-length binary numbers representing integers, letters, or members
of a set.
In the projects described in the text readers do not create Code objects directly --
instead Codes are created by methods in
other classes, e.g. the +code+ method added to the Fixnum class or the top level +encode+ method
defined in the BitLab module.
See also HexCode, a derived class that has the same operations and attributes but displays
values with hexadecimal (base 16) digits.
#--
Way cool -- this class used to have a maxcodesize (set to 60) to make sure codes fit within
a single 64-bit word. But a typo during one experiment created an 80-bit code. Turns out
indexing etc work just fine on Bignums:
>> c1 = s[1].code(80)
=> 00000000000000000000000000000000000000000000000000000000000000000000000001101000
>> c1.flip(0)
=> 10000000000000000000000000000000000000000000000000000000000000000000000001101000
=end
class Code
attr_accessor :value, :length
# Create a new code for the number +x+. An optional argument specifies the number
# of bits in the code, in which case the code will be padded with leading 0s (if the
# value is small enough to fit in that number of bits) or truncated (if the number
# is too big to fit in that number of bits).
#
# Examples:
# >> Code.new(26)
# => 11010
# >> Code.new(26, 8)
# => 00011010
# >> Code.new(26,4)
# => 1010
def initialize(x, length = log2(x+1).ceil)
@value = x % (1 << length)
@length = length
@has_parity_bit = false
end
# Create a new code by copying this code and extending it by one bit. The extra bit is appended
# on the right. The argument on the right side of the + operator should be an integer;
# the bit appended to the code is the least significant bit of this number.
#
# Examples:
# >> x = Code.new(4)
# => 100
# >> x + 0
# => 1000
# >> x + 1
# => 1001
def +(bit)
val = (@value << 1) | bit[0]
return Code.new(val, @length+1)
end
# Extend this code by attaching bits in +x+ to the end of this code. The argument
# +x+ can either be an integer, in which case one bit, corresponding to the least significant
# bit of +x+, is added to the end of the this code, or another Code object, in which case
# all the bits in +x+ are added to the end of the this code.
#
# Examples:
# >> x = Code.new(4)
# => 100
# >> y = Code.new(5)
# => 101
# >> x << 1
# => 1001
# >> x << y
# => 1001101
def <<(x)
if x.class == Code
val = x.value # val known to fit in x.length bits
n = x.length
else
val = x[0] # val is a single bit
n = 1
end
@value <<= n
@value |= val # val can't have any higher order bits set -- see above
@length += n
return self
end
# Return one or more bits from this code.
# If the argument passed to this method is an integer, the method returns a single integer,
# either 0 or 1.
# If the argument is a Range, the method returns a new Code object with the specified bits
# from this code.
#
# Note: The index operator for Fixnums orders bits from right to left, consistent with standard
# usage but the opposite of Strings and Arrays. In this module bits are ordered from
# left to right to be consistent with Strings and Arrays.
#
# Example:
# >> x = Code.new(117)
# => 1110101
# >> x[1]
# => 1
# >> x[1..3]
# => 110
def [](i)
if i.class == Range
res = 0
n = 0
i.each { |j| res <<= 1; res |= @value[@length-j-1]; n += 1 }
return Code.new(res, n)
else
return @value[@length-i-1]
end
end
# Return the bit value that would give this code an even parity, i.e. if this code
# already has an even number of 1s return 0, if it has an odd number of 1s return 1.
#
# Example:
# >> x = Code.new(17)
# => 10001
# >> x.parity_bit
# => 0
# >> x << 1
# => 100011
# >> x.parity_bit
# => 1
def parity_bit
bit = 0
for i in 0...@length
bit ^= @value[i]
end
return bit
end
# Extend this code by adding a new bit, chosen so that this code will now have even parity.
#
# Example:
# >> x = Code.new(17)
# => 10001
# >> x.add_parity_bit
# => 100010
def add_parity_bit
@has_parity_bit = true
return self << parity_bit
end
# Return +true+ or +false+, depending on whether this code has an even or odd number of 1 bits.
def even_parity?
return parity_bit() == 0
end
# Invert bit +i+ of this code object, where bit 0 is the leftmost bit
# (see the note about bit order in the documentation for the [] operator
# for Code objects).
#
# Example:
# >> x = Code.new(17)
# => 10001
# >> x.flip(3)
# => 10011
def flip(i)
raise "bit index out of range" unless i < @length
@value ^= (1 << (@length - i - 1))
return self
end
# Return a one-letter string containing the character encoded by this Code object,
# which must have fewer than 8 bits.
# Ignores the parity bit if it has been attached.
def chr
raise "code must have fewer than 8 bits" unless @value < 256
if @has_parity_bit
if even_parity?
return (@value >> 1).chr
else
return "?"
end
else
return @value.chr
end
end
# Compare the numeric values of this object and Code object +x+.
def <=>(x)
return x.class == Code && @value <=> x.value
end
# Return a string with the binary digits of the value represented by this Code object.
def inspect
if @length == 0
return ""
else
return sprintf "%0#{@length}b", @value
end
end
alias to_s inspect
end # Code
=begin rdoc
== HexCode
HexCodes are Code objects that are printed in hexadecimal. All of the attributes
and methods of the Code class are applicable to HexCodes -- the only differences
are in to_s, which generates the string of digits used to print a number,
and in the method that compares objects (Codes can only be compared to other Codes,
HexCodes to other HexCodes).
=end
class HexCode < Code
# Create a new HexCode object for the number +x+. The optional second
# argument specifies the number of bits to use in the encoding.
def initialize(x, length = log2(x+1).ceil)
super
end
# Compare the numeric values of this object and HexCode object +x+.
def <=>(x)
return x.class == HexCode && @value <=> x.value
end
# Return a string with the hexadecimal digits of the value represented by this Code object.
def inspect
if @length == 0
return ""
else
return sprintf "%0#{@length/4}X", @value
end
end
alias to_s inspect
end
=begin rdoc
== Message
Message objects are arrays of Code objects.
There are two types of messages: packed and unpacked.
If the message is unpacked, each code is stored in its own array element.
This is the standard form for messages created with the
:ascii or :parity encodings.
If the message is packed,
codes are repackaged into 8-bit bytes, and individual codes may cross
array item boundaries. This form is used by the method that uses a
Huffman code to encode a string.
#--
TODO: allow for the possibility that a code word being added to a packed code can be longer than 8 bits
=end
class Message
attr_accessor :packed, :array
@@packsize = 8 # number of bits to pack into single code
# Create a new Message of the specified type (:packed or :unpacked).
# The message is initially empty; new characters are added by with the << operator.
def initialize(type)
raise "Message: unknown type" unless [:packed, :unpacked].include? type
if type == :packed
@packed = true
@array = [ Code.new(0,0) ]
else
@packed = false
@array = [ ]
end
end
# Create a new Message object that is a copy of this message.
def copy
dup = self.clone # copies @packed
dup.array = Array.new
@array.each { |x| dup.array << x.clone } # deep copy of @array
return dup
end
# Iterate over each bit in a message, without regard for code boundaries.
#
# Example:
# >> m
# => 10011
# >> m.each_bit { |b| puts b }
# 1
# 0
# 0
# 1
# 1
# => 10011
def each_bit
@array.each do |byte|
for i in 0...byte.length
yield(byte[i])
end
end
return self
end
# Append the bits in Code object +x+ to the end of this message.
#
# Example:
# >> m = Message.new(:packed)
# =>
# >> m << Code.new(4)
# => 100
# >> m << Code.new(3)
# => 10011
def <<(x)
raise "Message#<<: not a code" unless x.class == Code
if @packed
if @array[-1].length + x.length <= @@packsize
@array[-1] << x
else
n = @@packsize - @array[-1].length
m = x.length - n
@array[-1] << x[0...n]
@array << x[n...x.length]
end
else
@array << x
end
return self
end
# Deprecated -- use standalone BitLab#decode instead
# def decode :nodoc:
# res = ""
# if @encoding.class == Symbol
# @array.each do |code|
# res << code.chr
# end
# elsif @encoding.class == Node
# res = huffman_decode(self, @encoding)
# else
# res = unpack
# end
# return res
# end
# def unpack
# "unpack: not implemented"
# end
# Return the length (in bits) of this message.
def length
@array.inject(0) { |sum, code| sum += code.length }
end
# Create a string of binary digits representing the Codes in this message.
def inspect
if @packed
return @array.join("")
else
return @array.join(" ")
end
end
alias to_s inspect
end # Message
end # BitLab
class PriorityQueue
attr_accessor :on_canvas
# Update the drawing of the priority queue after operation +op+ has been performed
# on +node+. Operation is either :shift or <<, and +node+ is a
# reference to the Node object being removed or inserted into the queue.
#
# Calls helper methods left_edge, right_edge, and tree_sep
# to figure out how to place subtrees to use the minimum amount of horizontal space
# (see bitlab.rb).
def update(op, node)
if op == :shift
move_tree(node, 0, 4 * @@unit) # move subtree rooted at node down 4 units
else
i = 0
dx = @@drawing.options[:qx] - left_edge(@q[0])
while @q[i] != node
move_tree(@q[i], dx, 0)
dx = 3 * @@unit - tree_sep(@q[i], node)
move_tree(node, dx, 0)
dx = 3 * @@unit - tree_sep(@q[i], @q[i+1])
i += 1
sleep(0.2)
end
move_tree(node, 0, -2 * @@unit)
if i < @q.length - 1
dx = 3 * @@unit - tree_sep(@q[i], @q[i+1])
i += 1
while i < @q.length
sleep(0.2)
move_tree(@q[i], dx, 0)
i += 1
end
end
end
end
def left_edge(tree) # :nodoc:
x = tree.coords.x
while tree.lfchain != tree
tree = tree.lfchain
x = min(x, tree.coords.x)
end
return x
end
def right_edge(tree) # :nodoc:
x = tree.coords.x
while tree.rfchain != tree
tree = tree.rfchain
x = max(x, tree.coords.x)
end
return x
end
def tree_sep(left, right) # :nodoc:
res = right.coords.x - left.coords.x
while (left.rfchain != left && right.lfchain != right)
left = left.rfchain
right = right.lfchain
dist = right.coords.x - left.coords.x
res = dist if dist < res
end
# loop do
# puts "res = #{res}"
# break if left == left.rfchain || right == right.lfchain
# left = left.rfchain
# right = right.lfchain
# puts "down #{left.inspect} --- #{right.inspect}"
# dist = right.coords.x - left.coords.x
# res = dist if dist < res
# end
return res
end
end
end # RubyLabs
class Fixnum
=begin rdoc
== Fixnum
Add a method to +Fixnum+ to make a +Code+ object showing the binary or
hexadecimal representation of a number. Intended mainly to show codes for
characters in +String+ objects.
=end
# Create a Code object showing the binary or hexadecimal representation of
# this number. The two arguments, both optional, define the type of
# representation and the number of digits:
# x.code make a binary code for x, using as many bits as necessary
# x.code(n) make a binary code for x, using n bits
# x.code(:hex) make a hex code for x, using as many digits as necessary
# x.code(:hex,n) make a hex code for x, using n digits (i.e. 4*n bits)
#
# Example:
# >> x = 26
# => 26
# >> x.code
# => 11010
# >> x.code(8)
# => 00011010
# >> x.code(:hex)
# => 1A
# >> x.code(:hex, 3)
# => 01A
def code(*args)
if args.first == :hex
base = args.shift
else
base = :binary
end
if args.first.kind_of? Integer
digits = args.shift
elsif args.empty?
b = (self == 0) ? 1 : log2(self+1).ceil
digits = (base == :hex) ? (b/4.0).ceil : b
else
raise "code: can't understand #{args}"
end
if base == :hex
return HexCode.new(self, 4*digits)
else
return Code.new(self, digits)
end
# bits = (base == :hex) ? digits * 4 : digits
# return Code.new(self, bits, base)
end
end