time_series 0.0.1
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.
- data/.gitignore +1 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +14 -0
- data/lib/key.rb +205 -0
- data/lib/redis_connection.rb +21 -0
- data/lib/time_series.rb +383 -0
- data/lib/version.rb +3 -0
- data/spec/compressor_spec.rb +311 -0
- data/spec/key_spec.rb +256 -0
- data/spec/time_series_spec.rb +125 -0
- data/time_series.gemspec +30 -0
- metadata +175 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 panos
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# TimeSeries
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'time_series'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install time_series
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/key.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'redis'
|
5
|
+
require 'hiredis'
|
6
|
+
require 'date'
|
7
|
+
#require 'json'
|
8
|
+
require 'awesome_print'
|
9
|
+
|
10
|
+
class Time
|
11
|
+
# def self.from_key key
|
12
|
+
# key.gsub!(/^[^:]*:[^:]*:[^:]*:/, '')
|
13
|
+
# Time.strptime(key, "%Y:%m:%d:%H:%M:%S") # TODO works only with second-resolution
|
14
|
+
# end
|
15
|
+
|
16
|
+
def human
|
17
|
+
a = (Time.now-self).to_i
|
18
|
+
case a
|
19
|
+
when 0 then 'just now'
|
20
|
+
when 1 then 'a second ago'
|
21
|
+
when 2..59 then a.to_s+' seconds ago'
|
22
|
+
when 60..119 then 'a minute ago' #120 = 2 minutes
|
23
|
+
when 120..3540 then (a/60).to_i.to_s+' minutes ago'
|
24
|
+
when 3541..7100 then 'an hour ago' # 3600 = 1 hour
|
25
|
+
when 7101..82800 then ((a+99)/3600).to_i.to_s+' hours ago'
|
26
|
+
when 82801..172000 then 'a day ago' # 86400 = 1 day
|
27
|
+
when 172001..518400 then ((a+800)/(60*60*24)).to_i.to_s+' days ago'
|
28
|
+
when 518400..1036800 then 'a week ago'
|
29
|
+
else ((a+180000)/(60*60*24*7)).to_i.to_s+' weeks ago'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class String
|
35
|
+
|
36
|
+
def redis
|
37
|
+
@redis = $redis ||= Redis.new(:path => "/tmp/redis.sock",:driver => :hiredis)
|
38
|
+
return @redis
|
39
|
+
end
|
40
|
+
|
41
|
+
def prefix
|
42
|
+
matchdata = /(^[^:]+:[^:]+:[^:]+:)/.match(self)
|
43
|
+
return matchdata[1] unless matchdata.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
def exists?
|
47
|
+
redis.exists self
|
48
|
+
end
|
49
|
+
|
50
|
+
#deprecated
|
51
|
+
def parent_key
|
52
|
+
parent
|
53
|
+
end
|
54
|
+
|
55
|
+
def parent
|
56
|
+
self.gsub(/:[^:]*$/, '')
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_persistant_children?
|
60
|
+
not persistant_children.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_children?
|
64
|
+
has_persistant_children?
|
65
|
+
end
|
66
|
+
|
67
|
+
#deprecated
|
68
|
+
def has_parent?
|
69
|
+
parent.exists?
|
70
|
+
end
|
71
|
+
|
72
|
+
def volatile?
|
73
|
+
redis.ttl(self) >= 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def persistant?
|
77
|
+
redis.ttl(self)==-1 and exists?
|
78
|
+
end
|
79
|
+
|
80
|
+
def ttl
|
81
|
+
redis.ttl self
|
82
|
+
end
|
83
|
+
|
84
|
+
#deprecated
|
85
|
+
def has_persistant_parent?
|
86
|
+
redis.ttl(parent_key) < 0 and parent.exists?
|
87
|
+
end
|
88
|
+
|
89
|
+
#deprecated
|
90
|
+
def has_volatile_parent?
|
91
|
+
redis.ttl(parent_key) >= 0
|
92
|
+
end
|
93
|
+
|
94
|
+
def children
|
95
|
+
redis.keys(self+":*")
|
96
|
+
end
|
97
|
+
|
98
|
+
def persistant_children
|
99
|
+
keys = redis.keys(self+":*")
|
100
|
+
keys.delete_if{|k| k.volatile?}
|
101
|
+
return keys
|
102
|
+
end
|
103
|
+
|
104
|
+
def siblings_keys
|
105
|
+
redis.keys(self.gsub(/:[^:]*$/, ':*'))
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def unionize_persistant_children
|
110
|
+
children = persistant_children
|
111
|
+
redis.zunionstore self, children
|
112
|
+
|
113
|
+
if recent?
|
114
|
+
redis.expire self, 60
|
115
|
+
else
|
116
|
+
redis.expire self, 600
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
def resolution
|
122
|
+
case self.count(':')
|
123
|
+
when 8 then :second
|
124
|
+
when 7 then :minute
|
125
|
+
when 6 then :hour
|
126
|
+
when 5 then :day
|
127
|
+
when 4 then :month
|
128
|
+
when 3 then :year
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def year?; resolution == :year end
|
133
|
+
def month?; resolution == :month end
|
134
|
+
def day?; resolution == :day end
|
135
|
+
def hour?; resolution == :hour end
|
136
|
+
def minute?; resolution == :minute end
|
137
|
+
def second?; resolution == :second end
|
138
|
+
|
139
|
+
def year; /[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
|
140
|
+
def month; /[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
|
141
|
+
def day; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
|
142
|
+
def hour; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
|
143
|
+
def minute; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
|
144
|
+
def second; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
|
145
|
+
|
146
|
+
def time
|
147
|
+
key = self.gsub(/^[^:]*:[^:]*:[^:]*:/, '') #remove prefix
|
148
|
+
case resolution
|
149
|
+
when :year then return DateTime.new(year).to_time
|
150
|
+
when :month then return DateTime.new(year, month).to_time
|
151
|
+
when :day then return DateTime.new(year, month, day).to_time
|
152
|
+
when :hour then return DateTime.new(year, month, day, hour, 0, 0, '+3').to_time
|
153
|
+
when :minute then return DateTime.new(year, month, day, hour, minute, 0, '+3').to_time
|
154
|
+
when :second then return DateTime.new(year, month, day, hour, minute, second, '+3').to_time
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def recent?
|
159
|
+
case resolution
|
160
|
+
when :year then year == Time.now.year
|
161
|
+
when :month then year == Time.now.year
|
162
|
+
when :day then year == Time.now.year and month == Time.now.month
|
163
|
+
when :hour then year == Time.now.year and month == Time.now.month and day == Time.now.day
|
164
|
+
when :minute then year == Time.now.year and month == Time.now.month and day == Time.now.day and hour == Time.now.hour
|
165
|
+
when :second then year == Time.now.year and month == Time.now.month and day == Time.now.day and hour == Time.now.hour and minute == Time.now.min
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_array
|
170
|
+
redis.zrevrange self, 0, -1, :with_scores => true
|
171
|
+
end
|
172
|
+
|
173
|
+
def array_to_hash array
|
174
|
+
hash = Hash.new
|
175
|
+
array.each{|a| hash[a[0]] = a[1].to_f }
|
176
|
+
return hash
|
177
|
+
end
|
178
|
+
|
179
|
+
def get
|
180
|
+
if exists?
|
181
|
+
;
|
182
|
+
elsif has_children?
|
183
|
+
unionize_persistant_children
|
184
|
+
else
|
185
|
+
if not year?
|
186
|
+
parent.get
|
187
|
+
# http://rubydoc.info/github/redis/redis-rb/Redis:zunionstore
|
188
|
+
p = parent #TODO Test the case when the immediate parent has no persistant children so weights has infinite values
|
189
|
+
while !p.has_children?
|
190
|
+
p = p.parent
|
191
|
+
end
|
192
|
+
weights = [ 1.0 / p.persistant_children.size ]
|
193
|
+
redis.zunionstore self, [parent], :weights => weights
|
194
|
+
if recent?
|
195
|
+
redis.expire self, 60
|
196
|
+
else
|
197
|
+
redis.expire self, 600
|
198
|
+
end
|
199
|
+
else
|
200
|
+
return {}
|
201
|
+
end
|
202
|
+
end
|
203
|
+
array_to_hash get_array
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'redis'
|
4
|
+
require 'hiredis'
|
5
|
+
|
6
|
+
|
7
|
+
# Initialize a connection to Redis.
|
8
|
+
# Currently the path to redis socket is hardcoded (/tmp/redis.sock),
|
9
|
+
# as well as the driver (:hiredis).
|
10
|
+
# global variable $redis gets set.
|
11
|
+
#
|
12
|
+
#
|
13
|
+
class RedisConnection
|
14
|
+
attr_reader :redis
|
15
|
+
def initialize *args
|
16
|
+
#@key= key
|
17
|
+
$redis ||= Redis.new(:path => "/tmp/redis.sock",:driver => :hiredis)
|
18
|
+
@redis = $redis
|
19
|
+
#puts green "Connected to Redis"
|
20
|
+
end # end initialize
|
21
|
+
end
|
data/lib/time_series.rb
ADDED
@@ -0,0 +1,383 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'active_support/core_ext/numeric/time.rb'
|
5
|
+
|
6
|
+
#require 'active_support/core_ext/time/calculations.rb'
|
7
|
+
require_relative "version"
|
8
|
+
require_relative 'redis_connection'
|
9
|
+
require_relative 'key'
|
10
|
+
|
11
|
+
##
|
12
|
+
# TimeSeries Class
|
13
|
+
#
|
14
|
+
class TimeSeries < RedisConnection
|
15
|
+
attr_reader :name, :duration, :resolution
|
16
|
+
|
17
|
+
##
|
18
|
+
# Create Timeseries.
|
19
|
+
#
|
20
|
+
# @param [String] name The timeseries name.
|
21
|
+
# @param [Hash] options The options hash.
|
22
|
+
# @option options [String] :resolution The time resolution: :year, :month, :day, :hour, :minute, :second
|
23
|
+
# @option options [Integer] :duration Duration is under development. It will allow for example 10, 20 or 30 seconds keys. Now only keys with :minute resolution are available.
|
24
|
+
def initialize name, options={}
|
25
|
+
super # initialize RedisConnection
|
26
|
+
|
27
|
+
@name = name
|
28
|
+
@prefix="#{$app_prefix}:ts:#{@name}:"
|
29
|
+
|
30
|
+
# parse the resolution option
|
31
|
+
if options.has_key? :resolution
|
32
|
+
@resolution = options[:resolution]
|
33
|
+
resolutions = [:year, :month, :day, :hour, :minute, :second]
|
34
|
+
#if the resolution option is invalid raise an exception
|
35
|
+
unless resolutions.include?(@resolution) #or @resolution.is_a?(Integer)
|
36
|
+
raise ArgumentError.new("resolution can be either :year or :month or :day or :hour or :minute or :second")
|
37
|
+
end
|
38
|
+
elsif keys.empty? # default resolution is :second
|
39
|
+
@resolution = :second
|
40
|
+
else # try to guess resolution from existing keys
|
41
|
+
max_res = 0
|
42
|
+
keys.each do |k|
|
43
|
+
res = k.count(':')
|
44
|
+
max_res = res if res > max_res
|
45
|
+
end
|
46
|
+
|
47
|
+
case max_res
|
48
|
+
when 8 then @resolution = :second
|
49
|
+
when 7 then @resolution = :minute
|
50
|
+
when 6 then @resolution = :hour
|
51
|
+
when 5 then @resolution = :day
|
52
|
+
when 4 then @resolution = :month
|
53
|
+
when 3 then @resolution = :year
|
54
|
+
else raise ArgumentError.new("Cannot guess resolution from existing keys")
|
55
|
+
end #case
|
56
|
+
end # if
|
57
|
+
|
58
|
+
# define the @duration based on @resolution
|
59
|
+
case @resolution
|
60
|
+
when :year then @duration = 12*30*24*3600
|
61
|
+
when :month then @duration = 30*24*3600
|
62
|
+
when :day then @duration = 24*3600
|
63
|
+
when :hour then @duration = 3600
|
64
|
+
when :minute then @duration = 60
|
65
|
+
when :second then @duration = options[:duration] ||= 20
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the current time.
|
70
|
+
#
|
71
|
+
# @return [Time] the current time
|
72
|
+
def current_time
|
73
|
+
time=Time.at(Time.now.to_i) # this way nsec and usec is 0
|
74
|
+
if @resolution == :second
|
75
|
+
sec = time.strftime("%S").to_i % @duration
|
76
|
+
time = time - sec
|
77
|
+
end
|
78
|
+
return time
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the time of the last key.
|
82
|
+
#
|
83
|
+
# @return [Time] the last key's time
|
84
|
+
def last_time
|
85
|
+
current_time - @duration
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the time of the previous key.
|
89
|
+
#
|
90
|
+
# @return [Time] the previous key's time
|
91
|
+
def previous_time
|
92
|
+
current_time - 2 * @duration
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the current key
|
96
|
+
#
|
97
|
+
# @return [String] current key
|
98
|
+
def current_key
|
99
|
+
time_to_key current_time, @resolution
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the last key
|
103
|
+
#
|
104
|
+
# @return [String] last key
|
105
|
+
def last_key
|
106
|
+
time_to_key last_time, @resolution
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the previous key
|
110
|
+
#
|
111
|
+
# @return [String] previous key
|
112
|
+
def previous_key
|
113
|
+
time_to_key previous_time, @resolution
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns all the keys
|
117
|
+
#
|
118
|
+
# @return [Array] all the keys in a String Array
|
119
|
+
def keys
|
120
|
+
return @redis.keys"#{$app_prefix}:ts:#{@name}:*"
|
121
|
+
end
|
122
|
+
|
123
|
+
# Deletes all the keys
|
124
|
+
#
|
125
|
+
# @return Number of keys deleted
|
126
|
+
def clear
|
127
|
+
i = 0
|
128
|
+
keys.each{|k| @redis.del k; i+=1}
|
129
|
+
return i
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the contents of all the keys
|
133
|
+
# TODO Considering to remove this method
|
134
|
+
#
|
135
|
+
# @return [Hash] contents of all the keys
|
136
|
+
def all
|
137
|
+
all = Hash.new
|
138
|
+
keys.each{ |k| all[k.gsub(/#{@prefix}/,'')]=k.get}
|
139
|
+
return all
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the contents of the last key
|
143
|
+
#
|
144
|
+
# @return [Hash] contents of the last key
|
145
|
+
def last
|
146
|
+
last_key.get
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns the contents of the previous key
|
150
|
+
#
|
151
|
+
# @return [Hash] contents of the previous key
|
152
|
+
def previous
|
153
|
+
previous_key.get
|
154
|
+
end
|
155
|
+
|
156
|
+
# Push a new Term into the Timeseries
|
157
|
+
def push term
|
158
|
+
@redis.zincrby current_key, 1, term
|
159
|
+
end
|
160
|
+
|
161
|
+
# Convert a Time object to the respective Key
|
162
|
+
# TODO Refactoring
|
163
|
+
#
|
164
|
+
# @param [Time] time The Time
|
165
|
+
# @option [String] resolution The time resolution: :year, :month, :day, :hour, :minute, :second
|
166
|
+
# @return [String] The Key
|
167
|
+
def time_to_key time, *resolution
|
168
|
+
if resolution.empty?
|
169
|
+
return time.strftime("#{@prefix}%Y:%m:%d:%H:%M:%S")
|
170
|
+
else
|
171
|
+
resolution = resolution.first
|
172
|
+
case resolution
|
173
|
+
when :year then return time.strftime("#{@prefix}%Y")
|
174
|
+
when :month then return time.strftime("#{@prefix}%Y:%m")
|
175
|
+
when :day then return time.strftime("#{@prefix}%Y:%m:%d")
|
176
|
+
when :hour then return time.strftime("#{@prefix}%Y:%m:%d:%H")
|
177
|
+
when :minute then return time.strftime("#{@prefix}%Y:%m:%d:%H:%M")
|
178
|
+
when :second then return time.strftime("#{@prefix}%Y:%m:%d:%H:%M:%S")
|
179
|
+
else puts red "wrong resolution in time_to_key"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Removes recent keys from a key array
|
185
|
+
#
|
186
|
+
# @param [Array] array Array of Keys
|
187
|
+
# @param [Integer] Number of seconds that a key is considered recent
|
188
|
+
# @return [Array] The new array
|
189
|
+
def array_older_than array, time
|
190
|
+
array.keep_if { |k| k.time <= Time.now - time }
|
191
|
+
end
|
192
|
+
|
193
|
+
# Keys with second resolution
|
194
|
+
#
|
195
|
+
# @param [Array] time
|
196
|
+
# @return [Array] Array with the keys
|
197
|
+
def seconds *time
|
198
|
+
array = keys.keep_if { |k| k.second? and k.persistant?}
|
199
|
+
if time.empty?
|
200
|
+
return array
|
201
|
+
else
|
202
|
+
time = time.first
|
203
|
+
return array_older_than array, time
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Keys with minute resolution
|
208
|
+
#
|
209
|
+
# @param [Array] time
|
210
|
+
# @return [Array] Array with the keys
|
211
|
+
def minutes *time
|
212
|
+
array = keys.keep_if { |k| k.minute? and k.persistant?}
|
213
|
+
if time.empty?
|
214
|
+
return array
|
215
|
+
else
|
216
|
+
time = time.first
|
217
|
+
return array_older_than array, time
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Keys with hour resolution
|
222
|
+
#
|
223
|
+
# @param [Array] time
|
224
|
+
# @return [Array] Array with the keys
|
225
|
+
def hours *time
|
226
|
+
array = keys.keep_if { |k| k.hour? and k.persistant?}
|
227
|
+
if time.empty?
|
228
|
+
return array
|
229
|
+
else
|
230
|
+
time = time.first
|
231
|
+
return array_older_than array, time
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Keys with day resolution
|
236
|
+
#
|
237
|
+
# @param [Array] time
|
238
|
+
# @return [Array] Array with the keys
|
239
|
+
def days *time
|
240
|
+
array = keys.keep_if { |k| k.day? and k.persistant?}
|
241
|
+
if time.empty?
|
242
|
+
return array
|
243
|
+
else
|
244
|
+
time = time.first
|
245
|
+
return array_older_than array, time
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Keys with month resolution
|
250
|
+
#
|
251
|
+
# @param [Array] time
|
252
|
+
# @return [Array] Array with the keys
|
253
|
+
def months *time
|
254
|
+
array = keys.keep_if { |k| k.month? and k.persistant?}
|
255
|
+
if time.empty?
|
256
|
+
return array
|
257
|
+
else
|
258
|
+
time = time.first
|
259
|
+
return array_older_than array, time
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
# Keys with year resolution
|
265
|
+
#
|
266
|
+
# @param [Array] time
|
267
|
+
# @return [Array] Array with the keys
|
268
|
+
def years *time
|
269
|
+
array = keys.keep_if { |k| k.year? and k.persistant?}
|
270
|
+
if time.empty?
|
271
|
+
return array
|
272
|
+
else
|
273
|
+
time = time.first
|
274
|
+
return array_older_than array, time
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
# Compress keys
|
280
|
+
# Key compression merges the given keys of the same resolution to keys
|
281
|
+
# of greater resolution. For example seconds are merged into minutes and
|
282
|
+
# days are merged into months.
|
283
|
+
# The values of the keys are merged too.
|
284
|
+
# After the merge the keys are deleted.
|
285
|
+
#
|
286
|
+
# @param [Array] keys to be compressed
|
287
|
+
# @return [Integer] Number of keys compressed
|
288
|
+
def compress keys
|
289
|
+
parents = []
|
290
|
+
keys.each do |key|
|
291
|
+
unless key.recent? or key.year?
|
292
|
+
parent = key.parent
|
293
|
+
parents << parent unless parents.include? parent
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
i = 0
|
298
|
+
parents.each do |parent|
|
299
|
+
unless parent.recent?
|
300
|
+
children = parent.persistant_children
|
301
|
+
@redis.zunionstore parent, children
|
302
|
+
children.each{|k| @redis.del k; i+=1}
|
303
|
+
end
|
304
|
+
end
|
305
|
+
return i
|
306
|
+
end
|
307
|
+
|
308
|
+
# Remove terms with low scores
|
309
|
+
#
|
310
|
+
# @param [Array] keys that will be examined
|
311
|
+
# @return [Array] Number of keys the operation took place, it doesn't mean that something changed
|
312
|
+
def remove_by_score keys, *population
|
313
|
+
if population.empty?
|
314
|
+
population = 1
|
315
|
+
else
|
316
|
+
population = population.first
|
317
|
+
end
|
318
|
+
i = 0
|
319
|
+
keys.each {|k| @redis.zremrangebyscore(k, '-inf', population); i+=1} # TODO What zremrangebyscore returns?
|
320
|
+
return i
|
321
|
+
end
|
322
|
+
|
323
|
+
#def term_weights terms, factor
|
324
|
+
# terms.each do |term, value|
|
325
|
+
# terms[term]=value*factor
|
326
|
+
# end
|
327
|
+
# return terms
|
328
|
+
#end
|
329
|
+
|
330
|
+
def year time; time_to_key(time, :year).get end
|
331
|
+
def month time; time_to_key(time, :month).get end
|
332
|
+
def day time; time_to_key(time, :day).get end
|
333
|
+
def hour time; time_to_key(time, :hour).get end
|
334
|
+
def minute time; time_to_key(time, :minute).get end
|
335
|
+
def second time; time_to_key(time, :second).get end
|
336
|
+
|
337
|
+
end
|
338
|
+
|
339
|
+
|
340
|
+
|
341
|
+
__END__
|
342
|
+
=begin
|
343
|
+
def get_by_time time
|
344
|
+
key = time_to_key time
|
345
|
+
if key.exists?
|
346
|
+
return get(key)
|
347
|
+
elsif false
|
348
|
+
return nil
|
349
|
+
end
|
350
|
+
#array_to_hash @redis.zrange key, 0, -1, :with_scores => true
|
351
|
+
end
|
352
|
+
|
353
|
+
|
354
|
+
def get_by_resolution time, resolution
|
355
|
+
key = time_to_key time, resolution
|
356
|
+
if key.exists?
|
357
|
+
;
|
358
|
+
elsif key.has_children?
|
359
|
+
key.unionize_persistant_children
|
360
|
+
else
|
361
|
+
return get_parent time, resolution
|
362
|
+
# fix data
|
363
|
+
# add ttl
|
364
|
+
end
|
365
|
+
|
366
|
+
return get(key)
|
367
|
+
end
|
368
|
+
|
369
|
+
def get_parent time, resolution
|
370
|
+
case resolution
|
371
|
+
when :month then get_year time
|
372
|
+
when :day then get_month time
|
373
|
+
when :hour then get_day time
|
374
|
+
when :minute then get_hour time
|
375
|
+
when :second then get_minute time
|
376
|
+
else get_year time
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
=end
|
382
|
+
|
383
|
+
|