lib/compsci/node.rb in compsci-0.3.0.1 vs lib/compsci/node.rb in compsci-0.3.1.1

- old
+ new

@@ -1,77 +1,151 @@ module CompSci # has a value and an array of children; allows child gaps class Node - attr_accessor :value + def self.display_line(nodes: [], width: 80) + block_width = [width / nodes.size, 1].max + nodes.map { |node| + str = node ? node.to_s : '_' + space = [(block_width + str.size) / 2, str.size + 1].max + str.ljust(space, ' ').rjust(block_width, ' ') + }.join + end + + attr_accessor :value, :metadata attr_reader :children - def initialize(value, children: []) + def initialize(value, children: 2, metadata: {}) @value = value - if children.is_a?(Integer) - @children = Array.new(children) - else - @children = children - end - # @metadata = {} + @children = Array.new(children) + @metadata = metadata end - def to_s - @value.to_s + def [](idx) + @children[idx] end - # This could be done directly with self.children, but #set_child is part - # of the Node API. - def set_child(idx, node) + def []=(idx, node) @children[idx] = node end + def to_s + @value.to_s + end + def inspect "#<%s:0x%0xi @value=%s @children=[%s]>" % - [self.class, - self.object_id, - self.to_s, - @children.map(&:to_s).join(', ')] + [self.class, self.object_id, self, @children.join(', ')] end + + def display(width: 80) + lines = [self.class.display_line(nodes: [self], width: width)] + nodes = @children + while nodes.any? { |n| !n.nil? } + lines << self.class.display_line(nodes: nodes, width: width) + if nodes.size > 3**7 + lines << "nodes.size = #{nodes.size}; abort render" + break + end + nodes = nodes.reduce([]) { |memo, n| + memo + Array.new(@children.size) { |i| n and n.children[i] } + } + end + lines.join("\n") + end end + # TODO: implement key with @metadata !?!?!?!? + # adds a key to Node; often the key is used to place the node in the # tree, independent of the value; e.g. key=priority, value=process_id class KeyNode < Node - attr_accessor :key + class DuplicateKey < RuntimeError; end + class SearchError < RuntimeError; end - def initialize(val, key: nil, children: []) - @key = key + attr_reader :key, :duplicates + + def self.key_cmp_idx(new_key, key, child_slots: 2, duplicates: false) + if child_slots < 2 + raise(ArgumentError, "child_slots: #{child_slots} too small") + elsif child_slots == 2 + raise(DuplicateKey, "#{new_key}") if new_key == key and !duplicates + new_key < key ? 0 : 1 + elsif child_slots == 3 + (new_key <=> key) + 1 + else + raise(ArgumentError: "child_slots: #{child_slots} too big") + end + end + + def initialize(val, key: nil, children: 2, duplicates: false) super(val, children: children) + @key, @duplicates = key, duplicates end def to_s - [key, value].join(':') + [@key, @value].join(':') end - end - # accumulate children; no child gaps - class FlexNode < Node - # These methods look like convenience methods, but they provide the - # FlexNode interface also used by ChildFlexNode - def add_child(node) - @children << node + # which child idx should handle key? + def cidx(key) + self.class.key_cmp_idx(key, @key, + child_slots: @children.size, + duplicates: @duplicates) end - def new_child(value) - self.add_child self.class.new(value) + # works for 2 or 3 children + def insert(key, val) + idx = self.cidx(key) + if @children[idx] + @children[idx].insert(key, val) + else + @children[idx] = self.class.new(val, key: key, + children: @children.size, + duplicates: @duplicates) + end end - def add_parent(node) - node.add_child(self) + # returns a single node for binary search + # returns multiple nodes for ternary search + def search(key) + if @children.size == 2 + self.search2(key) + elsif @children.size == 3 + self.search3(key) + else + raise(SearchError, "can't search for @children.size children") + end end + + # returns a single node or nil + def search2(key) + return self if key == @key + child = @children[self.cidx(key)] + child.search(key) if child + end + + # returns an array of nodes, possibly empty + def search3(key) + if key == @key + nodes = [self] + node = @children[1] + while node + nodes << node + node = node.children[1] + end + return nodes + end + child = @children[self.cidx(key)] + child ? child.search(key) : [] + end end # like Node but with a reference to its parent class ChildNode < Node attr_accessor :parent - def initialize(value, children: []) + def initialize(value, children: 2) @parent = nil super(value, children: children) end # O(log n) recursive @@ -81,37 +155,13 @@ def siblings @parent ? @parent.children : [] end - def set_child(idx, node) + # update both sides of the relationship + def []=(idx, node) node.parent ||= self raise "node has a parent: #{node.parent}" if node.parent != self @children[idx] = node - end - - def set_parent(idx, node) - @parent = node - @parent.set_child(idx, self) - end - end - - # ChildNode which accumulates children with no gaps - # It meets the FlexNode API but does not inherit from FlexNode since it - # needs to reimplement each method; instead get parent stuff from ChildNode - class ChildFlexNode < ChildNode - def add_child(node) - node.parent ||= self - raise "node has a parent: #{node.parent}" if node.parent != self - @children << node - end - - def new_child(value) - self.add_child self.class.new(value) - end - - def add_parent(node) - @parent = node - @parent.add_child(self) end end end