lru_cache 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ -fd
3
+ --fail-fast
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
+
@@ -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.
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require "lru_cache/thread_safe"
2
+
3
+ describe LRUCache::ThreadSafe do
4
+
5
+ # TODO: Do something clever to demonstrate that mutex synchronization ensures thread safety.
6
+
7
+ 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: []