valkey-objects 0.4.8 → 0.5.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.
@@ -1,813 +1,576 @@
1
- # frozen_string_literal: true
1
+ require 'redis'
2
2
 
3
- require_relative "objects/version"
4
-
5
- require 'connection_pool'
6
- require 'redis-client'
7
- require 'json'
8
- require 'amatch'
9
- require 'ap'
10
- require 'yaml'
11
- require 'classifier-reborn'
12
- require 'knn'
13
- require 'sentimental'
14
- require 'tokenizer'
3
+ module ValkeyObjects
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def valkey
10
+ @valkey ||= Redis.new
11
+ end
12
+
13
+ def valkey=(client)
14
+ @valkey = client
15
+ end
15
16
 
16
- module VK
17
+ def knn *a
18
+ KNN.new([a].flatten.compact)
19
+ end
17
20
 
21
+ # Define a value (string) attribute
22
+ def value(name, options = {})
23
+ key_name = options[:key] || name.to_s
18
24
 
19
- @@XX = {}
20
-
21
- def self.included(x)
22
- x.extend VK
23
- end
24
-
25
- def self.extended(x)
26
- ##
27
- # Example:
28
- # class ExmpleObject
29
- # include VK
30
- #
31
- ## Add Object Containers by Value Type:
32
- # value :myValue
33
- # counter :myCounter
34
- # hashkey :myHashKey
35
- # sortedset :mySortedSet
36
- # set :mySet
37
- # queue :myQueue
38
- # place :myPlace
39
- # toggle :myToggle
40
- # ticker :myTicker
41
- # entry :myEntry
42
- #
43
- ## Add an Object Container With an Implicit Expiration:
44
- # value :myExpiringValue, ttl: (seconds to live without interaction)
45
- #
46
- # def initialize k
47
- # @id = k
48
- # end
49
- # end
50
- ##
51
- # Create the Object:
52
- # @obj = ExampleObject.new('object id...')
53
- ##
54
- # For all object methods:
55
- # @obj.myObjectContainer.expire(seconds)
56
- # @obj.myObjectContainer.delete!
57
- #
58
- xx = x.name.gsub("::", "-")
59
- ##
60
- # Object Method Types:
61
- ##
62
- # A String Value
63
- ##
64
- # value :myValue
65
- # @obj.myValue
66
- # @obj.myValue.exist?
67
- # @obj.myValue.value = "my value"
68
- # @obj.myValue.value => "my value"
69
- define_method(:value) { |k, h={}| define_method(k.to_sym) { VALUE.new(%[#{xx}:value:#{k}:#{@id}], h) } };
70
- ##
71
- # A Number Value
72
- ##
73
- # counter :myCounter
74
- # @obj.myCounter
75
- # @obj.myCounter.exist?
76
- # @obj.myCounter.value = number
77
- # @obj.myCounter.value => number
78
- # @obj.myCounter.incr number
79
- # @obj.myCounter.decr number
80
- #
81
- define_method(:counter) { |k, h={}| define_method(k.to_sym) { COUNTER.new(%[#{xx}:counter:#{k}:#{@id}], h); } };
82
- ##
83
- # An Epoch Value
84
- ##
85
- # timestamp :myTimestamp
86
- # @obj.myTimestamp
87
- # @obj.myTimestamp.exist?
88
- # @obj.myTimestamp.value!
89
- # @obj.myTimestamp.value => epoch
90
- # @obj.myTimestamp.ago => Seconds since epoch
91
- # @obj.myTimestamp.to_time => Time object
92
- define_method(:timestamp) { |k, h={}| define_method(k.to_sym) { TIMESTAMP.new(%[#{xx}:timestamp:#{k}:#{@id}], h) } }
93
- ##
94
- # A Hash Value
95
- ##
96
- # hashkey :myHashKey
97
- # @obj.myHashKey
98
- # @obj.myHashKey[:key] = value
99
- # @obj.myHashKey[:key] => "value"
100
- # @obj.myHashKey.update({ key: 'value', ... })
101
- define_method(:hashkey) { |k, h={}| define_method(k.to_sym) { HASH.new(%[#{xx}:hashkey:#{k}:#{@id}], h); } };
102
- ##
103
- # A Sorted Set Value
104
- ##
105
- # sortedset :mySortedSet
106
- # @obj.mySortedSet
107
- # @obj.mySortedSet[:key] = value
108
- # @obj.mySortedSet[:key] => ...
109
- # @obj.mySortedSet.value { |key, i| ... }
110
- # @obj.mySortedSet.poke key, number
111
- #
112
- define_method(:sortedset) { |k, h={}| define_method(k.to_sym) { SORTEDSET.new(%[#{xx}:sortedset:#{k}:#{@id}], h); } };
113
- ##
114
- # A Collection of Values
115
- ##
116
- # set :mySet
117
- # @obj.mySet
118
- # @obj.mySet << "x"
119
- # @obj.myset.rm "x"
120
- # @obj.mySet & @obj.otherSet
121
- # @obj.mySet | @obj.otherSet
122
- # @obj.myset["pattern"]
123
- # @obj.mySet.value { |key, i| ... }
124
- #
125
- define_method(:set) { |k, h={}| define_method(k.to_sym) { SET.new(%[#{xx}:set:#{k}:#{@id}], h); } };
126
- ##
127
- # A List of Values
128
- ##
129
- # queue :myQueue
130
- # @obj.myQueue
131
- # @obj.myQueue << "x"
132
- # @obj.myQueue.front => "x" and pop
133
- # @obj.myQueue.last => last in queue
134
- # @obj.myQueue.value { |key, i| ... }
135
- #
136
- define_method(:queue) { |k, h={}| define_method(k.to_sym) { QUEUE.new(%[#{xx}:queue:#{k}:#{@id}], h); } };
137
- ##
138
- # A Collection of Places
139
- ##
140
- # place :myPlace
141
- # @obj.myPlace
142
- # @obj.myPlace.add "key", longitude, latitude
143
- # @obj.myPlace["key"] => { longitude: xx, latitude: yy }
144
- # @obj.myPlace.distance "key", "other key"
145
- # @obj.myPlace.radius longitude, latitude, distance
146
- # @obj.myPlace.value { |key, i| ... }
147
- #
148
- define_method(:place) { |k, h={}| define_method(k.to_sym) { PLACE.new(%[#{xx}:place:#{k}:#{@id}], h); } };
149
- ##
150
- # A Boolean Value
151
- ##
152
- # toggle :myToggle
153
- # @obj.myToggle
154
- # @obj.myToggle.exist?
155
- # @obj.myToggle.value = bool
156
- # @obj.myToggle.value => ...
157
- # @obj.myToggle.value! => value = !value
158
- #
159
- define_method(:toggle) { |k, h={}| define_method(k.to_sym) { TOGGLE.new(%[#{xx}:toggle:#{k}:#{@id}], h); } };
160
- ##
161
- # A Sorted Hash of Values
162
- ##
163
- # ticker :myTicker
164
- # @obj.myTicker
165
- # @obj.myTicker[:key] = value
166
- # @obj.myTicker[:key] => "value"
167
- # @obj.myticker.value { |i,e| ... }
168
- define_method(:ticker) { |k, h={}| define_method(k.to_sym) { SORTEDHASH.new(%[#{xx}:ticker:#{k}:#{@id}], h); } };
169
- ##
170
- # A List of Hashes
171
- ##
172
- # entry :myEntry
173
- # @obj.myEntry
174
- # @obj.myEntry << { key: 'value', ... }
175
- # @obj.myEntry.value { |i,e| ... }
176
- define_method(:entry) { |k, h={}| define_method(k.to_sym) { HASHLIST.new(%[#{xx}:entry:#{k}:#{@id}], h); } };
177
- ##
178
- # A list of Strings
179
- ##
180
- # vector :myVector
181
- # @obj.myVector
182
- # @obj.myVector << "An Entry of Text."
183
- # @obj.myVector.value { |i,e| ... }
184
- # @obj.myvector[0] = "An Entry of Text."
185
- define_method(:vector) { |k, h={}| define_method(k.to_sym) { VECTOR.new(%[#{xx}:vector:#{k}:#{@id}], h); } };
186
- ##
187
- # An ordered list of strings.
188
- ##
189
- # vector :myCorpus
190
- # @obj.myCorpus
191
- # @obj.myCorpus.value { |i,e| ... }
192
- # @obj.myCorpus["An entry"] => 5
193
- define_method(:corpus) { |k, h={}| define_method(k.to_sym) { CORPUS.new(%[#{xx}:corpus:#{k}:#{@id}], h); } };
194
- ##
195
- # An ordered list of strings.
196
- ##
197
- # book :myBook
198
- # @obj.myBook
199
- # @obj.myBook.value { |index,entry,embedding| ... }
200
- # @obj.mybook.near("Some entry") => [...]
201
- # @obj.myBook["An entry"] => 5
202
- define_method(:book) { |k, h={}| define_method(k.to_sym) { BOOK.new(%[#{xx}:book:#{k}:#{@id}], h); } };
203
- end
204
-
205
- def id
206
- @id
207
- end
208
-
209
- def self.at epoch
210
- Time.at epoch
211
- end
212
-
213
- def self.clock *t
214
- if t[0]
215
- Time.now.utc.to_i - t[0].to_i
216
- else
217
- Time.now.utc.to_i
25
+ define_method(name) do
26
+ ValkeyObjects::Value.new("#{self.class.name.downcase}:#{@id}:value:#{key_name}", self.class.valkey)
27
+ end
218
28
  end
219
- end
220
-
221
- def self.redis
222
- @redis ||= ConnectionPool::Wrapper.new do
223
- RedisClient.config(host: "127.0.0.1", port: 6379, db: 0).new_client
29
+
30
+ # Define a counter attribute
31
+ def counter(name, options = {})
32
+ key_name = options[:key] || name.to_s
33
+
34
+ define_method(name) do
35
+ ValkeyObjects::Counter.new("#{self.class.name.downcase}:#{@id}:counter:#{key_name}", self.class.valkey)
36
+ end
224
37
  end
225
- end
226
-
227
- @@SENTIMENT_THRESHOLD = 0.9
228
- ##
229
- # Bayesean Classification
230
- #
231
- # Determine the A OR B of an example.
232
- ##
233
- # VK.classify("catagory A", "Catagory B").
234
- def self.classify(a, b)
235
- ClassifierReborn::Bayes.new(a, b)
236
- end
237
-
238
- def self.threshold n
239
- @@SENTIMENT_THRESHOLD = n
240
- end
241
- ##
242
- # Sentiment Analysis.
243
- ##
244
- # VK.feels("Some text")
245
- def self.feels x
246
- Sentimental.new(threshold: @@SENTIMENT_THRESHOLD).sentiment
247
- end
248
-
249
- @@CRi = Hash.new { |h,k| h[k] = CRi.new(k) }
250
- class CRi
251
- def initialize k
252
- @id = k
253
- @lsi = ClassifierReborn::LSI.new
38
+
39
+ # Define a list attribute
40
+ def list(name, options = {})
41
+ key_name = options[:key] || name.to_s
42
+
43
+ define_method(name) do
44
+ ValkeyObjects::List.new("#{self.class.name.downcase}:#{@id}:list:#{key_name}", self.class.valkey)
45
+ end
254
46
  end
255
- def learn x, i
256
- @lsi.add_item x.to_s, i.to_sym
47
+
48
+ # Define a set attribute
49
+ def set(name, options = {})
50
+ key_name = options[:key] || name.to_s
51
+
52
+ define_method(name) do
53
+ ValkeyObjects::Set.new("#{self.class.name.downcase}:#{@id}:set:#{key_name}", self.class.valkey)
54
+ end
257
55
  end
258
- def [] k
259
- @lsi.classify k
56
+
57
+ # Define a hash attribute
58
+ def hash_key(name, options = {})
59
+ key_name = options[:key] || name.to_s
60
+
61
+ define_method(name) do
62
+ ValkeyObjects::HashKey.new("#{self.class.name.downcase}:#{@id}:hash:#{key_name}", self.class.valkey)
63
+ end
260
64
  end
261
- def search x, *n
262
- @lsi.search(x, n[0])
65
+
66
+ # Define a sorted set attribute
67
+ def sorted_set(name, options = {})
68
+ key_name = options[:key] || name.to_s
69
+
70
+ define_method(name) do
71
+ ValkeyObjects::SortedSet.new("#{self.class.name.downcase}:#{@id}:sorted_set:#{key_name}", self.class.valkey)
72
+ end
263
73
  end
264
74
  end
265
- ##
266
- # Vector Indexing
267
- #
268
- # Vector (of text) -> concept (index)
269
- # - Find text concept based upon known examples.
270
- # - Learn new examples of a concept.
271
- # - Find examples from within clusters.
272
- ##
273
- # index[:vector]['Text to classify'] => :index
274
- # index[:vector].learn("Text to classify", :index)
275
- # index[:vector].search("Similar text.", 5) => ["Similar text?", "other similar text", ...]
276
- def self.index
277
- @@CRi
278
- end
279
- ##
280
- # Vector Engine
281
- #
282
- # Return known string based upon nearest neighbor by vector of number.
283
- ##
284
- # vector([["return val", 0, 1, 2, ...], ...]).classify(["guess", 0, 1, 2, ...]) => "return val"
285
- def self.vector vectors, size
286
- Knn::Classifier.new(vectors, SquaredEuclideanCalculator)
75
+
76
+ # generic KNN processor
77
+ class KNN
78
+ # Initialize with a collection of strings
79
+ def initialize(collection)
80
+ @collection = collection.flatten.compact
81
+ end
82
+
83
+ # Find k nearest neighbors to the query string
84
+ # Returns array of hashes with :string and :distance keys
85
+ def hood(query)
86
+ # Calculate distances for all strings
87
+ distances = @collection.map do |str|
88
+ { string: str, distance: levenshtein_distance(query, str) }
89
+ end
90
+
91
+ # Sort by distance (ascending) and take top k
92
+ distances.sort_by { |item| item[:distance] }.take([10, @collection.size].min)
93
+ end
94
+
95
+ def rank query
96
+ neighbors = hood(query)
97
+
98
+ neighbors.map do |item|
99
+ max_len = [query.length, item[:string].length].max
100
+ similarity = max_len.zero? ? 1.0 : 1.0 - (item[:distance].to_f / max_len)
101
+ dist = levenshtein_distance(item[:string], query)
102
+ { string: item[:string], similarity: similarity.round(4), distance: dist.round(4) }
103
+ end.sort_by { |h| h[:distance] && -h[:similarity] }
104
+ end
105
+
106
+ def [] q
107
+ rank(q)[0][:string]
108
+ end
109
+
110
+ private
111
+
112
+ # Calculate Levenshtein distance between two strings
113
+ def levenshtein_distance(s1, s2)
114
+ return s2.length if s1.empty?
115
+ return s1.length if s2.empty?
116
+
117
+ # Create distance matrix
118
+ d = Array.new(s1.length + 1) { Array.new(s2.length + 1) }
119
+
120
+ # Initialize first row and column
121
+ (0..s1.length).each { |i| d[i][0] = i }
122
+ (0..s2.length).each { |j| d[0][j] = j }
123
+
124
+ # Fill in the rest of the matrix
125
+ (1..s1.length).each do |i|
126
+ (1..s2.length).each do |j|
127
+ cost = s1[i - 1] == s2[j - 1] ? 0 : 1
128
+
129
+ d[i][j] = [
130
+ d[i - 1][j] + 1, # deletion
131
+ d[i][j - 1] + 1, # insertion
132
+ d[i - 1][j - 1] + cost # substitution
133
+ ].min
134
+ end
135
+ end
136
+
137
+ d[s1.length][s2.length]
138
+ end
287
139
  end
288
140
 
289
- class O
290
- attr_reader :key
291
- def initialize k, h={}
292
- @key = k
293
- @opts = h
294
- # puts %[VK #{@key} #{h}]
295
- if @opts.has_key?(:ttl)
296
- expire @opts[:ttl]
297
- end
141
+ # Value (String) wrapper
142
+ class Value
143
+ attr_reader :key, :valkey
144
+
145
+ def initialize(key, valkey)
146
+ @key = key
147
+ @valkey = valkey
148
+ end
149
+
150
+ def value
151
+ valkey.get(key)
298
152
  end
299
- def delete!
300
- VK.redis.call("DEL", key);
153
+
154
+ def value=(val)
155
+ valkey.set(key, val)
301
156
  end
302
- def expire sec
303
- VK.redis.call("EXPIRE", key, sec);
157
+
158
+ def delete
159
+ valkey.del(key)
160
+ end
161
+
162
+ def exists?
163
+ valkey.exists?(key)
304
164
  end
305
- def value
306
- x = VK.redis.call("GET", key);
307
- if @opts.has_key?(:flush) == true
308
- delete!
309
- end
310
- return x
311
- end
312
- end
313
165
 
314
- class TIMESTAMP < O
315
- def value!
316
- VK.redis.call("SET", key, "#{VK.clock.to_i}");
166
+ def expire(seconds)
167
+ valkey.expire(key, seconds)
317
168
  end
318
- def exist?
319
- VK.redis.call("GET", key) ? true : false
169
+
170
+ def expireat(unix_timestamp)
171
+ valkey.expireat(key, unix_timestamp)
320
172
  end
321
- def ago
322
- VK.clock.to_i - value.to_i;
173
+
174
+ def ttl
175
+ valkey.ttl(key)
323
176
  end
324
- def to_time
325
- Time.at(value);
177
+
178
+ def persist
179
+ valkey.persist(key)
180
+ end
181
+
182
+ def to_s
183
+ value.to_s
326
184
  end
327
185
  end
328
186
 
329
- class TOGGLE < O
330
- def value
331
- x = VK.redis.call("GET", key) == 'true' ? true : false
332
- if @opts.has_key?(:flush) == true
333
- delete!
334
- end
335
- return x
187
+ # Counter wrapper
188
+ class Counter
189
+ attr_reader :key, :valkey
190
+
191
+ def initialize(key, valkey)
192
+ @key = key
193
+ @valkey = valkey
336
194
  end
337
- def exist?
338
- VK.redis.call("GET", key) ? true : false
339
- end
195
+
340
196
  def value= x
341
- VK.redis.call("SET", key, "#{x.to_s}")
197
+ valkey.set(key, x)
342
198
  end
343
- def value!
344
- if self.value == true || self.value == nil
345
- self.value = false
346
- else
347
- self.value = true
348
- end
349
- end
350
- end
351
-
352
- class VALUE < O
199
+
353
200
  def value
354
- x = VK.redis.call("GET", key)
355
- if @opts.has_key?(:flush) == true
356
- delete!
357
- end
358
- return x
201
+ val = valkey.get(key)
202
+ val ? val.to_i : 0
359
203
  end
360
- def value= x
361
- VK.redis.call("SET", key, x)
204
+
205
+ def increment(by = 1)
206
+ valkey.incrby(key, by)
362
207
  end
363
- def exist?
364
- VK.redis.call("GET", key) ? true : false
208
+
209
+ def decrement(by = 1)
210
+ valkey.decrby(key, by)
365
211
  end
366
- def match r, &b
367
- m = Regexp.new(r).match(value)
368
- if block_given?
369
- b.call(m)
370
- else
371
- return m
372
- end
212
+
213
+ def reset
214
+ valkey.set(key, 0)
215
+ end
216
+
217
+ def delete
218
+ valkey.del(key)
373
219
  end
374
- end
375
220
 
376
- class VECTOR < O
377
- include Amatch
378
- def value &b
379
- a = []
380
- VK.redis.call("LRANGE", key, 0, -1).each_with_index { |e, i|
381
- if block_given?
382
- a << b.call(i, VK.redis.call("GET", e))
383
- else
384
- a << VK.redis.call("GET", e)
385
- end
386
- if @opts.has_key?(:flush) == true
387
- VK.redis.call("DEL", e);
388
- end
389
- }
390
- if @opts.has_key?(:flush) == true
391
- delete!
392
- end
393
- return a
394
- end
395
- def [] k
396
- VK.redis.call("GET", "#{@key}-#{k}");
397
- end
398
- def << i
399
- kk = %[#{@key}-#{VK.redis.call("LLEN",@key)}]
400
- VK.redis.call("SET", kk, i);
401
- VK.redis.call("RPUSH", key, kk)
402
- end
403
- def []= k,v
404
- kk = %[#{@key}-#{k}]
405
- VK.redis.call("SET", kk, v)
406
- VK.redis.call("RPUSH", key, kk);
407
- end
408
- def nearest p
409
- h = {}
410
- value { |i,v|
411
- h[i] = {
412
- value: v,
413
- levenshtein: p.levenshtein_similar(v),
414
- damerau: p.damerau_levenshtein_similar(v),
415
- hamming: p.hamming_similar(v),
416
- distance: p.pair_distance_similar(v),
417
- subsequence: p.longest_subsequence_similar(v),
418
- substring: p.longest_substring_similar(v),
419
- jaro: p.jaro_similar(v),
420
- winkler: p.jarowinkler_similar(v)
421
- }
422
- }
423
- return h
424
- end
425
- end
426
-
427
- class COUNTER < O
428
- def incr n
429
- VK.redis.call("SET", @key, value + n.to_f)
221
+ def expire(seconds)
222
+ valkey.expire(key, seconds)
430
223
  end
431
- def decr n
432
- VK.redis.call("SET", @key, value - n.to_f)
433
- end
434
- def value
435
- x = VK.redis.call("GET", @key).to_f
436
- if @opts.has_key?(:flush) == true
437
- delete!
438
- end
439
- return x
224
+
225
+ def expireat(unix_timestamp)
226
+ valkey.expireat(key, unix_timestamp)
227
+ end
228
+
229
+ def ttl
230
+ valkey.ttl(key)
440
231
  end
441
- def value= n
442
- VK.redis.call("SET", @key, n.to_f)
232
+
233
+ def persist
234
+ valkey.persist(key)
235
+ end
236
+
237
+ def to_i
238
+ value.to_i
443
239
  end
444
- def exist?
445
- VK.redis.call("GET", @key) ? true : false
446
- end
447
240
  end
448
241
 
449
- class HASH < O
450
- def [] k
451
- VK.redis.call("HGET", key, k);
242
+ # List wrapper
243
+ class List
244
+ include Enumerable
245
+ attr_reader :key, :valkey
246
+
247
+ def initialize(key, valkey)
248
+ @key = key
249
+ @valkey = valkey
250
+ end
251
+
252
+ def <<(value)
253
+ valkey.rpush(key, value)
254
+ end
255
+
256
+ def push(*values)
257
+ valkey.rpush(key, values)
258
+ end
259
+
260
+ def unshift(*values)
261
+ valkey.lpush(key, values.reverse)
262
+ end
263
+
264
+ def pop
265
+ valkey.rpop(key)
266
+ end
267
+
268
+ def shift
269
+ valkey.lpop(key)
270
+ end
271
+
272
+ def [](index)
273
+ valkey.lindex(key, index)
274
+ end
275
+
276
+ def []=(index, value)
277
+ valkey.lset(key, index, value)
278
+ end
279
+
280
+ def length
281
+ valkey.llen(key)
452
282
  end
453
- def []= k,v
454
- VK.redis.call("HSET", key, k, v);
283
+ alias_method :size, :length
284
+
285
+ def range(start_idx, end_idx)
286
+ valkey.lrange(key, start_idx, end_idx)
455
287
  end
456
- def update(h={})
457
- h.each_pair { |k,v| VK.redis.call("HSET", key, k, v); }
288
+
289
+ def values
290
+ range(0, -1)
458
291
  end
459
- def to_h
460
- VK.redis.call("HGETALL", key);
292
+
293
+ def each(&block)
294
+ values.each(&block)
295
+ end
296
+
297
+ def expire(seconds)
298
+ valkey.expire(key, seconds)
299
+ end
300
+
301
+ def expireat(unix_timestamp)
302
+ valkey.expireat(key, unix_timestamp)
303
+ end
304
+
305
+ def ttl
306
+ valkey.ttl(key)
307
+ end
308
+
309
+ def persist
310
+ valkey.persist(key)
311
+ end
312
+
313
+ ### KNN ###
314
+ def knn
315
+ KNN.new(values)
316
+ end
317
+
318
+
319
+ def clear
320
+ valkey.del(key)
321
+ end
322
+
323
+ def delete
324
+ clear
461
325
  end
462
326
  end
463
327
 
464
- class QUEUE < O
465
- def value &b
466
- VK.redis.call("LRANGE", key, 0, -1).each_with_index { |e, i| b.call(i, e) }
467
- if @opts.has_key?(:flush) == true
468
- delete!
469
- end
328
+ # Set wrapper
329
+ class Set
330
+ include Enumerable
331
+ attr_reader :key, :valkey
332
+
333
+ def initialize(key, valkey)
334
+ @key = key
335
+ @valkey = valkey
336
+ end
337
+
338
+ def add(value)
339
+ valkey.sadd(key, value)
340
+ end
341
+ alias_method :<<, :add
342
+
343
+ def remove(value)
344
+ valkey.srem(key, value)
345
+ end
346
+
347
+ def member?(value)
348
+ valkey.sismember(key, value)
349
+ end
350
+ alias_method :include?, :member?
351
+
352
+ def members
353
+ valkey.smembers(key)
354
+ end
355
+ alias_method :to_a, :members
356
+
357
+ def size
358
+ valkey.scard(key)
359
+ end
360
+ alias_method :length, :size
361
+
362
+ def each(&block)
363
+ members.each(&block)
470
364
  end
471
- def last
472
- VK.redis.call("LRANGE", key, -1,-1)[0]
365
+
366
+ def expire(seconds)
367
+ valkey.expire(key, seconds)
473
368
  end
474
- def length
475
- VK.redis.call("LLEN", key)
476
- end
477
- def << i
478
- VK.redis.call("RPUSH", key, i)
369
+
370
+ def expireat(unix_timestamp)
371
+ valkey.expireat(key, unix_timestamp)
479
372
  end
480
- def front
481
- VK.redis.call("LPOP", key)
373
+
374
+ def ttl
375
+ valkey.ttl(key)
376
+ end
377
+
378
+ def persist
379
+ valkey.persist(key)
380
+ end
381
+
382
+ ### KNN ###
383
+ def knn
384
+ KNN.new(members)
385
+ end
386
+
387
+ def clear
388
+ valkey.del(key)
389
+ end
390
+
391
+ def delete
392
+ clear
482
393
  end
483
394
  end
484
395
 
485
- class SORTEDSET < O
486
- def value &b
487
- VK.redis.call("ZREVRANGE", key, 0, -1, 'WITHSCORES').each_with_index { |e, i| b.call(i, e) }
488
- if @opts.has_key?(:flush) == true
489
- delete!
490
- end
396
+ # Hash wrapper
397
+ class HashKey
398
+ include Enumerable
399
+ attr_reader :key, :valkey
400
+
401
+ def initialize(key, valkey)
402
+ @key = key
403
+ @valkey = valkey
491
404
  end
492
- def to_h
493
- h = {}
494
- VK.redis.call("ZREVRANGE", key, 0, -1, 'WITHSCORES').each_with_index { |e, i| h[e[0]] = e[1] }
495
- return h
405
+
406
+ def [](field)
407
+ valkey.hget(key, field)
496
408
  end
497
- def [] k
498
- VK.redis.call("ZSCORE", key, k).to_f;
409
+
410
+ def []=(field, value)
411
+ valkey.hset(key, field, value)
499
412
  end
500
- def []= k,v
501
- VK.redis.call("ZADD", key, v, k).to_f;
502
- end
503
- def poke k, n
504
- VK.redis.call("ZINCRBY", key, n.to_f, k);
413
+
414
+ def fetch(field, default = nil)
415
+ valkey.hget(key, field) || default
505
416
  end
506
- end
507
417
 
508
- class SET < O
509
- def value &b
510
- a = Set.new
511
- VK.redis.call("SMEMBERS", key).each_with_index { |e, i|
512
- if block_given?
513
- a << b.call(i, e)
514
- else
515
- a << e
516
- end
517
- }
518
- if @opts.has_key?(:flush) == true
519
- delete!
520
- end
521
- return a
418
+ def delete(field)
419
+ valkey.hdel(key, field)
522
420
  end
523
- def include? k
524
- if VK.redis.call("SMISMEMBER", key, k)[0] == 0
525
- return false
526
- else
527
- return true
528
- end
421
+
422
+ def key?(field)
423
+ valkey.hexists(key, field)
529
424
  end
530
- def length
531
- VK.redis.call("SCARD", key)
425
+ alias_method :has_key?, :key?
426
+
427
+ def keys
428
+ valkey.hkeys(key)
532
429
  end
533
- def << i
534
- VK.redis.call("SADD", key, i)
430
+
431
+ def values
432
+ valkey.hvals(key)
535
433
  end
536
- def rm i
537
- VK.redis.call("SREM", key, i)
538
- end
539
- def & k
540
- VK.redis.call("SINTER", key, k.key)
434
+
435
+ def all
436
+ valkey.hgetall(key)
541
437
  end
542
- def | k
543
- VK.redis.call("SUNION", key, k.key)
544
- end
545
- def [] k
546
- r, h = Regexp.new(k), {}
547
- VK.redis.call("SMEMBERS", key).each { |e| if m = r.match(e); h[e] = m; end; }
548
- return h
438
+ alias_method :to_h, :all
439
+
440
+ def size
441
+ valkey.hlen(key)
549
442
  end
550
- def to_a
551
- VK.redis.call("SMEMBERS", key)
443
+ alias_method :length, :size
444
+
445
+ def expire(seconds)
446
+ valkey.expire(key, seconds)
552
447
  end
553
- end
554
-
555
- class PLACE < O
556
- def value &b
557
- a = []
558
- VK.redis.call("ZRANGE", key, 0, -1).each_with_index { |e, i|
559
- if block_given?
560
- a << b.call(i, e)
561
- else
562
- a << e
563
- end
564
- };
565
- if @opts.has_key?(:flush) == true
566
- delete!
567
- end
568
- return a
448
+
449
+ def expireat(unix_timestamp)
450
+ valkey.expireat(key, unix_timestamp)
569
451
  end
570
- def add i, lon, lat
571
- VK.redis.call("GEOADD", key, lon, lat, i)
452
+
453
+ def ttl
454
+ valkey.ttl(key)
572
455
  end
573
- def [] i
574
- x = VK.redis.call("GEOPOS", key, i)[0];
575
- return { longitude: x[0], latitude: x[1] }
456
+
457
+ def persist
458
+ valkey.persist(key)
576
459
  end
577
- def distance a, b
578
- VK.redis.call("GEODIST", key, a, b, 'm').to_f;
460
+
461
+ def each(&block)
462
+ all.each(&block)
579
463
  end
580
- def radius lon, lat, r
581
- h = {}
582
- VK.redis.call("GEORADIUS", key, lon, lat, r, 'm', 'WITHDIST').each { |e| h[e[0]] = e[1].to_f };
583
- return h
464
+
465
+ def clear
466
+ valkey.del(key)
584
467
  end
585
468
  end
586
469
 
587
- class SORTEDHASH < O
588
- def value &b
589
- VK.redis.call("ZREVRANGE", key, 0, -1, 'WITHSCORES').each_with_index { |e, i|
590
- kx = %[#{@key}-#{e[0]}]
591
- a = []
592
- if block_given?
593
- b.call(i, { key: e[0], value: VK.redis.call("GET", kx), score: e[1] } )
594
- else
595
- a << { key: e[0], value: VK.redis.call("GET", kx), score: e[1] }
596
- end
597
- if @opts.has_key?(:flush) == true
598
- VK.redis.call("DEL", kx)
599
- end
600
- }
601
- if @opts.has_key?(:flush) == true
602
- delete!
470
+ # Sorted Set wrapper
471
+ class SortedSet
472
+ include Enumerable
473
+ attr_reader :key, :valkey
474
+
475
+ def initialize(key, valkey)
476
+ @key = key
477
+ @valkey = valkey
478
+ end
479
+
480
+ def add(member, score)
481
+ valkey.zadd(key, score, member)
482
+ end
483
+ alias_method :[]=, :add
484
+
485
+ def score(member)
486
+ if !member?(member)
487
+ add(member, 0)
603
488
  end
604
- return a
489
+ return valkey.zscore(key, member).to_f
605
490
  end
606
- def [] k
607
- kx = %[#{@key}-#{k}]
608
- VK.redis.call("GET", kx)
491
+ alias_method :[], :score
492
+
493
+ def remove(member)
494
+ valkey.zrem(key, member)
609
495
  end
610
- def []= k, v
611
- kx = %[#{@key}-#{k}]
612
- VK.redis.call("SET", kx, v)
613
- VK.redis.call("ZINCRBY", key, 1, k)
496
+
497
+ def member?(member)
498
+ !valkey.zscore(key, member).nil?
614
499
  end
615
- end
500
+ alias_method :include?, :member?
616
501
 
617
- class HASHLIST < O
618
- def value &b
619
- a = []
620
- VK.redis.call("LRANGE", key, 0, -1).each_with_index { |e, i|
621
- if block_given?
622
- a << b.call(i, JSON.parse(VK.redis.call("GET", e)))
623
- else
624
- a << JSON.parse(VK.redis.call("GET", e))
625
- end
626
- if @opts.has_key?(:flush) == true
627
- VK.redis.call("DEL", e)
628
- end
629
- }
630
- if @opts.has_key?(:flush) == true
631
- delete!
502
+ def range(start_idx, end_idx, with_scores: true)
503
+ if with_scores
504
+ valkey.zrange(key, start_idx, end_idx, with_scores: true)
505
+ else
506
+ valkey.zrange(key, start_idx, end_idx)
632
507
  end
633
- return a
634
- end
635
- def length
636
- VK.redis.call("LLEN", key)
637
508
  end
638
- def [] k
639
- hx = %[#{key}-#{k}]
640
- JSON.parse(VK.redis.call("GET", hx));
509
+
510
+ def revrange(start_idx, end_idx, with_scores: true)
511
+ if with_scores
512
+ valkey.zrevrange(key, start_idx, end_idx, with_scores: true)
513
+ else
514
+ valkey.zrevrange(key, start_idx, end_idx)
515
+ end
641
516
  end
642
- def push h={}
643
- hx = %[#{key}-#{length}]
644
- VK.redis.call("SET", hx, JSON.generate(h));
645
- VK.redis.call("RPUSH", key, hx)
646
- return hx
517
+
518
+ def members
519
+ range(0, -1)
647
520
  end
648
- def first
649
- VK.redis.call("LRANGE", key, 0, 0)[0]
521
+
522
+ def incr k, n
523
+ add(k, score(k) + n)
650
524
  end
651
- def last
652
- VK.redis.call("LRANGE", key, -1, -1)[0]
525
+
526
+ def decr k, n
527
+ add(k, score(k) - n)
653
528
  end
654
- end
655
529
 
656
- class CORPUS < O
657
- def _set
658
- %[#{key}-sset]
659
- end
660
- def _index
661
- %[#{key}-hash]
662
- end
663
- def length
664
- VK.redis.call("SCARD", _set)
665
- end
666
- def value &b
667
- VK.redis.call("HGETALL", _index).each_pair { |e, i| b.call(i, e) }
668
- end
669
- def [] i
670
- if VK.redis.call("SMISMEMBER", _set, i)[0] == 0
671
- VK.redis.call("SADD", _set, i)
672
- VK.redis.call("HSET", _index, i, length);
673
- end
674
- return VK.redis.call("HGET", _index, i)
675
- end
676
- end
530
+ def up n, *a
531
+ [a].flatten.compact.each { |e| incr(e, n) }
532
+ end
677
533
 
678
- class BOOK < O
679
- def _set
680
- %[#{key}-sset]
534
+ def dn n, *a
535
+ [a].flatten.compact.each { |e| decr(e, n) }
681
536
  end
682
- def _index
683
- %[#{key}-index]
537
+
538
+ ### KNN ###
539
+ def knn
540
+ KNN.new(members.map { |e| e[0] })
684
541
  end
685
- def _sec
686
- %[#{key}-sec]
687
- end
688
- def _embed
689
- %[#{key}-embed]
690
- end
691
- def length
692
- VK.redis.call("SCARD", _set)
693
- end
694
- def value &b
695
- VK.redis.call("HGETALL", _index).each_pair { |e, i| b.call(i.to_i, e) }
696
- end
697
- def entry
698
- Hash.new { |h,k| VK.redis.call("HGET", _sec, k) }
699
- end
700
- def vectors
701
- a = []
702
- value { |i, e| a << [ i, VK.embed(e) ].flatten }
703
- return a
704
- end
705
- def embed
706
- return KNN.new(vectors, :distance_measure => :tanimoto_coefficient)
707
- end
708
- def near g, *n
709
- return embed.nearest_neighbours(VK.embed(g)).map { |e|
710
- { index: e[0], distance: e[1], entry: entry[e[0]] }
711
- }.sort_by { |ee| ee[:distance] }
712
- end
713
- def [] i
714
- if VK.redis.call("SMISMEMBER", _set, i.to_s)[0] == 0
715
- VK.redis.call("HSET", _index, i.to_s, length.to_s);
716
- VK.redis.call("HSET", _sec, length.to_s, i.to_s);
717
- VK.redis.call("SADD", _set, i)
718
- end
719
- return VK.redis.call("HGET", _index, i.to_s).to_i;
720
- end
721
- end
542
+
543
+ def size
544
+ valkey.zcard(key)
545
+ end
546
+ alias_method :length, :size
722
547
 
723
- @@SENTIMENT_THRESHOLD = 0.9
724
- ##
725
- # Bayesean Classification
726
- #
727
- # Determine the A OR B of an example.
728
- ##
729
- # VK.classify("catagory A", "Catagory B").
730
- def self.classify a, b
731
- ClassifierReborn::Bayes.new a, b
732
- end
733
-
734
- def self.threshold= n
735
- @@SENTIMENT_THRESHOLD = n
736
- end
737
- ##
738
- # Sentiment Analysis.
739
- ##
740
- # VK.feels("Some text")
741
- def self.feels x
742
- Sentimental.new(threshold: @@SENTIMENT_THRESHOLD).sentiment
743
- end
744
-
745
- @@CRi = Hash.new { |h,k| h[k] = CRi.new(k) }
746
- class CRi
747
- def initialize k
748
- @id = k
749
- @lsi = ClassifierReborn::LSI.new
750
- end
751
- def learn x, i
752
- @lsi.add_item x.to_s, i.to_sym
753
- end
754
- def [] k
755
- @lsi.classify k
756
- end
757
- def search x, *n
758
- @lsi.search(x, n[0])
759
- end
760
- end
761
- ##
762
- # Vector Indexing
763
- #
764
- # Vector (of text) -> concept (index)
765
- # - Find text concept based upon known examples.
766
- # - Learn new examples of a concept.
767
- # - Find examples from within clusters.
768
- ##
769
- # index[:vector]['Text to classify'] => :index
770
- # index[:vector].learn("Text to classify", :index)
771
- # index[:vector].search("Similar text.", 5) => ["Similar text?", "other similar text", ...]
772
- def self.index
773
- @@CRi
774
- end
775
- @@VI = Hash.new { |h,k| h[k] = [] }
776
- @@VC = Hash.new { |h,k| h[k] = Knn::Classifier.new(@@VI[k], SquaredEuclideanCalculator) }
777
- def self.learn fact, index
778
- @@CRi[index.to_sym].learn fact, index
779
- @@VI[index] << fact
780
- end
781
-
782
- @@TOKENIZER = Tokenizer::WhitespaceTokenizer.new(:en)
783
- def self.tokenize i
784
- @@TOKENIZER.tokenize(i)
785
- end
548
+ def each(&block)
549
+ members.each(&block)
550
+ end
551
+
552
+ def expire(seconds)
553
+ valkey.expire(key, seconds)
554
+ end
786
555
 
787
- @@WORDLIST = CORPUS.new(%[WORDLIST])
788
- def self.wordlist
789
- @@WORDLIST
790
- end
556
+ def expireat(unix_timestamp)
557
+ valkey.expireat(key, unix_timestamp)
558
+ end
791
559
 
792
- def self.embed i
793
- m = VK.tokenize(i).map { |e| VK.wordlist[e].to_f }
794
- return [m, Array.new(2048 - m.length, 0.0)].flatten
795
- end
796
-
797
- def self.flushdb!
798
- VK.redis.call("FLUSHDB")
799
- end
560
+ def ttl
561
+ valkey.ttl(key)
562
+ end
800
563
 
801
- def self.keys k='*'
802
- VK.redis.call("KEYS", k)
803
- end
564
+ def persist
565
+ valkey.persist(key)
566
+ end
567
+
568
+ def clear
569
+ valkey.del(key)
570
+ end
804
571
 
805
- def self.txt f, &b
806
- x = File.read(f).gsub(/\n\n+/,"\n\n").gsub(/ +/, " ").split("\n\n").map { |e| e.gsub(/\n/, " ") }
807
- if block_given?
808
- x.map { |e| b.call(e) }
809
- else
810
- return x
811
- end
572
+ def delete
573
+ clear
574
+ end
812
575
  end
813
576
  end