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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cce648530bed16f5d251c8d84dae0a3c8726f1c688fbfdbf3723a39fe9f3e696
4
- data.tar.gz: e0f31869dab8d231f63d2c075661e2e4ead17576d258bcc6bd19a643a659e35b
3
+ metadata.gz: 58a162c50ba7d2b9d21ca0ff2d62e34acd0c46d2666b596ceffcceb2dc4215c0
4
+ data.tar.gz: 61514fa0fa904ea71540c0f9d4776ea75d5405aa5a1b73d32e051c29606b5fdb
5
5
  SHA512:
6
- metadata.gz: a2e76c6c010157ba7aff6098e44c9cde7958d288b0bb7ea41a69201ab1783763da31f657c52156a2991ac02fe56e48d31acdb3fb71749827eb126b5a363416e5
7
- data.tar.gz: 908fa035b142fea947303b3306b38f3443e782d4a182f158d6674cc8396a704fb45cc68b4533ce61ded2e98a7fe57c2fd23716706df56b0a7254aa8f5dd35539
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 type when `a` matches regular expression `re`
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).
@@ -42,7 +42,7 @@ class Factbase::Looged
42
42
 
43
43
  def insert
44
44
  f = @fb.insert
45
- @loog.debug('Inserted new fact')
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 '#{@expr}'")
112
+ @loog.debug("Nothing found by '#{q}'")
111
113
  else
112
- @loog.debug("Found #{r} fact(s) by '#{@expr}'")
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 '#{@expr}'")
125
+ @loog.debug("Nothing found by '#{q}'")
124
126
  else
125
- @loog.debug("Found #{array.size} fact(s) by '#{@expr}'")
127
+ @loog.debug("Found #{array.size} fact(s) by '#{q}'")
126
128
  end
127
129
  array
128
130
  end
@@ -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
- r = @operands[0].evaluate(fact)
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
- @operands.each do |o|
81
- r = o.evaluate(fact)
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
- @operands.each do |o|
90
- r = o.evaluate(fact)
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
@@ -29,7 +29,7 @@ require 'yaml'
29
29
  # License:: MIT
30
30
  class Factbase
31
31
  # Current version of the gem (changed by .rultor.yml on every release)
32
- VERSION = '0.0.29'
32
+ VERSION = '0.0.30'
33
33
 
34
34
  # Constructor.
35
35
  def initialize(facts = [])
@@ -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 2 fact(s) by \'(not (exists bar))\''
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
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.29
4
+ version: 0.0.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko