lrucache 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 +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Guardfile +14 -0
- data/README.md +40 -0
- data/Rakefile +24 -0
- data/lib/lrucache.rb +93 -0
- data/lib/lrucache/version.rb +3 -0
- data/lrucache.gemspec +29 -0
- data/spec/lrucache_spec.rb +102 -0
- data/spec/spec_helper.rb +10 -0
- metadata +206 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.2@lrucache
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
watch(/^.+\.gemspec/)
|
7
|
+
end
|
8
|
+
|
9
|
+
guard 'rspec', :cli => '-c --format documentation -r ./spec/spec_helper.rb',
|
10
|
+
:version => 2 do
|
11
|
+
watch(%r{^spec/.+_spec\.rb})
|
12
|
+
watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
14
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
lrucache - A simple LRU-cache based on a hash and a priority queue.
|
2
|
+
|
3
|
+
Setup
|
4
|
+
=====
|
5
|
+
gem install lrucache
|
6
|
+
|
7
|
+
Example
|
8
|
+
=======
|
9
|
+
require 'lrucache'
|
10
|
+
cache = LRUCache.new(:max_size => 3, :default => 42)
|
11
|
+
cache[1] = 'a'
|
12
|
+
cache[2] = 'b'
|
13
|
+
cache[3] = 'c'
|
14
|
+
puts cache[2] # b
|
15
|
+
puts cache[1] # a
|
16
|
+
puts cache[3] # c
|
17
|
+
puts cache[:does_not_exist] # 42, has no effect on LRU.
|
18
|
+
cache[4] = 'd' # Out of space! Throws out the least-recently used (2 => 'b').
|
19
|
+
puts cache.keys # [1,3,4]
|
20
|
+
|
21
|
+
|
22
|
+
TTL (time-to-live)
|
23
|
+
==================
|
24
|
+
cache = LRUCache.new(:expires => 1.hour)
|
25
|
+
cache.store("banana", "yellow")
|
26
|
+
cache.store("monkey", "banana", Time.now + 3.days)
|
27
|
+
# or ...
|
28
|
+
cache.store("monkey", "banana", 3.days)
|
29
|
+
|
30
|
+
# Three minutes later ...
|
31
|
+
cache.fetch("banana") # "yellow"
|
32
|
+
cache.fetch("monkey") # "banana"
|
33
|
+
|
34
|
+
# Three hours later ...
|
35
|
+
cache.fetch("banana") # nil
|
36
|
+
cache.fetch("monkey") # "banana"
|
37
|
+
|
38
|
+
# Three days later ...
|
39
|
+
cache.fetch("banana") # nil
|
40
|
+
cache.fetch("monkey") # nil
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc "Run all specs"
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
6
|
+
t.rspec_opts = [
|
7
|
+
'-c',
|
8
|
+
'--format documentation',
|
9
|
+
'-r ./spec/spec_helper.rb'
|
10
|
+
]
|
11
|
+
t.pattern = 'spec/**/*_spec.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "open coverage report"
|
15
|
+
task :coverage do
|
16
|
+
system 'rake spec'
|
17
|
+
system 'open coverage/index.html'
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Open development console"
|
21
|
+
task :console do
|
22
|
+
puts "Loading development console..."
|
23
|
+
system "irb -I #{File.join('.', 'lib')} -r #{File.join('.', 'lib', 'lrucache')}"
|
24
|
+
end
|
data/lib/lrucache.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require "lrucache/version"
|
2
|
+
require "priority_queue"
|
3
|
+
|
4
|
+
# Not thread-safe!
|
5
|
+
class LRUCache
|
6
|
+
|
7
|
+
attr_reader :default, :max_size
|
8
|
+
|
9
|
+
def initialize(opts={})
|
10
|
+
@max_size = (opts[:max_size] || 100).to_i
|
11
|
+
@default = opts[:default]
|
12
|
+
@expires = (opts[:expires] || 0).to_f
|
13
|
+
raise "max_size must be greather than zero" unless @max_size > 0
|
14
|
+
@pqueue = PriorityQueue.new
|
15
|
+
@data = {}
|
16
|
+
@counter = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear
|
20
|
+
@data.clear
|
21
|
+
@pqueue.delete_min until @pqueue.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def include?(key)
|
25
|
+
datum = @data[key]
|
26
|
+
return false if datum.nil?
|
27
|
+
value, expires = datum
|
28
|
+
if expires.nil? || expires > Time.now # no expiration, or not expired
|
29
|
+
access(key)
|
30
|
+
true
|
31
|
+
else # expired
|
32
|
+
delete(key)
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def store(key, value, expires=nil)
|
38
|
+
expire_lru! unless @data.include?(key) || @data.size < @max_size
|
39
|
+
expiration =
|
40
|
+
if expires.nil?
|
41
|
+
(@expires > 0) ? (Time.now + @expires) : nil
|
42
|
+
elsif expires.is_a?(Time)
|
43
|
+
expires
|
44
|
+
else
|
45
|
+
expires = expires.to_f
|
46
|
+
(expires > 0) ? (Time.now + expires) : nil
|
47
|
+
end
|
48
|
+
@data[key] = [value, expiration]
|
49
|
+
access(key)
|
50
|
+
end
|
51
|
+
|
52
|
+
alias :[]= :store
|
53
|
+
|
54
|
+
def fetch(key)
|
55
|
+
datum = @data[key]
|
56
|
+
return @default if datum.nil?
|
57
|
+
value, expires = datum
|
58
|
+
if expires.nil? || expires > Time.now # no expiration, or not expired
|
59
|
+
access(key)
|
60
|
+
value
|
61
|
+
else # expired
|
62
|
+
delete(key)
|
63
|
+
@default
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
alias :[] :fetch
|
68
|
+
|
69
|
+
def size
|
70
|
+
@data.size
|
71
|
+
end
|
72
|
+
|
73
|
+
def keys
|
74
|
+
@data.keys
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete(key)
|
78
|
+
@data.delete(key)
|
79
|
+
@pqueue.delete(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def expire_lru!
|
85
|
+
key, priority = @pqueue.delete_min
|
86
|
+
@data.delete(key) unless priority.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def access(key)
|
90
|
+
@pqueue.change_priority(key, @counter += 1)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/lrucache.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "lrucache/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "lrucache"
|
7
|
+
s.version = LRUCache::VERSION
|
8
|
+
s.authors = ["Chris Johnson"]
|
9
|
+
s.email = ["chris@kindkid.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = "A simple LRU-cache based on a hash and priority queue"
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "lrucache"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
s.add_dependency "PriorityQueue", '~> 0.1.2'
|
21
|
+
|
22
|
+
s.add_development_dependency "rspec", "~> 2.6.0"
|
23
|
+
s.add_development_dependency "simplecov", "~> 0.4.2"
|
24
|
+
s.add_development_dependency("rb-fsevent", "~> 0.4.3") if RUBY_PLATFORM =~ /darwin/i
|
25
|
+
s.add_development_dependency "guard", "~> 0.6.2"
|
26
|
+
s.add_development_dependency "guard-bundler", "~> 0.1.3"
|
27
|
+
s.add_development_dependency "guard-rspec", "~> 0.4.2"
|
28
|
+
s.add_development_dependency "timecop", "~> 0.3.5"
|
29
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
describe LRUCache do
|
2
|
+
it "should never exceed its max_size" do
|
3
|
+
c = LRUCache.new(:max_size => 7)
|
4
|
+
(1..100).each do |i|
|
5
|
+
c[i] = rand(2**16)
|
6
|
+
c.size.should <= 7
|
7
|
+
end
|
8
|
+
c.size.should == 7
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should expire the eldest entries first" do
|
12
|
+
c = LRUCache.new(:max_size => 3)
|
13
|
+
c[1] = 'a'
|
14
|
+
c[2] = 'b'
|
15
|
+
c[3] = 'c'
|
16
|
+
c[2].should == 'b'
|
17
|
+
c[1].should == 'a'
|
18
|
+
c[3].should == 'c'
|
19
|
+
c[4] = 'd' # Out of space! Throws out the least-recently used (2 => 'b').
|
20
|
+
c.keys.sort.should == [1,3,4]
|
21
|
+
c[5] = 'e'
|
22
|
+
c.keys.sort.should == [3,4,5]
|
23
|
+
c[1] = 'a'
|
24
|
+
c.keys.sort.should == [1,4,5]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return the default value for expired and non-existent entries" do
|
28
|
+
default = double(:default)
|
29
|
+
c = LRUCache.new(:max_size => 3, :default => default)
|
30
|
+
c[:a].should == default
|
31
|
+
c[:a] = 'a'
|
32
|
+
c[:a].should == 'a'
|
33
|
+
c[:b] = 'b'
|
34
|
+
c[:c] = 'c'
|
35
|
+
c[:d] = 'd'
|
36
|
+
c[:a].should == default
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should honor TTL" do
|
40
|
+
c = LRUCache.new(:expires => 20)
|
41
|
+
now = Time.now
|
42
|
+
Timecop.freeze(now) do
|
43
|
+
c.store(:a, 'a')
|
44
|
+
c.store(:b, 'b', now + 50)
|
45
|
+
c.store(:c, 'c', 50)
|
46
|
+
c[:a].should == 'a'
|
47
|
+
c[:b].should == 'b'
|
48
|
+
c[:c].should == 'c'
|
49
|
+
c.size.should == 3
|
50
|
+
end
|
51
|
+
Timecop.freeze(now + 19) do
|
52
|
+
c[:a].should == 'a'
|
53
|
+
c[:b].should == 'b'
|
54
|
+
c[:c].should == 'c'
|
55
|
+
c.size.should == 3
|
56
|
+
end
|
57
|
+
Timecop.freeze(now + 49) do
|
58
|
+
c[:a].should be_nil
|
59
|
+
c[:b].should == 'b'
|
60
|
+
c[:c].should == 'c'
|
61
|
+
c.size.should == 2
|
62
|
+
end
|
63
|
+
Timecop.freeze(now + 50) do
|
64
|
+
c[:a].should be_nil
|
65
|
+
c[:b].should be_nil
|
66
|
+
c[:c].should be_nil
|
67
|
+
c.size.should == 0
|
68
|
+
end
|
69
|
+
c[:a].should be_nil
|
70
|
+
c[:b].should be_nil
|
71
|
+
c[:c].should be_nil
|
72
|
+
c.size.should == 0
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should have a default max_size of 100" do
|
76
|
+
LRUCache.new.max_size.should == 100
|
77
|
+
LRUCache.new(:max_size => 82).max_size.should == 82
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can be cleared" do
|
81
|
+
c = LRUCache.new
|
82
|
+
(1..100).each {|i| c[i] = rand(2**16)}
|
83
|
+
c.size.should == 100
|
84
|
+
c.clear
|
85
|
+
c.size.should == 0
|
86
|
+
c.keys.should == []
|
87
|
+
end
|
88
|
+
|
89
|
+
describe ".include?(key)" do
|
90
|
+
it "affects the access time of the key" do
|
91
|
+
c = LRUCache.new(:max_size => 3)
|
92
|
+
c[1] = 'a'
|
93
|
+
c[2] = 'b'
|
94
|
+
c[3] = 'c'
|
95
|
+
c.include?(1).should be_true
|
96
|
+
c[4] = 'd'
|
97
|
+
c[5] = 'e'
|
98
|
+
c.include?(1).should be_true
|
99
|
+
c.include?(2).should be_false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lrucache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Chris Johnson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-07 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: PriorityQueue
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 31
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 1
|
33
|
+
- 2
|
34
|
+
version: 0.1.2
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 23
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 6
|
49
|
+
- 0
|
50
|
+
version: 2.6.0
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: simplecov
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 11
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 4
|
65
|
+
- 2
|
66
|
+
version: 0.4.2
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rb-fsevent
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 9
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
- 4
|
81
|
+
- 3
|
82
|
+
version: 0.4.3
|
83
|
+
type: :development
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: guard
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
- 6
|
97
|
+
- 2
|
98
|
+
version: 0.6.2
|
99
|
+
type: :development
|
100
|
+
version_requirements: *id005
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: guard-bundler
|
103
|
+
prerelease: false
|
104
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 29
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
- 1
|
113
|
+
- 3
|
114
|
+
version: 0.1.3
|
115
|
+
type: :development
|
116
|
+
version_requirements: *id006
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: guard-rspec
|
119
|
+
prerelease: false
|
120
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 11
|
126
|
+
segments:
|
127
|
+
- 0
|
128
|
+
- 4
|
129
|
+
- 2
|
130
|
+
version: 0.4.2
|
131
|
+
type: :development
|
132
|
+
version_requirements: *id007
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
name: timecop
|
135
|
+
prerelease: false
|
136
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
hash: 25
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
- 3
|
145
|
+
- 5
|
146
|
+
version: 0.3.5
|
147
|
+
type: :development
|
148
|
+
version_requirements: *id008
|
149
|
+
description: A simple LRU-cache based on a hash and priority queue
|
150
|
+
email:
|
151
|
+
- chris@kindkid.com
|
152
|
+
executables: []
|
153
|
+
|
154
|
+
extensions: []
|
155
|
+
|
156
|
+
extra_rdoc_files: []
|
157
|
+
|
158
|
+
files:
|
159
|
+
- .gitignore
|
160
|
+
- .rvmrc
|
161
|
+
- Gemfile
|
162
|
+
- Guardfile
|
163
|
+
- README.md
|
164
|
+
- Rakefile
|
165
|
+
- lib/lrucache.rb
|
166
|
+
- lib/lrucache/version.rb
|
167
|
+
- lrucache.gemspec
|
168
|
+
- spec/lrucache_spec.rb
|
169
|
+
- spec/spec_helper.rb
|
170
|
+
has_rdoc: true
|
171
|
+
homepage: ""
|
172
|
+
licenses: []
|
173
|
+
|
174
|
+
post_install_message:
|
175
|
+
rdoc_options: []
|
176
|
+
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
none: false
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
hash: 3
|
185
|
+
segments:
|
186
|
+
- 0
|
187
|
+
version: "0"
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
|
+
none: false
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
hash: 3
|
194
|
+
segments:
|
195
|
+
- 0
|
196
|
+
version: "0"
|
197
|
+
requirements: []
|
198
|
+
|
199
|
+
rubyforge_project: lrucache
|
200
|
+
rubygems_version: 1.6.2
|
201
|
+
signing_key:
|
202
|
+
specification_version: 3
|
203
|
+
summary: A simple LRU-cache based on a hash and priority queue
|
204
|
+
test_files:
|
205
|
+
- spec/lrucache_spec.rb
|
206
|
+
- spec/spec_helper.rb
|