valkey-objects 0.4.7 → 0.4.9

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,810 +1,572 @@
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)
164
+ end
165
+
166
+ def expire(seconds)
167
+ valkey.expire(key, seconds)
304
168
  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
169
 
314
- class TIMESTAMP < O
315
- def value!
316
- VK.redis.call("SET", key, "#{VK.clock.to_i}");
170
+ def expireat(unix_timestamp)
171
+ valkey.expireat(key, unix_timestamp)
317
172
  end
318
- def exist?
319
- VK.redis.call("GET", key) ? true : false
173
+
174
+ def ttl
175
+ valkey.ttl(key)
320
176
  end
321
- def ago
322
- VK.clock.to_i - value.to_i;
177
+
178
+ def persist
179
+ valkey.persist(key)
323
180
  end
324
- def to_time
325
- Time.at(value);
181
+
182
+ def to_s
183
+ value.to_s
326
184
  end
327
185
  end
328
186
 
329
- class TOGGLE < O
187
+ # Counter wrapper
188
+ class Counter
189
+ attr_reader :key, :valkey
190
+
191
+ def initialize(key, valkey)
192
+ @key = key
193
+ @valkey = valkey
194
+ end
195
+
330
196
  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
197
+ val = valkey.get(key)
198
+ val ? val.to_i : 0
336
199
  end
337
- def exist?
338
- VK.redis.call("GET", key) ? true : false
339
- end
340
- def value= x
341
- VK.redis.call("SET", key, "#{x.to_s}")
200
+
201
+ def increment(by = 1)
202
+ valkey.incrby(key, by)
342
203
  end
343
- def value!
344
- if self.value == true || self.value == nil
345
- self.value = false
346
- else
347
- self.value = true
348
- end
204
+
205
+ def decrement(by = 1)
206
+ valkey.decrby(key, by)
349
207
  end
350
- end
351
-
352
- class VALUE < O
353
- def value
354
- x = VK.redis.call("GET", key)
355
- if @opts.has_key?(:flush) == true
356
- delete!
357
- end
358
- return x
208
+
209
+ def reset
210
+ valkey.set(key, 0)
359
211
  end
360
- def value= x
361
- VK.redis.call("SET", key, x)
212
+
213
+ def delete
214
+ valkey.del(key)
362
215
  end
363
- def exist?
364
- VK.redis.call("GET", key) ? true : false
216
+
217
+ def expire(seconds)
218
+ valkey.expire(key, seconds)
365
219
  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
220
+
221
+ def expireat(unix_timestamp)
222
+ valkey.expireat(key, unix_timestamp)
373
223
  end
374
- end
375
224
 
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)
225
+ def ttl
226
+ valkey.ttl(key)
430
227
  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
228
+
229
+ def persist
230
+ valkey.persist(key)
440
231
  end
441
- def value= n
442
- VK.redis.call("SET", @key, n.to_f)
232
+
233
+ def to_i
234
+ value.to_i
443
235
  end
444
- def exist?
445
- VK.redis.call("GET", @key) ? true : false
446
- end
447
236
  end
448
237
 
449
- class HASH < O
450
- def [] k
451
- VK.redis.call("HGET", key, k);
238
+ # List wrapper
239
+ class List
240
+ include Enumerable
241
+ attr_reader :key, :valkey
242
+
243
+ def initialize(key, valkey)
244
+ @key = key
245
+ @valkey = valkey
246
+ end
247
+
248
+ def <<(value)
249
+ valkey.rpush(key, value)
250
+ end
251
+
252
+ def push(*values)
253
+ valkey.rpush(key, values)
254
+ end
255
+
256
+ def unshift(*values)
257
+ valkey.lpush(key, values.reverse)
258
+ end
259
+
260
+ def pop
261
+ valkey.rpop(key)
262
+ end
263
+
264
+ def shift
265
+ valkey.lpop(key)
266
+ end
267
+
268
+ def [](index)
269
+ valkey.lindex(key, index)
270
+ end
271
+
272
+ def []=(index, value)
273
+ valkey.lset(key, index, value)
274
+ end
275
+
276
+ def length
277
+ valkey.llen(key)
278
+ end
279
+ alias_method :size, :length
280
+
281
+ def range(start_idx, end_idx)
282
+ valkey.lrange(key, start_idx, end_idx)
283
+ end
284
+
285
+ def values
286
+ range(0, -1)
287
+ end
288
+
289
+ def each(&block)
290
+ values.each(&block)
291
+ end
292
+
293
+ def expire(seconds)
294
+ valkey.expire(key, seconds)
295
+ end
296
+
297
+ def expireat(unix_timestamp)
298
+ valkey.expireat(key, unix_timestamp)
299
+ end
300
+
301
+ def ttl
302
+ valkey.ttl(key)
303
+ end
304
+
305
+ def persist
306
+ valkey.persist(key)
452
307
  end
453
- def []= k,v
454
- VK.redis.call("HSET", key, k, v);
308
+
309
+ ### KNN ###
310
+ def knn
311
+ KNN.new(values)
455
312
  end
456
- def update(h={})
457
- h.each_pair { |k,v| VK.redis.call("HSET", key, k, v); }
313
+
314
+
315
+ def clear
316
+ valkey.del(key)
458
317
  end
459
- def to_h
460
- VK.redis.call("HGETALL", key);
318
+
319
+ def delete
320
+ clear
461
321
  end
462
322
  end
463
323
 
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
324
+ # Set wrapper
325
+ class Set
326
+ include Enumerable
327
+ attr_reader :key, :valkey
328
+
329
+ def initialize(key, valkey)
330
+ @key = key
331
+ @valkey = valkey
332
+ end
333
+
334
+ def add(value)
335
+ valkey.sadd(key, value)
336
+ end
337
+ alias_method :<<, :add
338
+
339
+ def remove(value)
340
+ valkey.srem(key, value)
341
+ end
342
+
343
+ def member?(value)
344
+ valkey.sismember(key, value)
345
+ end
346
+ alias_method :include?, :member?
347
+
348
+ def members
349
+ valkey.smembers(key)
350
+ end
351
+ alias_method :to_a, :members
352
+
353
+ def size
354
+ valkey.scard(key)
355
+ end
356
+ alias_method :length, :size
357
+
358
+ def each(&block)
359
+ members.each(&block)
360
+ end
361
+
362
+ def expire(seconds)
363
+ valkey.expire(key, seconds)
470
364
  end
471
- def last
472
- VK.redis.call("LRANGE", key, -1,-1)[0]
365
+
366
+ def expireat(unix_timestamp)
367
+ valkey.expireat(key, unix_timestamp)
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 ttl
371
+ valkey.ttl(key)
372
+ end
373
+
374
+ def persist
375
+ valkey.persist(key)
376
+ end
377
+
378
+ ### KNN ###
379
+ def knn
380
+ KNN.new(members)
381
+ end
382
+
383
+ def clear
384
+ valkey.del(key)
479
385
  end
480
- def front
481
- VK.redis.call("LPOP", key)
386
+
387
+ def delete
388
+ clear
482
389
  end
483
390
  end
484
391
 
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
392
+ # Hash wrapper
393
+ class HashKey
394
+ include Enumerable
395
+ attr_reader :key, :valkey
396
+
397
+ def initialize(key, valkey)
398
+ @key = key
399
+ @valkey = valkey
491
400
  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
401
+
402
+ def [](field)
403
+ valkey.hget(key, field)
496
404
  end
497
- def [] k
498
- VK.redis.call("ZSCORE", key, k).to_f;
405
+
406
+ def []=(field, value)
407
+ valkey.hset(key, field, value)
499
408
  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);
409
+
410
+ def fetch(field, default = nil)
411
+ valkey.hget(key, field) || default
505
412
  end
506
- end
507
413
 
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
414
+ def delete(field)
415
+ valkey.hdel(key, field)
522
416
  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
417
+
418
+ def key?(field)
419
+ valkey.hexists(key, field)
529
420
  end
530
- def length
531
- VK.redis.call("SCARD", key)
421
+ alias_method :has_key?, :key?
422
+
423
+ def keys
424
+ valkey.hkeys(key)
532
425
  end
533
- def << i
534
- VK.redis.call("SADD", key, i)
426
+
427
+ def values
428
+ valkey.hvals(key)
535
429
  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)
430
+
431
+ def all
432
+ valkey.hgetall(key)
541
433
  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
434
+ alias_method :to_h, :all
435
+
436
+ def size
437
+ valkey.hlen(key)
549
438
  end
550
- end
551
-
552
- class PLACE < O
553
- def value &b
554
- a = []
555
- VK.redis.call("ZRANGE", key, 0, -1).each_with_index { |e, i|
556
- if block_given?
557
- a << b.call(i, e)
558
- else
559
- a << e
560
- end
561
- };
562
- if @opts.has_key?(:flush) == true
563
- delete!
564
- end
565
- return a
439
+ alias_method :length, :size
440
+
441
+ def expire(seconds)
442
+ valkey.expire(key, seconds)
443
+ end
444
+
445
+ def expireat(unix_timestamp)
446
+ valkey.expireat(key, unix_timestamp)
566
447
  end
567
- def add i, lon, lat
568
- VK.redis.call("GEOADD", key, lon, lat, i)
448
+
449
+ def ttl
450
+ valkey.ttl(key)
569
451
  end
570
- def [] i
571
- x = VK.redis.call("GEOPOS", key, i)[0];
572
- return { longitude: x[0], latitude: x[1] }
452
+
453
+ def persist
454
+ valkey.persist(key)
573
455
  end
574
- def distance a, b
575
- VK.redis.call("GEODIST", key, a, b, 'm').to_f;
456
+
457
+ def each(&block)
458
+ all.each(&block)
576
459
  end
577
- def radius lon, lat, r
578
- h = {}
579
- VK.redis.call("GEORADIUS", key, lon, lat, r, 'm', 'WITHDIST').each { |e| h[e[0]] = e[1].to_f };
580
- return h
460
+
461
+ def clear
462
+ valkey.del(key)
581
463
  end
582
464
  end
583
465
 
584
- class SORTEDHASH < O
585
- def value &b
586
- VK.redis.call("ZREVRANGE", key, 0, -1, 'WITHSCORES').each_with_index { |e, i|
587
- kx = %[#{@key}-#{e[0]}]
588
- a = []
589
- if block_given?
590
- b.call(i, { key: e[0], value: VK.redis.call("GET", kx), score: e[1] } )
591
- else
592
- a << { key: e[0], value: VK.redis.call("GET", kx), score: e[1] }
593
- end
594
- if @opts.has_key?(:flush) == true
595
- VK.redis.call("DEL", kx)
596
- end
597
- }
598
- if @opts.has_key?(:flush) == true
599
- delete!
466
+ # Sorted Set wrapper
467
+ class SortedSet
468
+ include Enumerable
469
+ attr_reader :key, :valkey
470
+
471
+ def initialize(key, valkey)
472
+ @key = key
473
+ @valkey = valkey
474
+ end
475
+
476
+ def add(member, score)
477
+ valkey.zadd(key, score, member)
478
+ end
479
+ alias_method :[]=, :add
480
+
481
+ def score(member)
482
+ if !member?(member)
483
+ add(member, 0)
600
484
  end
601
- return a
485
+ return valkey.zscore(key, member).to_f
602
486
  end
603
- def [] k
604
- kx = %[#{@key}-#{k}]
605
- VK.redis.call("GET", kx)
487
+ alias_method :[], :score
488
+
489
+ def remove(member)
490
+ valkey.zrem(key, member)
606
491
  end
607
- def []= k, v
608
- kx = %[#{@key}-#{k}]
609
- VK.redis.call("SET", kx, v)
610
- VK.redis.call("ZINCRBY", key, 1, k)
492
+
493
+ def member?(member)
494
+ !valkey.zscore(key, member).nil?
611
495
  end
612
- end
496
+ alias_method :include?, :member?
613
497
 
614
- class HASHLIST < O
615
- def value &b
616
- a = []
617
- VK.redis.call("LRANGE", key, 0, -1).each_with_index { |e, i|
618
- if block_given?
619
- a << b.call(i, JSON.parse(VK.redis.call("GET", e)))
620
- else
621
- a << JSON.parse(VK.redis.call("GET", e))
622
- end
623
- if @opts.has_key?(:flush) == true
624
- VK.redis.call("DEL", e)
625
- end
626
- }
627
- if @opts.has_key?(:flush) == true
628
- delete!
498
+ def range(start_idx, end_idx, with_scores: true)
499
+ if with_scores
500
+ valkey.zrange(key, start_idx, end_idx, with_scores: true)
501
+ else
502
+ valkey.zrange(key, start_idx, end_idx)
629
503
  end
630
- return a
631
504
  end
632
- def length
633
- VK.redis.call("LLEN", key)
634
- end
635
- def [] k
636
- hx = %[#{key}-#{k}]
637
- JSON.parse(VK.redis.call("GET", hx));
505
+
506
+ def revrange(start_idx, end_idx, with_scores: true)
507
+ if with_scores
508
+ valkey.zrevrange(key, start_idx, end_idx, with_scores: true)
509
+ else
510
+ valkey.zrevrange(key, start_idx, end_idx)
511
+ end
638
512
  end
639
- def push h={}
640
- hx = %[#{key}-#{length}]
641
- VK.redis.call("SET", hx, JSON.generate(h));
642
- VK.redis.call("RPUSH", key, hx)
643
- return hx
513
+
514
+ def members
515
+ range(0, -1)
644
516
  end
645
- def first
646
- VK.redis.call("LRANGE", key, 0, 0)[0]
517
+
518
+ def incr k, n
519
+ add(k, score(k) + n)
647
520
  end
648
- def last
649
- VK.redis.call("LRANGE", key, -1, -1)[0]
521
+
522
+ def decr k, n
523
+ add(k, score(k) - n)
650
524
  end
651
- end
652
525
 
653
- class CORPUS < O
654
- def _set
655
- %[#{key}-sset]
656
- end
657
- def _index
658
- %[#{key}-hash]
659
- end
660
- def length
661
- VK.redis.call("SCARD", _set)
662
- end
663
- def value &b
664
- VK.redis.call("HGETALL", _index).each_pair { |e, i| b.call(i, e) }
665
- end
666
- def [] i
667
- if VK.redis.call("SMISMEMBER", _set, i)[0] == 0
668
- VK.redis.call("SADD", _set, i)
669
- VK.redis.call("HSET", _index, i, length);
670
- end
671
- return VK.redis.call("HGET", _index, i)
672
- end
673
- end
526
+ def up n, *a
527
+ [a].flatten.compact.each { |e| incr(e, n) }
528
+ end
674
529
 
675
- class BOOK < O
676
- def _set
677
- %[#{key}-sset]
530
+ def dn n, *a
531
+ [a].flatten.compact.each { |e| decr(e, n) }
678
532
  end
679
- def _index
680
- %[#{key}-index]
533
+
534
+ ### KNN ###
535
+ def knn
536
+ KNN.new(members.map { |e| e[0] })
681
537
  end
682
- def _sec
683
- %[#{key}-sec]
684
- end
685
- def _embed
686
- %[#{key}-embed]
687
- end
688
- def length
689
- VK.redis.call("SCARD", _set)
690
- end
691
- def value &b
692
- VK.redis.call("HGETALL", _index).each_pair { |e, i| b.call(i.to_i, e) }
693
- end
694
- def entry
695
- Hash.new { |h,k| VK.redis.call("HGET", _sec, k) }
696
- end
697
- def vectors
698
- a = []
699
- value { |i, e| a << [ i, VK.embed(e) ].flatten }
700
- return a
701
- end
702
- def embed
703
- return KNN.new(vectors, :distance_measure => :tanimoto_coefficient)
704
- end
705
- def near g, *n
706
- return embed.nearest_neighbours(VK.embed(g)).map { |e|
707
- { index: e[0], distance: e[1], entry: entry[e[0]] }
708
- }.sort_by { |ee| ee[:distance] }
709
- end
710
- def [] i
711
- if VK.redis.call("SMISMEMBER", _set, i.to_s)[0] == 0
712
- VK.redis.call("HSET", _index, i.to_s, length.to_s);
713
- VK.redis.call("HSET", _sec, length.to_s, i.to_s);
714
- VK.redis.call("SADD", _set, i)
715
- end
716
- return VK.redis.call("HGET", _index, i.to_s).to_i;
717
- end
718
- end
538
+
539
+ def size
540
+ valkey.zcard(key)
541
+ end
542
+ alias_method :length, :size
719
543
 
720
- @@SENTIMENT_THRESHOLD = 0.9
721
- ##
722
- # Bayesean Classification
723
- #
724
- # Determine the A OR B of an example.
725
- ##
726
- # VK.classify("catagory A", "Catagory B").
727
- def self.classify a, b
728
- ClassifierReborn::Bayes.new a, b
729
- end
730
-
731
- def self.threshold= n
732
- @@SENTIMENT_THRESHOLD = n
733
- end
734
- ##
735
- # Sentiment Analysis.
736
- ##
737
- # VK.feels("Some text")
738
- def self.feels x
739
- Sentimental.new(threshold: @@SENTIMENT_THRESHOLD).sentiment
740
- end
741
-
742
- @@CRi = Hash.new { |h,k| h[k] = CRi.new(k) }
743
- class CRi
744
- def initialize k
745
- @id = k
746
- @lsi = ClassifierReborn::LSI.new
747
- end
748
- def learn x, i
749
- @lsi.add_item x.to_s, i.to_sym
750
- end
751
- def [] k
752
- @lsi.classify k
753
- end
754
- def search x, *n
755
- @lsi.search(x, n[0])
756
- end
757
- end
758
- ##
759
- # Vector Indexing
760
- #
761
- # Vector (of text) -> concept (index)
762
- # - Find text concept based upon known examples.
763
- # - Learn new examples of a concept.
764
- # - Find examples from within clusters.
765
- ##
766
- # index[:vector]['Text to classify'] => :index
767
- # index[:vector].learn("Text to classify", :index)
768
- # index[:vector].search("Similar text.", 5) => ["Similar text?", "other similar text", ...]
769
- def self.index
770
- @@CRi
771
- end
772
- @@VI = Hash.new { |h,k| h[k] = [] }
773
- @@VC = Hash.new { |h,k| h[k] = Knn::Classifier.new(@@VI[k], SquaredEuclideanCalculator) }
774
- def self.learn fact, index
775
- @@CRi[index.to_sym].learn fact, index
776
- @@VI[index] << fact
777
- end
778
-
779
- @@TOKENIZER = Tokenizer::WhitespaceTokenizer.new(:en)
780
- def self.tokenize i
781
- @@TOKENIZER.tokenize(i)
782
- end
544
+ def each(&block)
545
+ members.each(&block)
546
+ end
547
+
548
+ def expire(seconds)
549
+ valkey.expire(key, seconds)
550
+ end
783
551
 
784
- @@WORDLIST = CORPUS.new(%[WORDLIST])
785
- def self.wordlist
786
- @@WORDLIST
787
- end
552
+ def expireat(unix_timestamp)
553
+ valkey.expireat(key, unix_timestamp)
554
+ end
788
555
 
789
- def self.embed i
790
- m = VK.tokenize(i).map { |e| VK.wordlist[e].to_f }
791
- return [m, Array.new(2048 - m.length, 0.0)].flatten
792
- end
793
-
794
- def self.flushdb!
795
- VK.redis.call("FLUSHDB")
796
- end
556
+ def ttl
557
+ valkey.ttl(key)
558
+ end
797
559
 
798
- def self.keys k='*'
799
- VK.redis.call("KEYS", k)
800
- end
560
+ def persist
561
+ valkey.persist(key)
562
+ end
563
+
564
+ def clear
565
+ valkey.del(key)
566
+ end
801
567
 
802
- def self.txt f, &b
803
- x = File.read(f).gsub(/\n\n+/,"\n\n").gsub(/ +/, " ").split("\n\n").map { |e| e.gsub(/\n/, " ") }
804
- if block_given?
805
- x.map { |e| b.call(e) }
806
- else
807
- return x
808
- end
568
+ def delete
569
+ clear
570
+ end
809
571
  end
810
572
  end