150 lines
2.7 KiB
Ruby
150 lines
2.7 KiB
Ruby
class HuffNode
|
|
attr_accessor :weight, :symbol, :left, :right, :parent
|
|
|
|
def initialize(params = {})
|
|
@weight = params[:weight] || 0
|
|
@symbol = params[:symbol] || ''
|
|
@left = params[:left] || nil
|
|
@right = params[:right] || nil
|
|
@parent = params[:parent] || nil
|
|
end
|
|
|
|
def walk(&block)
|
|
walk_node('', &block)
|
|
end
|
|
|
|
def walk_node(code, &block)
|
|
yield(self, code)
|
|
@left.walk_node(code + '0', &block) unless @left.nil?
|
|
@right.walk_node(code + '1', &block) unless @right.nil?
|
|
end
|
|
|
|
def leaf?
|
|
@symbol != ''
|
|
end
|
|
|
|
def internal?
|
|
@symbol == ''
|
|
end
|
|
|
|
def root?
|
|
internal? and @parent.nil?
|
|
end
|
|
|
|
end
|
|
|
|
class NodeQueue
|
|
attr_accessor :nodes, :huffman_root
|
|
|
|
def initialize(list)
|
|
frequencies = {}
|
|
list.each do |c|
|
|
frequencies[c] ||= 0
|
|
frequencies[c] += 1
|
|
end
|
|
@nodes = []
|
|
frequencies.each do |c, w|
|
|
@nodes << HuffNode.new(:symbol => c, :weight => w)
|
|
end
|
|
generate_tree
|
|
end
|
|
|
|
def find_smallest(not_this)
|
|
smallest = nil
|
|
for i in 0..@nodes.size - 1
|
|
if i == not_this
|
|
next
|
|
end
|
|
if smallest.nil? or @nodes[i].weight < @nodes[smallest].weight
|
|
smallest = i
|
|
end
|
|
end
|
|
smallest
|
|
end
|
|
|
|
|
|
def generate_tree
|
|
while @nodes.size > 1
|
|
node1 = self.find_smallest(-1)
|
|
node2 = self.find_smallest(node1)
|
|
new = merge_nodes(@nodes[node1], @nodes[node2])
|
|
if node1 > node2
|
|
@nodes.delete_at(node1)
|
|
@nodes.delete_at(node2)
|
|
else
|
|
@nodes.delete_at(node2)
|
|
@nodes.delete_at(node1)
|
|
end
|
|
|
|
@nodes << new
|
|
end
|
|
@huffman_root = @nodes.first
|
|
end
|
|
|
|
def merge_nodes(node1, node2)
|
|
left = node1
|
|
right = node2
|
|
node = HuffNode.new(:weight => left.weight + right.weight, :left => left, :right => right)
|
|
left.parent = right.parent = node
|
|
node
|
|
end
|
|
|
|
end
|
|
|
|
class HuffmanEncoding
|
|
attr_accessor :root, :lookup, :input, :output
|
|
|
|
def initialize(input)
|
|
@input = input
|
|
@root = NodeQueue.new(input).huffman_root
|
|
# @output = encode_list(input)
|
|
end
|
|
|
|
def lookup
|
|
return @lookup if @lookup
|
|
@lookup = {}
|
|
@root.walk do |node, code|
|
|
@lookup[code] = node.symbol if node.leaf?
|
|
end
|
|
@lookup
|
|
end
|
|
|
|
def encode(entry)
|
|
self.lookup.invert[entry] || ""
|
|
end
|
|
|
|
def decode(code)
|
|
self.lookup[code] || ""
|
|
end
|
|
|
|
def encode_list(list)
|
|
code = ''
|
|
list.each do |c|
|
|
code += encode(c)
|
|
end
|
|
code
|
|
end
|
|
|
|
def decode_string(code)
|
|
code = code.to_s
|
|
string = ''
|
|
subcode = ''
|
|
code.each_char do |bit|
|
|
subcode += bit
|
|
unless decode(subcode).nil?
|
|
string += decode(subcode)
|
|
subcode = ''
|
|
end
|
|
end
|
|
string
|
|
end
|
|
|
|
def to_s
|
|
@output
|
|
end
|
|
|
|
def [](char)
|
|
encode(char)
|
|
end
|
|
|
|
end
|