lib/sycamore/tree.rb in sycamore-0.1.0 vs lib/sycamore/tree.rb in sycamore-0.2.0
- old
+ new
@@ -16,14 +16,16 @@
########################################################################
# @group CQS reflection
########################################################################
# the names of all command methods, which add elements to a Tree
- ADDITIVE_COMMAND_METHODS = %i[add << replace add_node_with_empty_child] << :[]=
+ ADDITIVE_COMMAND_METHODS = %i[add << replace add_node_with_empty_child
+ clear_child_of_node] << :[]=
# the names of all command methods, which delete elements from a Tree
- DESTRUCTIVE_COMMAND_METHODS = %i[delete >> clear compact replace] << :[]=
+ DESTRUCTIVE_COMMAND_METHODS = %i[delete >> clear compact replace
+ clear_child_of_node] << :[]=
# the names of all additive command methods, which only add elements from a Tree
PURE_ADDITIVE_COMMAND_METHODS = ADDITIVE_COMMAND_METHODS - DESTRUCTIVE_COMMAND_METHODS
# the names of all destructive command methods, which only delete elements from a Tree
@@ -42,11 +44,11 @@
eql? matches? === ==]
# the names of all methods, which side-effect-freeze return only a value
QUERY_METHODS = PREDICATE_METHODS +
%i[new_child dup hash to_native_object to_h to_s inspect
- node nodes keys child_of child_at dig fetch
+ node node! nodes keys child_of child_at dig fetch search
size total_size tsize height
each each_path paths each_node each_key each_pair] << :[]
%i[COMMAND_METHODS QUERY_METHODS PREDICATE_METHODS
ADDITIVE_COMMAND_METHODS DESTRUCTIVE_COMMAND_METHODS
@@ -221,16 +223,10 @@
self
end
##
- # Adds a node with an empty child to this tree.
- #
- # @return +self+ as a proper command method
- #
- # @raise [InvalidNode]
- #
# @api private
#
def add_node_with_empty_child(node)
raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
@@ -239,10 +235,21 @@
end
self
end
+ ##
+ # @api private
+ #
+ def clear_child_of_node(node)
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
+
+ @data[node] = Nothing
+
+ self
+ end
+
private def add_child(node, children)
return add_node(node) if Nothing.like?(children)
add_node_with_empty_child(node)
@data[node] << children
@@ -287,11 +294,10 @@
# tree.delete "d" => {foo: :bar}
# tree.to_h # => {"d" => {foo: :baz}}
# tree.delete "d" => {foo: :baz}
# tree.to_h # => {}
#
- # @todo differentiate a greedy and a non-greedy variant
# @todo support Paths
#
def delete(nodes_or_tree)
case
when Tree.like?(nodes_or_tree) then delete_tree(nodes_or_tree)
@@ -372,10 +378,13 @@
# reference a deeper node with a path of nodes.
#
# Note that even if you assign a {Sycamore::Tree} directly the given tree
# will not become part of this tree by reference.
#
+ # An exception is the assignment of the {Nothing} tree: it will delete the
+ # child tree at the given path entirely.
+ #
# @overload []=(*path, node)
# Replaces the contents of the child at the given path with a single node.
# @param path [Array<Object>, Sycamore::Path] a path as a sequence of nodes or a {Path} object
# @param node [Object]
#
@@ -404,16 +413,27 @@
# tree.to_h # => {:foo => :baz, 1 => {2 => {3 => 4}}}
# tree[1] = tree[:foo]
# tree.to_h # => {:foo => :baz, 1 => :baz}
# tree[:foo] << :bar
# tree.to_h # => {:foo => [:baz, :bar], 1 => :baz}
+ # tree[:foo] = Sycamore::Nothing
+ # tree.to_h # => {:foo => nil, 1 => :baz}
#
def []=(*args)
path, nodes_or_tree = args[0..-2], args[-1]
raise ArgumentError, 'wrong number of arguments (given 1, expected 2)' if path.empty?
- child_at(*path).replace(nodes_or_tree)
+ if nodes_or_tree.equal? Sycamore::Nothing
+ if path.size == 1
+ clear_child_of_node(path.first)
+ else
+ path, node = path[0..-2], path[-1]
+ child_at(*path).clear_child_of_node(node)
+ end
+ else
+ child_at(*path).replace(nodes_or_tree)
+ end
end
##
# Deletes all nodes and their children.
#
@@ -444,11 +464,11 @@
# tree.to_h # => {foo: :bar}
#
def compact
@data.each do |node, child| case
when child.nothing? then next
- when child.empty? then @data[node] = Nothing
+ when child.empty? then clear_child_of_node(node)
else child.compact
end
end
self
@@ -487,18 +507,41 @@
# tree = Tree[foo: 1, bar: [2,3]]
# tree[:foo].node # => 1
# tree[:baz].node # => nil
# tree[:bar].node # => raise Sycamore::NonUniqueNodeSet, "multiple nodes present: [2, 3]"
#
+ # @see Tree#node!
+ #
def node
nodes = self.nodes
raise NonUniqueNodeSet, "multiple nodes present: #{nodes}" if nodes.size > 1
nodes.first
end
##
+ # The only node of this tree or an exception, if none or more {#nodes nodes} present.
+ #
+ # @return [Object] the single present node
+ #
+ # @raise [EmptyNodeSet] if no nodes present
+ # @raise [NonUniqueNodeSet] if more than one node present
+ #
+ # @example
+ # tree = Tree[foo: 1, bar: [2,3]]
+ # tree[:foo].node! # => 1
+ # tree[:baz].node! # => raise Sycamore::EmptyNodeSet, "no node present"
+ # tree[:bar].node! # => raise Sycamore::NonUniqueNodeSet, "multiple nodes present: [2, 3]"
+ #
+ # @see Tree#node
+ #
+ def node!
+ raise EmptyNodeSet, 'no node present' if empty?
+ node
+ end
+
+ ##
# The child tree of a node.
#
# When a child to the given node is not a present, an {Absence} object
# representing the missing tree is returned.
#
@@ -781,9 +824,35 @@
when elements.is_a?(Enumerable)
elements.all? { |element| include_node? element }
else
include_node? elements
end
+ end
+
+ ##
+ # Searches the tree for one or multiple nodes or a complete tree.
+ #
+ # @param nodes_or_tree [Object, Array, Tree, Hash]
+ # @return [Array<Path>]
+ #
+ # @example
+ # tree = Tree[ "a" => [:foo, 100], "b" => { foo: [:bar, :baz] } ]
+ # tree.search :bar # => [Sycamore::Path["b", :foo]]
+ # tree.search :foo # => [Sycamore::Path["a"], Sycamore::Path["b"]]
+ # tree.search [:bar, :baz] # => [Sycamore::Path["b", :foo]]
+ # tree.search foo: :bar # => [Sycamore::Path["b"]]
+ # tree.search 42 # => []
+ #
+ def search(nodes_or_tree)
+ _search(nodes_or_tree)
+ end
+
+ protected def _search(query, current_path: Path::ROOT, results: [])
+ results << current_path if include?(query)
+ each do |node, child|
+ child._search(query, current_path: current_path/node, results: results)
+ end
+ results
end
##
# The number of {#nodes nodes} in this tree.
#