lrucache 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Guardfile CHANGED
@@ -10,5 +10,6 @@ guard 'rspec', :cli => '-c --format documentation -r ./spec/spec_helper.rb',
10
10
  :version => 2 do
11
11
  watch(%r{^spec/.+_spec\.rb})
12
12
  watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
13
+ watch('lib/lrucache.rb') { "spec/lrucache_spec.rb" }
13
14
  watch('spec/spec_helper.rb') { "spec" }
14
15
  end
data/README.md CHANGED
@@ -21,7 +21,7 @@ Example
21
21
 
22
22
  TTL (time-to-live)
23
23
  ==================
24
- cache = LRUCache.new(:expires => 1.hour)
24
+ cache = LRUCache.new(:ttl => 1.hour)
25
25
  cache.store("banana", "yellow")
26
26
  cache.store("monkey", "banana", Time.now + 3.days)
27
27
  # or ...
data/lib/lrucache.rb CHANGED
@@ -4,13 +4,14 @@ require "priority_queue"
4
4
  # Not thread-safe!
5
5
  class LRUCache
6
6
 
7
- attr_reader :default, :max_size
7
+ attr_reader :default, :max_size, :ttl
8
8
 
9
9
  def initialize(opts={})
10
- @max_size = (opts[:max_size] || 100).to_i
10
+ @max_size = Integer(opts[:max_size] || 100)
11
11
  @default = opts[:default]
12
- @expires = (opts[:expires] || 0).to_f
13
- raise "max_size must be greather than zero" unless @max_size > 0
12
+ @ttl = Float(opts[:ttl] || 0)
13
+ raise "max_size must be greater than zero" unless @max_size > 0
14
+ raise "ttl must be positive or zero" unless @ttl >= 0
14
15
  @pqueue = PriorityQueue.new
15
16
  @data = {}
16
17
  @counter = 0
@@ -19,6 +20,7 @@ class LRUCache
19
20
  def clear
20
21
  @data.clear
21
22
  @pqueue.delete_min until @pqueue.empty?
23
+ @counter = 0 #might as well
22
24
  end
23
25
 
24
26
  def include?(key)
@@ -34,38 +36,48 @@ class LRUCache
34
36
  end
35
37
  end
36
38
 
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
39
+ def store(key, value, ttl=nil)
40
+ evict_lru! unless @data.include?(key) || @data.size < @max_size
41
+ ttl ||= @ttl
42
+ expires =
43
+ if ttl.is_a?(Time)
44
+ ttl
44
45
  else
45
- expires = expires.to_f
46
- (expires > 0) ? (Time.now + expires) : nil
46
+ ttl = Float(ttl)
47
+ (ttl > 0) ? (Time.now + ttl) : nil
47
48
  end
48
- @data[key] = [value, expiration]
49
+ @data[key] = [value, expires]
49
50
  access(key)
50
51
  end
51
52
 
52
53
  alias :[]= :store
53
54
 
54
- def fetch(key)
55
+ def fetch(key, ttl=nil)
55
56
  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)
57
+ unless datum.nil?
58
+ value, expires = datum
59
+ if expires.nil? || expires > Time.now # no expiration, or not expired
60
+ access(key)
61
+ return value
62
+ else # expired
63
+ delete(key)
64
+ end
65
+ end
66
+ if block_given?
67
+ value = yield
68
+ store(key, value, ttl)
60
69
  value
61
- else # expired
62
- delete(key)
70
+ else
63
71
  @default
64
72
  end
65
73
  end
66
74
 
67
75
  alias :[] :fetch
68
76
 
77
+ def empty?
78
+ size == 0
79
+ end
80
+
69
81
  def size
70
82
  @data.size
71
83
  end
@@ -75,13 +87,13 @@ class LRUCache
75
87
  end
76
88
 
77
89
  def delete(key)
78
- @data.delete(key)
79
90
  @pqueue.delete(key)
91
+ @data.delete(key)
80
92
  end
81
93
 
82
94
  private
83
95
 
84
- def expire_lru!
96
+ def evict_lru!
85
97
  key, priority = @pqueue.delete_min
86
98
  @data.delete(key) unless priority.nil?
87
99
  end
@@ -1,3 +1,3 @@
1
1
  class LRUCache
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,102 +1,471 @@
1
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
2
+ describe ".new" do
3
+
4
+ it "should default :max_size to 100" do
5
+ LRUCache.new.max_size.should == 100
6
+ end
7
+ it "should accept a :max_size parameter" do
8
+ LRUCache.new(:max_size => 7).max_size.should == 7
9
+ end
10
+ it "should raise an exception if :max_size parameter can't be converted to an integer" do
11
+ expect { LRUCache.new(:max_size => "moocow") }.to raise_exception
12
+ end
13
+ it "should raise an exception if :max_size parameter is converted to a negative integer" do
14
+ expect { LRUCache.new(:max_size => -1) }.to raise_exception
15
+ end
16
+
17
+ it "should default :default to nil" do
18
+ LRUCache.new.default.should be_nil
19
+ end
20
+ it "should accept a :default parameter" do
21
+ default = double(:default)
22
+ LRUCache.new(:default => default).default.should == default
23
+ end
24
+
25
+ it "should default :ttl to 0 (no expiration)" do
26
+ LRUCache.new.ttl.should == 0
27
+ end
28
+ it "should accept a :ttl parameter" do
29
+ LRUCache.new(:ttl => 98.6).ttl.should == 98.6
30
+ end
31
+ it "should raise an exception if :ttl parameter can't be converted to a float" do
32
+ expect { LRUCache.new(:ttl => "moocow") }.to raise_exception
33
+ end
34
+ it "should raise an exception if :ttl parameter is converted to a negative float" do
35
+ expect { LRUCache.new(:ttl => -1) }.to raise_exception
36
+ end
37
+
38
+ it "should initially be empty" do
39
+ LRUCache.new.should be_empty
7
40
  end
8
- c.size.should == 7
9
41
  end
10
42
 
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]
43
+ describe ".clear" do
44
+ it "should empty the hash and the priority queue" do
45
+ c = LRUCache.new
46
+ 10.times { c[rand(2**16)] = :x }
47
+ c.should_not be_empty
48
+ c.instance_variable_get(:@data).should_not be_empty
49
+ c.instance_variable_get(:@pqueue).should_not be_empty
50
+
51
+ c.clear
52
+
53
+ c.should be_empty
54
+ c.instance_variable_get(:@data).should be_empty
55
+ c.instance_variable_get(:@pqueue).should be_empty
56
+ end
25
57
  end
26
58
 
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
59
+ describe ".include?(key)" do
60
+ before(:each) { @cache = LRUCache.new }
61
+ context "when the key is not present" do
62
+ it "should return false" do
63
+ @cache.include?(:a).should be_false
64
+ end
65
+ it "should not affect priorities" do
66
+ @cache.should_not_receive(:access)
67
+ @cache.include?(:a)
68
+ end
69
+ end
70
+ context "when the key is present, but the value has expired" do
71
+ before(:each) do
72
+ now = Time.now
73
+ Timecop.freeze(now) { @cache.store(:a, 'a', now + 10) }
74
+ Timecop.freeze(now + 20)
75
+ end
76
+ after(:each) do
77
+ Timecop.return
78
+ end
79
+ it "should return false" do
80
+ @cache.include?(:a).should be_false
81
+ end
82
+ it "should delete the key" do
83
+ @cache.should_receive(:delete).with(:a)
84
+ @cache.include?(:a)
85
+ end
86
+ it "should not affect priorities" do
87
+ @cache.should_not_receive(:access)
88
+ @cache.include?(:a)
89
+ end
90
+ end
91
+ context "when the key is present, and the value has no expiration" do
92
+ before(:each) do
93
+ @cache.store(:a, 'a', nil)
94
+ end
95
+ it "should return true" do
96
+ @cache.include?(:a).should be_true
97
+ end
98
+ it "should update the key's access stamp" do
99
+ @cache.should_receive(:access).with(:a)
100
+ @cache.include?(:a)
101
+ end
102
+ end
103
+ context "when the key is present, and the value has not yet expired" do
104
+ before(:each) do
105
+ now = Time.now
106
+ Timecop.freeze(now)
107
+ @cache.store(:a, 'a', now + 10)
108
+ end
109
+ after(:each) do
110
+ Timecop.return
111
+ end
112
+ it "should return true" do
113
+ @cache.include?(:a).should be_true
114
+ end
115
+ it "should update the key's access stamp" do
116
+ @cache.should_receive(:access).with(:a)
117
+ @cache.include?(:a)
118
+ end
119
+ end
37
120
  end
38
121
 
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
122
+ describe ".store(key, value, ttl=nil)" do
123
+ context "regarding evictions" do
124
+ before(:each) do
125
+ @cache = LRUCache.new(:max_size => 2)
126
+ end
127
+ context "when the cache is not full" do
128
+ context "and the key is not present" do
129
+ it "should not evict an entry" do
130
+ @cache.should_not_receive(:evict_lru!)
131
+ @cache.store(:a, 'a')
132
+ end
133
+ it "should store the value" do
134
+ @cache.store(:a, 'a')
135
+ @cache.fetch(:a).should == 'a'
136
+ end
137
+ it "should update the key's access stamp" do
138
+ @cache.should_receive(:access).with(:a)
139
+ @cache.store(:a, 'a')
140
+ end
141
+ end
142
+ context "and the key is present" do
143
+ it "should not evict an entry" do
144
+ @cache.should_not_receive(:evict_lru!)
145
+ @cache.store(:a, 'a')
146
+ end
147
+ it "should store the value" do
148
+ @cache.store(:a, 'a')
149
+ @cache.fetch(:a).should == 'a'
150
+ end
151
+ it "should update the key's access stamp" do
152
+ @cache.should_receive(:access).with(:a)
153
+ @cache.store(:a, 'a')
154
+ end
155
+ end
156
+ end
157
+ context "when the cache is full" do
158
+ before(:each) do
159
+ @cache = LRUCache.new(:max_size => 2)
160
+ @cache[:b] = 'b'
161
+ @cache[:c] = 'c'
162
+ @lru = :b
163
+ end
164
+ context "and the key is not present" do
165
+ it "should evict the least-recently used entry from the cache" do
166
+ @cache.keys.should include(@lru)
167
+ @cache.store(:a, 'a')
168
+ @cache.keys.should_not include(@lru)
169
+ end
170
+ it "should store the value" do
171
+ @cache.store(:a, 'a')
172
+ @cache.fetch(:a).should == 'a'
173
+ end
174
+ it "should update the key's access stamp" do
175
+ @cache.should_receive(:access).with(:a)
176
+ @cache.store(:a, 'a')
177
+ end
178
+ end
179
+ context "and the key is present" do
180
+ it "should not evict an entry" do
181
+ @cache.should_not_receive(:evict_lru!)
182
+ @cache.store(:c, 'c')
183
+ end
184
+ it "should store the value" do
185
+ @cache.store(:c, 'c')
186
+ @cache.fetch(:c).should == 'c'
187
+ end
188
+ it "should update the key's access stamp" do
189
+ @cache.should_receive(:access).with(:c)
190
+ @cache.store(:c, 'c')
191
+ end
192
+ end
193
+ end
194
+ end
195
+ context "when ttl is not given and the cache's default ttl is zero" do
196
+ it "should set the entry with no expiration time" do
197
+ c = LRUCache.new(:ttl => 0)
198
+ c.store(:a,'a')
199
+ stored = c.instance_variable_get(:@data)[:a]
200
+ stored.should == ['a', nil]
201
+ end
202
+ end
203
+ context "when ttl is not given and the cache's default ttl is greater than zero" do
204
+ it "should set the entry to expire that many seconds in the future" do
205
+ c = LRUCache.new(:ttl => 1)
206
+ now = Time.now
207
+ Timecop.freeze(now) { c.store(:a,'a') }
208
+ stored = c.instance_variable_get(:@data)[:a]
209
+ stored.last.should == now + 1
210
+ end
211
+ end
212
+ context "when ttl is a Time" do
213
+ it "should set the entry to expire at the given time" do
214
+ c = LRUCache.new
215
+ ttl = Time.now + 246
216
+ c.store(:a, 'a', ttl)
217
+ stored = c.instance_variable_get(:@data)[:a]
218
+ stored.last.should == ttl
219
+ end
220
+ end
221
+ context "when ttl can be parsed as a float" do
222
+ it "should set the entry to expire that many seconds in the future" do
223
+ c = LRUCache.new
224
+ now = Time.now
225
+ Timecop.freeze(now) { c.store(:a, 'a', "98.6") }
226
+ stored = c.instance_variable_get(:@data)[:a]
227
+ stored.last.should == now + 98.6
228
+ end
229
+ end
230
+ context "when ttl cannot be parsed as a float" do
231
+ it "should raise an exception" do
232
+ c = LRUCache.new
233
+ expect { c.store(:a, 'a', "moocow") }.to raise_exception
234
+ end
68
235
  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
236
  end
74
237
 
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
238
+ describe ".fetch(key, ttl=nil)" do
239
+ context "when no block is given" do
240
+ context "and the key does not exist" do
241
+ before(:each) do
242
+ @default = double(:default)
243
+ @cache = LRUCache.new(:default => @default)
244
+ end
245
+ it "should return the default value" do
246
+ @cache.fetch(:a).should == @default
247
+ end
248
+ it "should not affect the priorities" do
249
+ @cache.should_not_receive(:access)
250
+ @cache.fetch(:a)
251
+ end
252
+ end
253
+ context "and the key has been evicted" do
254
+ before(:each) do
255
+ @cache = LRUCache.new(:max_size => 2)
256
+ @cache[:a] = 'a'
257
+ @cache[:b] = 'b'
258
+ @cache[:c] = 'c'
259
+ end
260
+ it "should return the default value" do
261
+ @cache.fetch(:a).should == @default
262
+ end
263
+ it "should not affect the priorities" do
264
+ @cache.should_not_receive(:access)
265
+ @cache.fetch(:a)
266
+ end
267
+ end
268
+ context "and the key has expired" do
269
+ before(:each) do
270
+ @cache = LRUCache.new(:ttl => 10)
271
+ now = Time.now
272
+ Timecop.freeze(now) { @cache[:a] = 'a' }
273
+ Timecop.freeze(now + 20)
274
+ end
275
+ after(:each) do
276
+ Timecop.return
277
+ end
278
+ it "should return the default value" do
279
+ @cache.fetch(:a).should == @default
280
+ end
281
+ it "should not affect the priorities" do
282
+ @cache.should_not_receive(:access)
283
+ @cache.fetch(:a)
284
+ end
285
+ it "should delete the key" do
286
+ @cache.should_receive(:delete).with(:a)
287
+ @cache.fetch(:a)
288
+ end
289
+ end
290
+ context "and the key is present and un-expired" do
291
+ before(:each) do
292
+ @cache = LRUCache.new(:ttl => nil)
293
+ @cache[:a] = 'a'
294
+ end
295
+ it "should return the cached value" do
296
+ @cache.fetch(:a).should == 'a'
297
+ end
298
+ it "should update the key's access stamp" do
299
+ @cache.should_receive(:access).with(:a)
300
+ @cache.fetch(:a)
301
+ end
302
+ end
303
+ end
304
+ context "when a block is given" do
305
+ context "when the key does not exist" do
306
+ it "should call the block and store and return the result" do
307
+ c = LRUCache.new
308
+ ttl = double(:ttl)
309
+ result = double(:result)
310
+ c.should_receive(:store).with(:a, result, ttl)
311
+ c.fetch(:a, ttl){ result }.should == result
312
+ end
313
+ end
314
+ context "when the key has been evicted" do
315
+ it "should call the block and store and return the result" do
316
+ c = LRUCache.new
317
+ c[:a] = 'a'
318
+ c.send(:evict_lru!)
319
+ ttl = double(:ttl)
320
+ result = double(:result)
321
+ c.should_receive(:store).with(:a, result, ttl)
322
+ c.fetch(:a, ttl){ result }.should == result
323
+ end
324
+ end
325
+ context "when the key has expired" do
326
+ it "should call the block and store and return the result" do
327
+ c = LRUCache.new
328
+ now = Time.now
329
+ Timecop.freeze(now) { c.store(:a, 'a', now + 10) }
330
+ Timecop.freeze(now + 20) do
331
+ ttl = double(:ttl)
332
+ result = double(:result)
333
+ c.should_receive(:store).with(:a, result, ttl)
334
+ c.fetch(:a, ttl){ result }.should == result
335
+ end
336
+ end
337
+ end
338
+ context "when the key is present and un-expired" do
339
+ it "should return the cached value without calling the block" do
340
+ c = LRUCache.new(:ttl => nil)
341
+ c[:a] = 'a'
342
+ c.fetch(:a) { raise 'fail' }.should == 'a'
343
+ end
344
+ end
345
+ end
78
346
  end
79
347
 
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 == []
348
+ describe ".empty?" do
349
+ it "should return true if and only if size is zero" do
350
+ c = LRUCache.new
351
+ c.empty?.should be_true
352
+ c[:a] = 'a'
353
+ c.empty?.should be_false
354
+ c.clear
355
+ c.empty?.should be_true
356
+ end
87
357
  end
88
358
 
89
- describe ".include?(key)" do
90
- it "affects the access time of the key" do
91
- c = LRUCache.new(:max_size => 3)
359
+ describe ".size and .keys" do
360
+ it "should return a count / list of entries in the cache" do
361
+ c = LRUCache.new(:max_size => 10)
362
+ (1..10).each do |i|
363
+ c.size.should == i-1
364
+ c[i] = :x
365
+ c.size.should == i
366
+ c.keys.sort.should == (1..i).to_a
367
+ end
368
+ (1..3).each do |i|
369
+ c.delete(i)
370
+ c.size.should == 10 - i
371
+ c.keys.sort.should == ((i+1)..10).to_a
372
+ end
373
+ (1..3).each do |i|
374
+ c[i] = :x
375
+ c.size.should == 7+i
376
+ c.keys.sort.should == (1..i).to_a + (4..10).to_a
377
+ end
378
+ (1..10).each do |i|
379
+ c[i] = :x
380
+ c.size.should == 10
381
+ c.keys.sort.should == (1..10).to_a
382
+ end
383
+ c.clear
384
+ c.size.should == 0
385
+ c.keys.should == []
386
+ end
387
+ it "may include expired entries" do
388
+ c = LRUCache.new(:ttl => 10, :max_size => 100)
389
+ now = Time.now
390
+ (0..19).each do |i|
391
+ Timecop.freeze(now + i) do
392
+ c[i] = :x
393
+ c.size.should == i+1
394
+ c.keys.sort.should == (0..i).to_a
395
+ end
396
+ end
397
+ Timecop.freeze(now + 20) do
398
+ c.size.should == 20
399
+ (0..19).each do |i|
400
+ c.include?(i)
401
+ end
402
+ c.size.should == 9
403
+ c.keys.sort.should == (11..19).to_a
404
+ end
405
+ end
406
+ it "should always return a value less than or equal to the cache's max_size" do
407
+ c = LRUCache.new(:max_size => 7)
408
+ (1..100).each do |i|
409
+ c[i] = :x
410
+ c.size.should <= 7
411
+ end
412
+ end
413
+ end
414
+
415
+
416
+ describe ".delete(key)" do
417
+ it "should remove the key from the internal hash and priority queue" do
418
+ c = LRUCache.new
419
+ c[:a] = 'a'
420
+ c.instance_variable_get(:@data).should include(:a)
421
+ c.instance_variable_get(:@pqueue).should include([:a,1])
422
+ c.delete(:a)
423
+ c.instance_variable_get(:@data).should_not include(:a)
424
+ c.instance_variable_get(:@pqueue).should_not include([:a,1])
425
+ end
426
+ end
427
+
428
+ describe ".evict_lru!" do
429
+ it "should find the least-recently used entry, and delete it" do
430
+ c = LRUCache.new
92
431
  c[1] = 'a'
93
432
  c[2] = 'b'
94
433
  c[3] = 'c'
95
- c.include?(1).should be_true
96
434
  c[4] = 'd'
97
- c[5] = 'e'
98
- c.include?(1).should be_true
99
- c.include?(2).should be_false
435
+ c[:dne1].should be_nil # Doesn't affect LRU.
436
+ c[2].should == 'b'
437
+ c[1].should == 'a'
438
+ c[3].should == 'c'
439
+ c[4].should == 'd'
440
+ c[:dne2].should be_nil # Doesn't affect LRU.
441
+ c.keys.sort.should == [1,2,3,4]
442
+ c.send(:evict_lru!)
443
+ c.keys.sort.should == [1,3,4]
444
+ c.send(:evict_lru!)
445
+ c.keys.sort.should == [3,4]
446
+ c.send(:evict_lru!)
447
+ c.keys.sort.should == [4]
448
+ c.send(:evict_lru!)
449
+ c.keys.should == []
450
+ c.send(:evict_lru!)
451
+ c.keys.should == []
452
+ end
453
+ it "should leave the internal hash the same size as the priority queue" do
454
+ c = LRUCache.new
455
+ expected_size = 0
456
+ counter = 0
457
+ 100.times do
458
+ if rand < 0.8
459
+ c[counter += 1] = :x
460
+ expected_size += 1
461
+ else
462
+ c.send(:evict_lru!)
463
+ expected_size -= 1
464
+ end
465
+ expected_size = [expected_size,0].max
466
+ c.instance_variable_get(:@data).size.should == expected_size
467
+ c.instance_variable_get(:@pqueue).count.should == expected_size
468
+ end
100
469
  end
101
470
  end
102
471
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lrucache
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 0
9
8
  - 1
10
- version: 0.0.1
9
+ - 0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Chris Johnson