factbase 0.0.29 → 0.0.30
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/README.md +14 -1
- data/lib/factbase/looged.rb +7 -5
- data/lib/factbase/query.rb +1 -1
- data/lib/factbase/term.rb +59 -11
- data/lib/factbase.rb +1 -1
- data/test/factbase/test_fact.rb +8 -0
- data/test/factbase/test_looged.rb +19 -2
- data/test/factbase/test_query.rb +4 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58a162c50ba7d2b9d21ca0ff2d62e34acd0c46d2666b596ceffcceb2dc4215c0
|
4
|
+
data.tar.gz: 61514fa0fa904ea71540c0f9d4776ea75d5405aa5a1b73d32e051c29606b5fdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b3841073dba72e3fcc6e1fd089235d93829264504447557b3b23f6eb3fa74a4ebccad0d27062af84dc00ab115ca06d9dcc9621bf245d54e09b121bb21983737
|
7
|
+
data.tar.gz: aed653f93b7505db410e81d9fefd8fe9d38fc6b89947f89c952992bcdd76dab1d5f9e4a8cd941df426cb7b200855f26fb2981a9698ac35e8e6ea682c693787e7
|
data/README.md
CHANGED
@@ -16,6 +16,14 @@ A fact is simply a map of properties and values.
|
|
16
16
|
The values are either atomic literals or non-empty sets of literals.
|
17
17
|
It is possible to delete a fact, but impossible to delete a property from a fact.
|
18
18
|
|
19
|
+
**ATTENTION**: The current implemention is naive and,
|
20
|
+
because of that, very slow. I will be very happy
|
21
|
+
if you suggest a better implementation without the change of the interface.
|
22
|
+
The `Factbase::query()` method is what mostly needs performance optimization:
|
23
|
+
currently it simply iterates through all facts in the factbase in order
|
24
|
+
to find those that match the provided terms. Obviously,
|
25
|
+
even a simple indexing may significantly increase performance.
|
26
|
+
|
19
27
|
Here is how you use it (it's thread-safe, by the way):
|
20
28
|
|
21
29
|
```ruby
|
@@ -60,9 +68,14 @@ All terms available in a query:
|
|
60
68
|
* `(gt a b)` returns true if `a` is greater than `b`
|
61
69
|
* `(size k)` returns cardinality of `k` property (zero if property is absent)
|
62
70
|
* `(type a)` returns type of `a` ("String", "Integer", "Float", or "Time")
|
63
|
-
* `(matches a re)` returns
|
71
|
+
* `(matches a re)` returns true when `a` matches regular expression `re`
|
64
72
|
* `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
|
65
73
|
|
74
|
+
There are also terms that match the entire factbase:
|
75
|
+
|
76
|
+
* `(max k)` returns true if the value of `k` property is the largest in the entire factbase
|
77
|
+
* `(min k)` returns true if the value of `k` is the smallest
|
78
|
+
|
66
79
|
## How to contribute
|
67
80
|
|
68
81
|
Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
|
data/lib/factbase/looged.rb
CHANGED
@@ -42,7 +42,7 @@ class Factbase::Looged
|
|
42
42
|
|
43
43
|
def insert
|
44
44
|
f = @fb.insert
|
45
|
-
@loog.debug(
|
45
|
+
@loog.debug("Inserted new fact ##{@fb.size}")
|
46
46
|
Fact.new(f, @loog)
|
47
47
|
end
|
48
48
|
|
@@ -79,6 +79,7 @@ class Factbase::Looged
|
|
79
79
|
v = args[1]
|
80
80
|
s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
|
81
81
|
s = v.to_s.inspect if v.is_a?(String)
|
82
|
+
s = "#{s[0..40]}...#{s[-40..]}" if s.length > 80
|
82
83
|
@loog.debug("Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
|
83
84
|
r
|
84
85
|
end
|
@@ -103,13 +104,14 @@ class Factbase::Looged
|
|
103
104
|
end
|
104
105
|
|
105
106
|
def each(&)
|
107
|
+
q = Factbase::Syntax.new(@expr).to_term.to_s
|
106
108
|
if block_given?
|
107
109
|
r = @query.each(&)
|
108
110
|
raise ".each of #{@query.class} returned #{r.class}" unless r.is_a?(Integer)
|
109
111
|
if r.zero?
|
110
|
-
@loog.debug("Nothing found by '#{
|
112
|
+
@loog.debug("Nothing found by '#{q}'")
|
111
113
|
else
|
112
|
-
@loog.debug("Found #{r} fact(s) by '#{
|
114
|
+
@loog.debug("Found #{r} fact(s) by '#{q}'")
|
113
115
|
end
|
114
116
|
r
|
115
117
|
else
|
@@ -120,9 +122,9 @@ class Factbase::Looged
|
|
120
122
|
end
|
121
123
|
# rubocop:enable Style/MapIntoArray
|
122
124
|
if array.empty?
|
123
|
-
@loog.debug("Nothing found by '#{
|
125
|
+
@loog.debug("Nothing found by '#{q}'")
|
124
126
|
else
|
125
|
-
@loog.debug("Found #{array.size} fact(s) by '#{
|
127
|
+
@loog.debug("Found #{array.size} fact(s) by '#{q}'")
|
126
128
|
end
|
127
129
|
array
|
128
130
|
end
|
data/lib/factbase/query.rb
CHANGED
@@ -40,7 +40,7 @@ class Factbase::Query
|
|
40
40
|
# @return [Integer] Total number of facts yielded
|
41
41
|
def each
|
42
42
|
return to_enum(__method__) unless block_given?
|
43
|
-
term = Factbase::Syntax.new(@query).to_term
|
43
|
+
term = Factbase::Syntax.new(@query).to_term.on(@maps)
|
44
44
|
yielded = 0
|
45
45
|
@maps.each do |m|
|
46
46
|
f = Factbase::Fact.new(@mutex, m)
|
data/lib/factbase/term.rb
CHANGED
@@ -45,6 +45,18 @@ class Factbase::Term
|
|
45
45
|
send(@op, fact)
|
46
46
|
end
|
47
47
|
|
48
|
+
# Put it into the context: let it see the entire array of maps.
|
49
|
+
# @param [Array] maps The maps
|
50
|
+
# @return [Factbase::Term] Itself
|
51
|
+
def on(maps)
|
52
|
+
m = "#{@op}_on"
|
53
|
+
send(m, maps) if respond_to?(m, true)
|
54
|
+
@operands.each do |o|
|
55
|
+
o.on(maps) if o.is_a?(Factbase::Term)
|
56
|
+
end
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
48
60
|
# Turns it into a string.
|
49
61
|
# @return [String] The string of it
|
50
62
|
def to_s
|
@@ -71,25 +83,19 @@ class Factbase::Term
|
|
71
83
|
|
72
84
|
def not(fact)
|
73
85
|
assert_args(1)
|
74
|
-
|
75
|
-
raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
|
76
|
-
!r
|
86
|
+
!only_bool(the_value(0, fact))
|
77
87
|
end
|
78
88
|
|
79
89
|
def or(fact)
|
80
|
-
|
81
|
-
|
82
|
-
raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
|
83
|
-
return true if r
|
90
|
+
(0..@operands.size - 1).each do |i|
|
91
|
+
return true if only_bool(the_value(i, fact))
|
84
92
|
end
|
85
93
|
false
|
86
94
|
end
|
87
95
|
|
88
96
|
def and(fact)
|
89
|
-
|
90
|
-
|
91
|
-
raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
|
92
|
-
return false unless r
|
97
|
+
(0..@operands.size - 1).each do |i|
|
98
|
+
return false unless only_bool(the_value(i, fact))
|
93
99
|
end
|
94
100
|
true
|
95
101
|
end
|
@@ -174,6 +180,26 @@ class Factbase::Term
|
|
174
180
|
true
|
175
181
|
end
|
176
182
|
|
183
|
+
def min(fact)
|
184
|
+
vv = the_value(0, fact)
|
185
|
+
return nil if vv.nil?
|
186
|
+
vv.any? { |v| v == @min }
|
187
|
+
end
|
188
|
+
|
189
|
+
def max(fact)
|
190
|
+
vv = the_value(0, fact)
|
191
|
+
return nil if vv.nil?
|
192
|
+
vv.any? { |v| v == @max }
|
193
|
+
end
|
194
|
+
|
195
|
+
def max_on(maps)
|
196
|
+
@max = best(maps) { |v, b| v > b }
|
197
|
+
end
|
198
|
+
|
199
|
+
def min_on(maps)
|
200
|
+
@min = best(maps) { |v, b| v < b }
|
201
|
+
end
|
202
|
+
|
177
203
|
def assert_args(num)
|
178
204
|
c = @operands.size
|
179
205
|
raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
|
@@ -195,4 +221,26 @@ class Factbase::Term
|
|
195
221
|
v = [v] unless v.is_a?(Array)
|
196
222
|
v
|
197
223
|
end
|
224
|
+
|
225
|
+
def only_bool(val)
|
226
|
+
val = val[0] if val.is_a?(Array)
|
227
|
+
return false if val.nil?
|
228
|
+
raise "Boolean expected, while #{val.class} received" unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
229
|
+
val
|
230
|
+
end
|
231
|
+
|
232
|
+
def best(maps)
|
233
|
+
k = @operands[0]
|
234
|
+
raise "A symbol expected, but provided: #{k}" unless k.is_a?(Symbol)
|
235
|
+
best = nil
|
236
|
+
maps.each do |m|
|
237
|
+
vv = m[k.to_s]
|
238
|
+
next if vv.nil?
|
239
|
+
vv = [vv] unless vv.is_a?(Array)
|
240
|
+
vv.each do |v|
|
241
|
+
best = v if best.nil? || yield(v, best)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
best
|
245
|
+
end
|
198
246
|
end
|
data/lib/factbase.rb
CHANGED
data/test/factbase/test_fact.rb
CHANGED
@@ -84,6 +84,14 @@ class TestFact < Minitest::Test
|
|
84
84
|
assert_equal(42, f.foo_bar, f.to_s)
|
85
85
|
end
|
86
86
|
|
87
|
+
def test_set_twice_same_value
|
88
|
+
map = {}
|
89
|
+
f = Factbase::Fact.new(Mutex.new, map)
|
90
|
+
f.foo = 42
|
91
|
+
f.foo = 42
|
92
|
+
assert_equal(42, map['foo'])
|
93
|
+
end
|
94
|
+
|
87
95
|
def test_time_in_utc
|
88
96
|
f = Factbase::Fact.new(Mutex.new, {})
|
89
97
|
t = Time.now
|
@@ -69,13 +69,30 @@ class TestLooged < Minitest::Test
|
|
69
69
|
fb.insert
|
70
70
|
fb.insert.bar = 3
|
71
71
|
fb.insert
|
72
|
+
fb.insert.str =
|
73
|
+
"Он поскорей звонит. Вбегает
|
74
|
+
К нему слуга француз Гильо,
|
75
|
+
Халат и туфли предлагает
|
76
|
+
И подает ему белье.
|
77
|
+
Спешит Онегин одеваться,
|
78
|
+
Слуге велит приготовляться
|
79
|
+
С ним вместе ехать и с собой
|
80
|
+
Взять также ящик боевой.
|
81
|
+
Готовы санки беговые.
|
82
|
+
Он сел, на мельницу летит.
|
83
|
+
Примчались. Он слуге велит
|
84
|
+
Лепажа стволы роковые
|
85
|
+
Нести за ним, а лошадям
|
86
|
+
Отъехать в поле к двум дубкам."
|
72
87
|
fb.query('(exists bar)').each(&:to_s)
|
73
88
|
fb.query('(not (exists bar))').delete!
|
74
89
|
[
|
75
|
-
'Inserted new fact',
|
90
|
+
'Inserted new fact #1',
|
91
|
+
'Inserted new fact #2',
|
76
92
|
'Set \'bar\' to 3 (Integer)',
|
93
|
+
'Set \'str\' to "Он поскорей звонит. Вбегает\n К нем...м\n Отъехать в поле к двум дубкам." (String)',
|
77
94
|
'Found 1 fact(s) by \'(exists bar)\'',
|
78
|
-
'Deleted
|
95
|
+
'Deleted 3 fact(s) by \'(not (exists bar))\''
|
79
96
|
].each do |s|
|
80
97
|
assert(log.to_s.include?("#{s}\n"), "#{log}\n")
|
81
98
|
end
|
data/test/factbase/test_query.rb
CHANGED
@@ -60,6 +60,10 @@ class TestQuery < Minitest::Test
|
|
60
60
|
'(eq (size hello) 0)' => 3,
|
61
61
|
'(eq num pi)' => 0,
|
62
62
|
'(absent time)' => 2,
|
63
|
+
'(max num)' => 1,
|
64
|
+
'(and (exists time) (max num))' => 0,
|
65
|
+
'(and (exists pi) (max num))' => 1,
|
66
|
+
'(min time)' => 1,
|
63
67
|
'(and (absent time) (exists pi))' => 1,
|
64
68
|
"(and (exists time) (not (\t\texists pi)))" => 1,
|
65
69
|
"(or (eq num 66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
|