lru_cache 1.0.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.
- data/.rspec +3 -0
- data/LICENSE +21 -0
- data/README.markdown +41 -0
- data/lib/lru_cache.rb +95 -0
- data/lib/lru_cache/thread_safe.rb +37 -0
- data/lru_cache.gemspec +15 -0
- data/spec/lru_cache_spec.rb +142 -0
- data/spec/thread_safe_spec.rb +7 -0
- metadata +69 -0
data/.rspec
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2012 by Brendan Baldwin
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.markdown
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# LRUCache
|
2
|
+
|
3
|
+
It's a simple in-memory cache that expires the least recently used item
|
4
|
+
in the cache when the count of items in the cache reaches a fixed limit.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
require "lru_cache"
|
9
|
+
|
10
|
+
# create a new cache with a 1,000 item limit
|
11
|
+
cache = LRUCache.new(1_000)
|
12
|
+
|
13
|
+
# get or set item in cache
|
14
|
+
cache["some key"] ||= expensive_operation
|
15
|
+
|
16
|
+
By default the LRUCache is not thread-safe. If you intend to access the
|
17
|
+
cache in multi-threaded code, use LRUCache::ThreadSafe.
|
18
|
+
|
19
|
+
require "lru_cache/thread_safe"
|
20
|
+
|
21
|
+
# create a new thread-safe cache with a 1,000 item limit
|
22
|
+
cache = LRUCache::ThreadSafe.new(1_000)
|
23
|
+
|
24
|
+
# get or set item in cache
|
25
|
+
cache["some key"] ||= expensive_operation
|
26
|
+
|
27
|
+
## Public Instance Methods
|
28
|
+
|
29
|
+
* `#clear` empties the cache
|
30
|
+
* `#count` returns the number of items currently in cache
|
31
|
+
* `#delete(key)` removes an item from the cache
|
32
|
+
* `#get(key)` returns the cached value or nil
|
33
|
+
* `#limit` returns the cache limit (defaults to 1)
|
34
|
+
* `#limit=value` sets the cache limit to value
|
35
|
+
* `#set(key, value)` stores the value in the cache
|
36
|
+
* `#[key]` alias for `#get(key)`
|
37
|
+
* `#[key]=value` alias for `#set(key, value)`
|
38
|
+
|
39
|
+
## License
|
40
|
+
|
41
|
+
This code is released under the MIT license, and is copyright 2012 by Brendan Baldwin. Please see the accompanying LICENSE file for the full text of the license.
|
data/lib/lru_cache.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
class LRUCache
|
2
|
+
|
3
|
+
Error = Class.new RuntimeError
|
4
|
+
InvalidLimitError = Class.new Error
|
5
|
+
NoLimitError = Class.new Error
|
6
|
+
|
7
|
+
Node = Struct.new(:key, :value, :mru, :lru)
|
8
|
+
|
9
|
+
attr_reader :count, :limit
|
10
|
+
|
11
|
+
# Sets up the new cache, optionally assigning given limit.
|
12
|
+
def initialize(limit=nil)
|
13
|
+
@index = {}
|
14
|
+
@count = 0
|
15
|
+
self.limit = limit if limit
|
16
|
+
end
|
17
|
+
|
18
|
+
# Empties the cache of all values.
|
19
|
+
def clear
|
20
|
+
count.times.each { delete_lru }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Remove and return value from cache.
|
24
|
+
def delete(key)
|
25
|
+
node = @index.delete(key)
|
26
|
+
return unless node
|
27
|
+
@count -= 1
|
28
|
+
node.mru.lru = node.lru if node.mru
|
29
|
+
node.lru.mru = node.mru if node.lru
|
30
|
+
@mru = node.lru if @mru == node
|
31
|
+
@lru = node.mru if @lru == node
|
32
|
+
node.value
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return value from cache and sets it as most-recently-used.
|
36
|
+
def get(key)
|
37
|
+
node = @index[key]
|
38
|
+
if node
|
39
|
+
set_mru(node)
|
40
|
+
node.value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Assigns a new limit to the cache. Deletes least recently used
|
45
|
+
# values if new limit is less than the count.
|
46
|
+
def limit=(new_limit)
|
47
|
+
new_limit and new_limit >= 1 or raise InvalidLimitError, "limit must be 1 or greater"
|
48
|
+
extra_count = count - new_limit
|
49
|
+
extra_count.times { delete_lru }
|
50
|
+
@limit = new_limit
|
51
|
+
end
|
52
|
+
|
53
|
+
# Stores a value in the cache.
|
54
|
+
def set(key, value)
|
55
|
+
node = @index[key]
|
56
|
+
unless node
|
57
|
+
node = Node.new(key)
|
58
|
+
@index[key] = node
|
59
|
+
@count += 1
|
60
|
+
delete_lru if count > ensure_limit
|
61
|
+
end
|
62
|
+
set_mru(node)
|
63
|
+
node.value = value
|
64
|
+
end
|
65
|
+
|
66
|
+
alias [] get
|
67
|
+
alias []= set
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Deletes the least recently used value from the cache.
|
72
|
+
def delete_lru
|
73
|
+
return unless @lru
|
74
|
+
@count -= 1
|
75
|
+
@index.delete(@lru.key)
|
76
|
+
@lru = @lru.mru
|
77
|
+
@lru.lru = nil if @lru
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the limit if set, otherwise raises exception.
|
81
|
+
def ensure_limit
|
82
|
+
limit || raise(NoLimitError, "limit not set for LRUCache")
|
83
|
+
end
|
84
|
+
|
85
|
+
# Advances a node to the most recently used spot.
|
86
|
+
def set_mru(node)
|
87
|
+
return if node == @mru
|
88
|
+
@mru.mru = node if @mru
|
89
|
+
node.lru.mru = node.mru if node.lru
|
90
|
+
node.lru = @mru
|
91
|
+
@lru = node.mru if count > 1 and @lru == node
|
92
|
+
@lru ||= node
|
93
|
+
@mru = node
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "lru_cache"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
class LRUCache::ThreadSafe < LRUCache
|
5
|
+
|
6
|
+
def initialize(limit=nil)
|
7
|
+
super
|
8
|
+
@mutex = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def count
|
12
|
+
@mutex.synchronize { super }
|
13
|
+
end
|
14
|
+
|
15
|
+
def clear
|
16
|
+
@mutex.synchronize { super }
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(key)
|
20
|
+
@mutex.synchronize { super }
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key)
|
24
|
+
@mutex.synchronize { super }
|
25
|
+
end
|
26
|
+
|
27
|
+
def limit=(new_limit)
|
28
|
+
@mutex.synchronize { super }
|
29
|
+
end
|
30
|
+
|
31
|
+
def set(key, value)
|
32
|
+
@mutex.synchronize { super }
|
33
|
+
end
|
34
|
+
|
35
|
+
alias [] get
|
36
|
+
alias []= set
|
37
|
+
end
|
data/lru_cache.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'lru_cache'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.authors = ['Brendan Baldwin']
|
5
|
+
s.email = ['brendan@usergenic.com']
|
6
|
+
s.homepage = 'https://github.com/brendan/lru_cache'
|
7
|
+
s.summary = %q{a simple LRU cache in ruby that uses a doubley-linked list and a hash}
|
8
|
+
s.description = %q{its performant / constant o(1)}
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.executables = s.files.grep(/^bin\//).map{ |f| File.basename(f) }
|
11
|
+
s.require_paths = ['lib']
|
12
|
+
|
13
|
+
s.add_development_dependency 'rspec'
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "lru_cache"
|
2
|
+
|
3
|
+
describe LRUCache do
|
4
|
+
|
5
|
+
alias cache subject
|
6
|
+
|
7
|
+
describe ".new" do
|
8
|
+
|
9
|
+
it "allows assignment of the limit" do
|
10
|
+
LRUCache.new.limit.should be_nil
|
11
|
+
LRUCache.new(5).limit.should == 5
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#clear" do
|
16
|
+
|
17
|
+
it "empties the cache of all items" do
|
18
|
+
cache.limit = 3
|
19
|
+
cache.count.should == 0
|
20
|
+
cache.set("x", :x)
|
21
|
+
cache.set("y", :y)
|
22
|
+
cache.set("z", :z)
|
23
|
+
cache.count.should == 3
|
24
|
+
cache.get("x").should == :x
|
25
|
+
|
26
|
+
cache.clear
|
27
|
+
|
28
|
+
cache.count.should == 0
|
29
|
+
cache.get("x").should be_nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#count" do
|
34
|
+
|
35
|
+
it "returns the number of items currently in cache" do
|
36
|
+
cache.limit = 3
|
37
|
+
cache.count.should == 0
|
38
|
+
cache.set("x", :x)
|
39
|
+
cache.count.should == 1
|
40
|
+
cache.set("y", :y)
|
41
|
+
cache.count.should == 2
|
42
|
+
cache.set("x", :x2)
|
43
|
+
cache.count.should == 2
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#delete" do
|
48
|
+
|
49
|
+
it "removes the item from the cache" do
|
50
|
+
cache.limit = 1
|
51
|
+
cache.count.should == 0
|
52
|
+
cache.set("x", :x)
|
53
|
+
cache.get("x").should == :x
|
54
|
+
cache.count.should == 1
|
55
|
+
cache.delete("x").should == :x
|
56
|
+
cache.count.should == 0
|
57
|
+
cache.get("x").should be_nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#get" do
|
62
|
+
|
63
|
+
it "returns an item for key when present in the cache" do
|
64
|
+
cache.limit = 1
|
65
|
+
cache.set("x", :x)
|
66
|
+
cache.get("x").should == :x
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns nil when key is not in the cache" do
|
70
|
+
cache.get("x").should be_nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#limit" do
|
75
|
+
|
76
|
+
it "returns the maximum number of items in cache" do
|
77
|
+
cache.limit = 500
|
78
|
+
cache.limit.should == 500
|
79
|
+
end
|
80
|
+
|
81
|
+
it "has no default value" do
|
82
|
+
LRUCache.new.limit.should be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#limit=" do
|
87
|
+
|
88
|
+
it "does not accept numbers less than 1" do
|
89
|
+
expect { cache.limit = 0 }.to raise_error LRUCache::InvalidLimitError
|
90
|
+
end
|
91
|
+
|
92
|
+
it "sets the maximum number of items in cache" do
|
93
|
+
cache.limit = 500
|
94
|
+
cache.limit.should == 500
|
95
|
+
end
|
96
|
+
|
97
|
+
it "drops items from cache if limit is newly less than count" do
|
98
|
+
cache.limit = 3
|
99
|
+
|
100
|
+
cache.set("x", :x)
|
101
|
+
cache.set("y", :y)
|
102
|
+
cache.set("z", :z)
|
103
|
+
|
104
|
+
cache.count.should == 3
|
105
|
+
|
106
|
+
cache.limit = 2
|
107
|
+
cache.count.should == 2
|
108
|
+
|
109
|
+
cache.get("x").should be_nil
|
110
|
+
cache.get("y").should == :y
|
111
|
+
cache.get("z").should == :z
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#set" do
|
116
|
+
|
117
|
+
it "puts an item in the cache" do
|
118
|
+
cache.limit = 1
|
119
|
+
cache.get("x").should be_nil
|
120
|
+
cache.set("x", :x)
|
121
|
+
cache.get("x").should == :x
|
122
|
+
end
|
123
|
+
|
124
|
+
it "will raise an exception when limit is not set" do
|
125
|
+
expect { LRUCache.new.set("x", :x) }.to raise_error LRUCache::NoLimitError
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#[]" do
|
130
|
+
|
131
|
+
it "is an alias for get" do
|
132
|
+
cache.method(:[]).should == cache.method(:get)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "#[]=" do
|
137
|
+
|
138
|
+
it "is an alias for set" do
|
139
|
+
cache.method(:[]=).should == cache.method(:set)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lru_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brendan Baldwin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: its performant / constant o(1)
|
31
|
+
email:
|
32
|
+
- brendan@usergenic.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .rspec
|
38
|
+
- LICENSE
|
39
|
+
- README.markdown
|
40
|
+
- lib/lru_cache.rb
|
41
|
+
- lib/lru_cache/thread_safe.rb
|
42
|
+
- lru_cache.gemspec
|
43
|
+
- spec/lru_cache_spec.rb
|
44
|
+
- spec/thread_safe_spec.rb
|
45
|
+
homepage: https://github.com/brendan/lru_cache
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.24
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: a simple LRU cache in ruby that uses a doubley-linked list and a hash
|
69
|
+
test_files: []
|