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 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: []