modular_tree 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/modular_tree/algorithms.rb +60 -25
- data/lib/modular_tree/filter.rb +27 -51
- data/lib/modular_tree/implementations.rb +6 -3
- data/lib/modular_tree/matcher.rb +64 -0
- data/lib/modular_tree/properties.rb +2 -1
- data/lib/modular_tree/version.rb +1 -1
- data/lib/modular_tree.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 420d72c774f0602ebf2b50b7346ee64759aaf48f89b8e041c80fe76464dbd379
|
4
|
+
data.tar.gz: 4ceb72733d99138a6244c4b163b46d0fb10fcdf20b8f7d61e582903bab750bc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5b2b90bcaaa71fd2754093cbd3ccca1fea3c9193c3261a82911bd10c7c8c6e74476804006238215aaf448062424dbfeef606550b69f378fd742d159d58b05b2
|
7
|
+
data.tar.gz: 8330f74bf4368cc7d56f35b42e48ff4f1dd51641ed94f05f4340f222eef22d62d0a3b32c883e2e6d916b78762a3d23870e62131b3e2f461c10ccabf16070d6ce
|
@@ -49,8 +49,9 @@ module Tree
|
|
49
49
|
include NodeProperty
|
50
50
|
include BranchesProperty
|
51
51
|
|
52
|
-
# True if the node doesn't contain any branches (#empty? for trees)
|
53
|
-
|
52
|
+
# True if the node doesn't contain any branches (#empty? for trees).
|
53
|
+
# Default implementation in BranchesProperty
|
54
|
+
# def bare? = raise NotImplemented
|
54
55
|
|
55
56
|
# The number of nodes in the tree. Note that this can be an expensive
|
56
57
|
# operation because every node has to be visited
|
@@ -61,9 +62,10 @@ module Tree
|
|
61
62
|
def descendants(*filter) = each(filter, this: false)
|
62
63
|
|
63
64
|
# Implementation of Enumerable#each extended with filters. The block is
|
64
|
-
# called with value, key, and parent as arguments
|
65
|
-
# ignore key and/or parent
|
66
|
-
|
65
|
+
# called with value, key, and parent as arguments but it may choose to
|
66
|
+
# ignore the key and/or parent argument. Returns an enumerator of values
|
67
|
+
# without a block
|
68
|
+
def each(*filter, this: true, &block) = common_each(*filter, :node_value, :do_each_preorder, this, &block)
|
67
69
|
|
68
70
|
# Implementation of Enumerable#select extended with a single filter. As
|
69
71
|
# #each, the block is called with value, key, and parent arguments
|
@@ -83,22 +85,49 @@ module Tree
|
|
83
85
|
def preorder(*filter, this: true) = each(*filter, this: this)
|
84
86
|
|
85
87
|
# Post-order enumerator of selected nodes
|
86
|
-
def postorder(*filter, this: true) = common_each(*filter, :
|
88
|
+
def postorder(*filter, this: true) = common_each(*filter, :node_value, :each_postorder, this)
|
87
89
|
|
88
90
|
# Enumerator of edges in the tree. Edges are [previous-matching-node,
|
89
91
|
# matching-node] tuples. Top-level nodes have previous-matching-node set to
|
90
92
|
# nil
|
91
93
|
#
|
94
|
+
# FIXME: Not working right now. Not sure how #edges relates to #pairs
|
92
95
|
def edges(*filter, this: true, &block)
|
93
|
-
if block_given
|
96
|
+
if block_given?
|
94
97
|
each(*filter, this: this) { |node, _, parent| yield parent, node }
|
95
98
|
else
|
96
99
|
Pairs.new { |enum| each(*filter, this: this) { |node, _, parent| enum << [parent, node] } }
|
97
100
|
end
|
98
101
|
end
|
99
102
|
|
100
|
-
#
|
101
|
-
#
|
103
|
+
# Find nodes matching +filter+ and call +traverse_block+ with a node and a
|
104
|
+
# block argument. +traverse_block+ can recurse into children by calling the
|
105
|
+
# supplied inner block
|
106
|
+
#
|
107
|
+
# An example of how to create a nested array implementation:
|
108
|
+
#
|
109
|
+
# root.traverse(true) { |node, inner| [node, inner.call(node)] }
|
110
|
+
#
|
111
|
+
def traverse(*filter, this: true, &traverse_block)
|
112
|
+
filter = self.class.filter(*filter)
|
113
|
+
inner_block_proxy = [nil] # Hack because you can't refer to a lambda within its declaration
|
114
|
+
inner_block = inner_block_proxy[0] = lambda { |node|
|
115
|
+
node.nodes(filter, this: false).map { |n| traverse_block.call(n, inner_block_proxy.first) }
|
116
|
+
}
|
117
|
+
nodes(filter, this: this).map { |node| traverse_block.call(node, inner_block) }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return array of [previous-matching-node, matching-node] tuples
|
121
|
+
def pairs(first_matcher_expr, second_matcher_expr, this: true)
|
122
|
+
first_matcher = Matcher.new(first_matcher_expr)
|
123
|
+
second_matcher = Matcher.new(second_matcher_expr)
|
124
|
+
or_matcher = first_matcher | second_matcher # avoids re-computing this value over and over
|
125
|
+
result = []
|
126
|
+
nodes(first_matcher, false, this: this) { |node|
|
127
|
+
node.do_pairs(result, first_matcher, or_matcher)
|
128
|
+
}
|
129
|
+
result
|
130
|
+
end
|
102
131
|
|
103
132
|
# Traverse the tree top-down while accumulating information in an
|
104
133
|
# accumulator object. The block takes a [accumulator, node] tuple and is
|
@@ -126,10 +155,7 @@ module Tree
|
|
126
155
|
do_aggregate(filter, this, &block)
|
127
156
|
end
|
128
157
|
|
129
|
-
|
130
|
-
def propagate(*filter, this: true, &block)
|
131
|
-
|
132
|
-
end
|
158
|
+
# def propagate(*filter, this: true, &block) raise NotImplemented end
|
133
159
|
|
134
160
|
# tree.each(DocumentNode).select(BriefNode).each { |doc, brief| ... }
|
135
161
|
# tree.edges(DocumentNode, BriefNode).group.all? { |doc, briefs| briefs.size <= 1 }
|
@@ -142,7 +168,7 @@ module Tree
|
|
142
168
|
args.size == 1 or raise ArgumentError
|
143
169
|
args.first
|
144
170
|
else
|
145
|
-
Filter.new(*args)
|
171
|
+
Filter.new(*args)
|
146
172
|
end
|
147
173
|
end
|
148
174
|
|
@@ -156,6 +182,16 @@ module Tree
|
|
156
182
|
end
|
157
183
|
end
|
158
184
|
|
185
|
+
def do_pairs(acc, first_matcher, or_matcher) # self is matched by first_matcher
|
186
|
+
nodes(or_matcher, false, this: false) { |node|
|
187
|
+
if first_matcher.match?(node)
|
188
|
+
node.do_pairs(acc, first_matcher, or_matcher)
|
189
|
+
else
|
190
|
+
acc << [self, node]
|
191
|
+
end
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
159
195
|
# TODO: Split into automatically generated variants
|
160
196
|
def do_each_preorder(enum, filter, value_method, key, parent, this, &block)
|
161
197
|
select, traverse = filter.match(self)
|
@@ -192,15 +228,15 @@ module Tree
|
|
192
228
|
end
|
193
229
|
end
|
194
230
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
231
|
+
# def do_propagate(filter, this, key, parent, &block)
|
232
|
+
# select, traverse = filter.match(self)
|
233
|
+
# if select && this
|
234
|
+
# return if yield(self, key, parent)
|
235
|
+
# end
|
236
|
+
# if !this || traverse
|
237
|
+
# each_branch { |branch, key| branch.do_propagate(filter, key, self.value) }
|
238
|
+
# end
|
239
|
+
# end
|
204
240
|
|
205
241
|
def do_accumulate(filter, this, acc, &block)
|
206
242
|
select, traverse = filter.match(self)
|
@@ -214,9 +250,8 @@ module Tree
|
|
214
250
|
values = traverse ? each_branch { |branch|
|
215
251
|
r = branch.do_aggregate(filter, true, &block)
|
216
252
|
}.to_a : []
|
217
|
-
yield(self.
|
253
|
+
yield(self.node_value, values)
|
218
254
|
end
|
219
|
-
|
220
255
|
end
|
221
256
|
|
222
257
|
module DownTreeAlgorithms
|
data/lib/modular_tree/filter.rb
CHANGED
@@ -1,86 +1,62 @@
|
|
1
1
|
module Tree
|
2
2
|
class Filter
|
3
|
+
# :call-seq:
|
4
|
+
# Filter.new(select_expr, traverse_expr)
|
5
|
+
# Filter.new(select_expr, &block)
|
6
|
+
# Filter.new(&block)
|
7
|
+
#
|
3
8
|
# Create a node filter. The filter is initialized by a select expression
|
4
9
|
# and a traverse expression. The select expression decides if the node
|
5
10
|
# should be given to the block (or submitted to an enumerator) and the
|
6
11
|
# traverse expression decides if the child nodes should be traversed
|
7
12
|
# recursively
|
8
13
|
#
|
9
|
-
# The expressions can be a
|
10
|
-
#
|
11
|
-
# nil. These values have
|
14
|
+
# The expressions can be a Matcher object or one of Matcher's initializers:
|
15
|
+
# Proc, Symbol, Class, or an array of classes. In addition, +select+ can be
|
16
|
+
# true, and +traverse+ can be true, false, or nil. These values have
|
17
|
+
# special meanings:
|
12
18
|
#
|
13
19
|
# when +select+ is
|
14
20
|
# true Select always. This is the default
|
21
|
+
# false This is an allowed value but it doesn't select any node
|
15
22
|
#
|
16
23
|
# when +traverse+ is
|
17
24
|
# true Traverse always. This is the default
|
18
25
|
# false Traverse only if select didn't match
|
19
|
-
# nil Expects +select+ to
|
20
|
-
#
|
26
|
+
# nil Expects +select+ to be a Proc object that returns a [select,
|
27
|
+
# traverse] tuple of booleans
|
21
28
|
#
|
22
29
|
# If the expression is a Proc object, it will be called with the current
|
23
30
|
# node as argument. If the return value is true, the node is
|
24
31
|
# selected/traversed and skipped otherwise. If the expression is a method
|
25
32
|
# name (Symbol), the method will be called on each node with no arguments.
|
26
33
|
# It is not an error if the method doesn't exists on the a given node but
|
27
|
-
# the node is not selected/traversed
|
34
|
+
# the node is not selected/traversed. If the expression is a class or an
|
35
|
+
# array of classes, a given node matches if it is an instance of one of the
|
36
|
+
# classes or any subclass
|
28
37
|
#
|
29
|
-
# If
|
30
|
-
#
|
38
|
+
# If a block is given, it is supposed to return a [select, traverse] tuple
|
39
|
+
# of booleans
|
31
40
|
#
|
32
41
|
# Filters should not have side-effects because they can be used in
|
33
42
|
# enumerators that doesn't execute the filter unless the enumerator is
|
34
43
|
# evaluated
|
35
44
|
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
case traverse
|
47
|
-
when Proc; lambda { |node| [select.call(node), traverse.call(node)] }
|
48
|
-
when true; lambda { |node| [select.call(node), true] }
|
49
|
-
when false; lambda { |node| r = select.call(node); [r, !r] }
|
50
|
-
when nil; lambda { |node| select.call(node) }
|
51
|
-
end
|
52
|
-
when true
|
53
|
-
case traverse
|
54
|
-
when Proc; lambda { |node| [true, traverse.call(node)] }
|
55
|
-
when true; lambda { |_| [true, true] }
|
56
|
-
when false; lambda { |_| [true, false] } # effectively same as #children.each
|
57
|
-
when nil; raise ArgumentError
|
58
|
-
end
|
59
|
-
end
|
45
|
+
def initialize(select_expr = nil, traverse_expr = nil, &block)
|
46
|
+
if select_expr.nil? && block_given?
|
47
|
+
@matcher = block
|
48
|
+
else
|
49
|
+
select_expr = true if select_expr.nil?
|
50
|
+
select = Matcher.new(select_expr)
|
51
|
+
traverse_expr = (block_given? ? block : true) if traverse_expr.nil?
|
52
|
+
traverse = traverse_expr ? Matcher.new(traverse_expr) : !select
|
53
|
+
@matcher = lambda { |node| [select.match?(node), traverse.match?(node)] }
|
54
|
+
end
|
60
55
|
end
|
61
56
|
|
62
57
|
# Match +node+ against the filter and return a [select, traverse] tuple of booleans
|
63
58
|
def match(node) = @matcher.call(node)
|
64
59
|
|
65
|
-
# Create a proc if arg is a Symbol or an Array of classes. Pass through
|
66
|
-
# Proc objects, true, false, and nil
|
67
|
-
def mk_lambda(arg) = self.class.mk_lambda(arg)
|
68
|
-
def self.mk_lambda(arg)
|
69
|
-
case arg
|
70
|
-
when Proc, true, false, nil
|
71
|
-
arg
|
72
|
-
when Symbol
|
73
|
-
lambda { |node| node.respond_to?(arg) && node.send(arg) }
|
74
|
-
when Class
|
75
|
-
lambda { |node| node.is_a? arg }
|
76
|
-
when Array
|
77
|
-
arg.all? { |a| a.is_a? Class } or raise ArgumentError, "Array elements should be classes"
|
78
|
-
lambda { |node| arg.any? { |a| node.is_a? a } }
|
79
|
-
else
|
80
|
-
raise ArgumentError
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
60
|
ALL = Filter.new
|
85
61
|
end
|
86
62
|
end
|
@@ -29,7 +29,7 @@ module Tree
|
|
29
29
|
include Implementation
|
30
30
|
def node = self
|
31
31
|
def this = self
|
32
|
-
def
|
32
|
+
def node_value = self
|
33
33
|
end
|
34
34
|
|
35
35
|
module ParentImplementation
|
@@ -69,7 +69,7 @@ module Tree
|
|
69
69
|
|
70
70
|
def node = array
|
71
71
|
def this = array.first
|
72
|
-
def
|
72
|
+
def node_value = array.first # FIXME What is this? It is a problem several places
|
73
73
|
|
74
74
|
attr_accessor :array
|
75
75
|
|
@@ -168,7 +168,10 @@ module Tree
|
|
168
168
|
def each_child(&block) = @children.map(&block)
|
169
169
|
def attach(child) = @children << child
|
170
170
|
|
171
|
-
# Can be used with any array implementation
|
171
|
+
# Can be used with any array implementation. +where+ is either an Integer
|
172
|
+
# index or an object
|
173
|
+
#
|
174
|
+
# TODO: Rename #attach. #insert & #append are Enumerable operations
|
172
175
|
def insert(where, child) = insert_append(:insert, where, child)
|
173
176
|
def append(where, child) = insert_append(:append, where, child)
|
174
177
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Tree
|
2
|
+
class AbstractMatcher
|
3
|
+
def match(node) = abstract_method
|
4
|
+
|
5
|
+
def |(other) = MatchExpression::OrExpr.new(self, other)
|
6
|
+
def &(other) = MatchExpression::AndExpr.new(self, other)
|
7
|
+
def !() = MatchExpression::NegationExpr.new(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
class Matcher < AbstractMatcher
|
11
|
+
def match(node) = match?(node)
|
12
|
+
|
13
|
+
def match?(node)
|
14
|
+
case @expr
|
15
|
+
when Proc
|
16
|
+
@expr.call(node)
|
17
|
+
when Symbol
|
18
|
+
node.respond_to?(@expr) && node.send(@expr)
|
19
|
+
when Class
|
20
|
+
node.is_a? @expr
|
21
|
+
when Array
|
22
|
+
@expr.any? { |klass| node.is_a? klass }
|
23
|
+
when true, false
|
24
|
+
@expr
|
25
|
+
else
|
26
|
+
raise ArgumentError, @expr.inspect
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(expr = nil, &block)
|
31
|
+
constrain expr, Proc, Symbol, Class, [Class], true, false, nil
|
32
|
+
expr.nil? == block_given? or raise ArgumentError
|
33
|
+
@expr = (expr.nil? ? block : expr)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.new(expr = nil, *args, &block)
|
37
|
+
expr.is_a?(AbstractMatcher) ? expr : super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module MatchExpression
|
42
|
+
class BinaryExpr < AbstractMatcher
|
43
|
+
def initialize(left, right)
|
44
|
+
constrain left, Matcher
|
45
|
+
constrain right, Matcher
|
46
|
+
@left, @right = left, right
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class AndExpr < BinaryExpr
|
51
|
+
def match?(node) = @left.match?(node) && @right.match?(node)
|
52
|
+
end
|
53
|
+
|
54
|
+
class OrExpr < BinaryExpr
|
55
|
+
def match?(node) = @left.match?(node) || @right.match?(node) ? true : false
|
56
|
+
end
|
57
|
+
|
58
|
+
class NegationExpr < AbstractMatcher
|
59
|
+
def initialize(matcher) = @matcher = matcher
|
60
|
+
def match?(node) = !@matcher.match?(node)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -13,7 +13,7 @@ module Tree
|
|
13
13
|
def node = abstract_method
|
14
14
|
|
15
15
|
# FIXME: What is this. Also describe all properties in this file
|
16
|
-
def
|
16
|
+
def node_value = abstract_method
|
17
17
|
end
|
18
18
|
|
19
19
|
module StemProperty # Aka. 'parent'
|
@@ -22,6 +22,7 @@ module Tree
|
|
22
22
|
|
23
23
|
module BranchesProperty
|
24
24
|
def branches = abstract_method
|
25
|
+
def bare? = branches.empty?
|
25
26
|
def each_branch(&block) = branches.each(&block)
|
26
27
|
end
|
27
28
|
|
data/lib/modular_tree/version.rb
CHANGED
data/lib/modular_tree.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modular_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Claus Rasmussen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-12-
|
11
|
+
date: 2022-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: constrain
|
@@ -82,6 +82,7 @@ files:
|
|
82
82
|
- lib/modular_tree/algorithms.rb
|
83
83
|
- lib/modular_tree/filter.rb
|
84
84
|
- lib/modular_tree/implementations.rb
|
85
|
+
- lib/modular_tree/matcher.rb
|
85
86
|
- lib/modular_tree/pairs.rb
|
86
87
|
- lib/modular_tree/pool.rb
|
87
88
|
- lib/modular_tree/properties.rb
|