Module: Tree::DownTreeFilteredAlgorithms
- Includes:
- BranchesProperty, NodeProperty
- Included in:
- ArrayTree, DownTreeAlgorithms, FilteredArrayTree
- Defined in:
- lib/modular_tree/algorithms.rb
Overview
A down tree can only be traversed top-down as it has no reference to its parent. It is also a kind of a graph node
TODO: Turn into class methods to implement array/hash/other adaptors.
#children should then be called as 'children(node)' everywhere
A #node method should also be defined at the top-most level
TODO: Better split between DownTreeAlgorithms and DownTreeFilteredAlgorithms
Class Method Summary collapse
-
.filter(*args) ⇒ Object
Create a Tree::Filter object.
Instance Method Summary collapse
-
#accumulate(*filter, accumulator, this: true, &block) ⇒ Object
Traverse the tree top-down while accumulating information in an accumulator object.
-
#aggregate(*filter, this: true, &block) ⇒ Object
Traverse the tree bottom-up while aggregating information.
-
#choose(*expr, &block) ⇒ Object
Filter children.
-
#descendants(*filter) ⇒ Object
Enumerator of descendant nodes matching filter.
-
#each(*filter, this: true, &block) ⇒ Object
Implementation of Enumerable#each extended with filters.
-
#edges(*filter, this: true, &block) ⇒ Object
Enumerator of edges in the tree.
-
#nodes(*filter, this: true, &block) ⇒ Object
Like #each but the block is called with node, key, and parent instead of value, key, and parent.
-
#pairs(first_match_expr, second_match_expr, this: true) ⇒ Object
Return array of [previous-matching-node, matching-node] tuples.
-
#postorder(*filter, this: true) ⇒ Object
Post-order enumerator of selected nodes.
-
#preorder(*filter, this: true) ⇒ Object
Pre-order enumerator of selected nodes.
-
#select(*expr, this: true, &block) ⇒ Object
Implementation of Enumerable#select extended with a single filter.
-
#size ⇒ Object
The number of nodes in the tree.
-
#traverse(*filter, this: true, &traverse_block) ⇒ Object
Find nodes matching
filter
and calltraverse_block
with a node and a block argument.
Methods included from BranchesProperty
#bare?, #branches, #each_branch
Methods included from NodeProperty
Class Method Details
.filter(*args) ⇒ Object
Create a Tree::Filter object. Can also take an existing filter as argument in which case the given filter will just be passed through
200 201 202 203 204 205 206 207 |
# File 'lib/modular_tree/algorithms.rb', line 200 def self.filter(*args) if args.first.is_a?(Filter) args.size == 1 or raise ArgumentError args.first else Filter.new(*args) end end |
Instance Method Details
#accumulate(*filter, accumulator, this: true, &block) ⇒ Object
Traverse the tree top-down while accumulating information in an accumulator object. The block takes a [accumulator, node] tuple and is responsible for adding itself to the accumulator. The return value from the block is then used as the accumulator for the branch nodes. Note that it returns the original accumulator and not the final result - this makes it different from #inject
#accumulate is a kind of “preorder” algorithm
174 175 176 177 178 179 |
# File 'lib/modular_tree/algorithms.rb', line 174 def accumulate(*filter, accumulator, this: true, &block) filter = self.class.filter(*filter) block_given? or raise ArgumentError, "Block is required" do_accumulate(filter, this, accumulator, &block) accumulator end |
#aggregate(*filter, this: true, &block) ⇒ Object
Traverse the tree bottom-up while aggregating information. The block is called with a [current-node, branch-node-results] tuple
#aggregate is a kind of “postorder” algorithm
TODO: Remove this
flag - it not used and doesn’t make sense
187 188 189 190 |
# File 'lib/modular_tree/algorithms.rb', line 187 def aggregate(*filter, this: true, &block) filter = self.class.filter(*filter) do_aggregate(filter, this, &block) end |
#choose(*expr, &block) ⇒ Object
Filter children. Doesn’t recurse. If a block is given, it should return truish to select a child node
The match expression can also be a list of classes (instead of an array of classes)
TODO: Maybe make #children an Array extended with filtering
98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/modular_tree/algorithms.rb', line 98 def choose(*expr, &block) !block_given? || expr.empty? or raise ArgumentError, "Can't use both match expression and block" if block_given? each_branch.select { |branch| yield branch } elsif !expr.empty? matcher = Matcher.new(*expr, &block) each_branch.select { |branch| matcher.match? branch } else each end end |
#descendants(*filter) ⇒ Object
Enumerator of descendant nodes matching filter. Same as #preorder with :this set to false. TODO: Maybe introduce forrests: tree.forrest.each
67 |
# File 'lib/modular_tree/algorithms.rb', line 67 def descendants(*filter) = each(filter, this: false) |
#each(*filter, this: true, &block) ⇒ Object
Implementation of Enumerable#each extended with filters. The block is called with value, key, and parent as arguments but it may choose to ignore the key and/or parent argument. Returns an enumerator of values without a block
73 |
# File 'lib/modular_tree/algorithms.rb', line 73 def each(*filter, this: true, &block) = common_each(*filter, :node_value, :do_each_preorder, this, &block) |
#edges(*filter, this: true, &block) ⇒ Object
Enumerator of edges in the tree. Edges are [previous-matching-node, matching-node] tuples. Top-level nodes have previous-matching-node set to nil (the result is a forrest). If the filter matches all nodes the value is an edge-representation of the tree
128 129 130 131 132 133 134 |
# File 'lib/modular_tree/algorithms.rb', line 128 def edges(*filter, this: true, &block) if block_given? each(*filter, this: this) { |node, _, parent| yield parent, node } else Pairs.new { |enum| each(*filter, this: this) { |node, _, parent| enum << [parent, node] } } end end |
#nodes(*filter, this: true, &block) ⇒ Object
Like #each but the block is called with node, key, and parent instead of value, key, and parent
112 |
# File 'lib/modular_tree/algorithms.rb', line 112 def nodes(*filter, this: true, &block) = common_each(*filter, :node, :do_each_preorder, this, &block) |
#pairs(first_match_expr, second_match_expr, this: true) ⇒ Object
Return array of [previous-matching-node, matching-node] tuples. Returns the empty array iff there is no matching set of nodes
138 139 140 141 142 143 144 145 146 147 |
# File 'lib/modular_tree/algorithms.rb', line 138 def pairs(first_match_expr, second_match_expr, this: true) first_matcher = Matcher.new(first_match_expr) second_matcher = Matcher.new(second_match_expr) or_matcher = first_matcher | second_matcher # avoids re-computing this value over and over result = [] nodes(first_matcher, false, this: this) { |node| node.do_pairs(result, first_matcher, or_matcher) } result end |
#postorder(*filter, this: true) ⇒ Object
Post-order enumerator of selected nodes
118 |
# File 'lib/modular_tree/algorithms.rb', line 118 def postorder(*filter, this: true) = common_each(*filter, :node_value, :each_postorder, this) |
#preorder(*filter, this: true) ⇒ Object
Pre-order enumerator of selected nodes. Same as #each without a block
115 |
# File 'lib/modular_tree/algorithms.rb', line 115 def preorder(*filter, this: true) = each(*filter, this: this) |
#select(*expr, this: true, &block) ⇒ Object
Implementation of Enumerable#select extended with a single filter. As #each, the block is called with value, key, and parent arguments
The match expression can also be a list of classes (instead of an array of classes)
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/modular_tree/algorithms.rb', line 80 def select(*expr, this: true, &block) !block_given? || expr.empty? or raise ArgumentError, "Can't use both match expression and block" if block_given? each.select { |branch| yield branch } elsif !expr.empty? matcher = Matcher.new(*expr, &block) each.select { |branch| matcher.match? branch } else each end end |
#size ⇒ Object
The number of nodes in the tree. Note that this can be an expensive operation because every node has to be visited
63 |
# File 'lib/modular_tree/algorithms.rb', line 63 def size = 1 + descendants.to_a.size |
#traverse(*filter, this: true, &traverse_block) ⇒ Object
Find nodes matching filter
and call traverse_block
with a node and a block argument. traverse_block
can recurse into children by calling the supplied inner block
An example of how to create a nested array implementation:
root.traverse(true) { |node, inner| [node, inner.call(node)] }
157 158 159 160 161 162 163 164 |
# File 'lib/modular_tree/algorithms.rb', line 157 def traverse(*filter, this: true, &traverse_block) filter = self.class.filter(*filter) inner_block_proxy = [nil] # Hack because you can't refer to a lambda within its declaration inner_block = inner_block_proxy[0] = lambda { |node| node.nodes(filter, this: false).map { |n| traverse_block.call(n, inner_block_proxy.first) } } nodes(filter, this: this).map { |node| traverse_block.call(node, inner_block) } end |