This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
require 'enumerator'
|
||||
|
||||
module Hashery
|
||||
|
||||
# Hash with LRU expiry policy. There are at most max_size elements in a
|
||||
# LRUHash. When adding more elements old elements are removed according
|
||||
# to LRU policy.
|
||||
#
|
||||
# Based on Robert Klemme's LRUHash class.
|
||||
#
|
||||
# LRUHash, Copyright (c) 2010 Robert Klemme.
|
||||
#
|
||||
class LRUHash
|
||||
|
||||
include Enumerable
|
||||
|
||||
attr_reader :max_size
|
||||
|
||||
attr_accessor :default
|
||||
attr_accessor :default_proc
|
||||
attr_accessor :release_proc
|
||||
|
||||
#
|
||||
# Initialize new LRUHash instance.
|
||||
#
|
||||
# max_size -
|
||||
# default_value -
|
||||
# block -
|
||||
#
|
||||
def initialize(max_size, default_value=nil, &block)
|
||||
@max_size = normalize_max(max_size)
|
||||
@default = default_value
|
||||
@default_proc = block
|
||||
|
||||
@h = {}
|
||||
@head = Node.new
|
||||
@tail = front(Node.new)
|
||||
end
|
||||
|
||||
#
|
||||
# Iterate over each pair.
|
||||
#
|
||||
def each_pair
|
||||
if block_given?
|
||||
each_node do |n|
|
||||
yield [n.key, n.value]
|
||||
end
|
||||
else
|
||||
enum_for :each_pair
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Same as each pair.
|
||||
#
|
||||
alias each each_pair
|
||||
|
||||
#
|
||||
# Iterate over each key.
|
||||
#
|
||||
def each_key
|
||||
if block_given?
|
||||
each_node do |n|
|
||||
yield n.key
|
||||
end
|
||||
else
|
||||
enum_for :each_key
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Iterate over each value.
|
||||
#
|
||||
def each_value
|
||||
if block_given?
|
||||
each_node do |n|
|
||||
yield n.value
|
||||
end
|
||||
else
|
||||
enum_for :each_value
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Size of the hash.
|
||||
#
|
||||
def size
|
||||
@h.size
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def empty?
|
||||
@head.succ.equal? @tail
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def fetch(key, &b)
|
||||
n = @h[key]
|
||||
|
||||
if n
|
||||
front(n).value
|
||||
else
|
||||
(b || FETCH)[key]
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def [](key)
|
||||
fetch(key) do |k|
|
||||
@default_proc ? @default_proc[self, k] : default
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def keys
|
||||
@h.keys
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def values
|
||||
@h.map {|k,n| n.value}
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def has_key?(key)
|
||||
@h.has_key? key
|
||||
end
|
||||
|
||||
alias key? has_key?
|
||||
alias member? has_key?
|
||||
alias include? has_key?
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def has_value?(value)
|
||||
each_pair do |k, v|
|
||||
return true if value.eql? v
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
alias value? has_value?
|
||||
|
||||
def values_at(*key_list)
|
||||
key_list.map {|k| self[k]}
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def assoc(key)
|
||||
n = @h[key]
|
||||
|
||||
if n
|
||||
front(n)
|
||||
[n.key, n.value]
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def rassoc(value)
|
||||
each_node do |n|
|
||||
if value.eql? n.value
|
||||
front(n)
|
||||
return [n.key, n.value]
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def key(value)
|
||||
pair = rassoc(value) and pair.first
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def store(key, value)
|
||||
# same optimization as in Hash
|
||||
key = key.dup.freeze if String === key && !key.frozen?
|
||||
|
||||
n = @h[key]
|
||||
|
||||
unless n
|
||||
if size == max_size
|
||||
# reuse node to optimize memory usage
|
||||
n = delete_oldest
|
||||
n.key = key
|
||||
n.value = value
|
||||
else
|
||||
n = Node.new key, value
|
||||
end
|
||||
|
||||
@h[key] = n
|
||||
end
|
||||
|
||||
front(n).value = value
|
||||
end
|
||||
|
||||
alias []= store
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def delete(key)
|
||||
n = @h[key] and remove_node(n).value
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def delete_if
|
||||
each_node do |n|
|
||||
remove_node n if yield n.key, n.value
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def max_size=(limit)
|
||||
limit = normalize_max(limit)
|
||||
|
||||
while size > limit
|
||||
delete_oldest
|
||||
end
|
||||
|
||||
@max_size = limit
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def clear
|
||||
until empty?
|
||||
delete_oldest
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def to_s
|
||||
s = nil
|
||||
each_pair {|k, v| (s ? (s << ', ') : s = '{') << k.to_s << '=>' << v.to_s}
|
||||
s ? (s << '}') : '{}'
|
||||
end
|
||||
|
||||
alias inspect to_s
|
||||
|
||||
private
|
||||
|
||||
#
|
||||
# Iterate nodes.
|
||||
#
|
||||
def each_node
|
||||
n = @head.succ
|
||||
|
||||
until n.equal? @tail
|
||||
succ = n.succ
|
||||
yield n
|
||||
n = succ
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
#
|
||||
# Move node to front.
|
||||
#
|
||||
# node - [Node]
|
||||
#
|
||||
def front(node)
|
||||
node.insert_after(@head)
|
||||
end
|
||||
|
||||
#
|
||||
# Remove the node and invoke release_proc
|
||||
# if set
|
||||
#
|
||||
# node - [Node]
|
||||
#
|
||||
def remove_node(node)
|
||||
n = @h.delete(node.key)
|
||||
n.unlink
|
||||
release_proc and release_proc[n.key, n.value]
|
||||
n
|
||||
end
|
||||
|
||||
#
|
||||
# Remove the oldest node returning the node
|
||||
#
|
||||
def delete_oldest
|
||||
n = @tail.pred
|
||||
raise "Cannot delete from empty hash" if @head.equal? n
|
||||
remove_node n
|
||||
end
|
||||
|
||||
#
|
||||
# Normalize the argument in order to be usable as max_size
|
||||
# criterion is that n.to_i must be an Integer and it must
|
||||
# be larger than zero.
|
||||
#
|
||||
# n - [#to_i] max size
|
||||
#
|
||||
def normalize_max(n)
|
||||
n = n.to_i
|
||||
raise ArgumentError, 'Invalid max_size: %p' % n unless Integer === n && n > 0
|
||||
n
|
||||
end
|
||||
|
||||
#
|
||||
FETCH = Proc.new {|k| raise KeyError, 'key not found'}
|
||||
|
||||
# A single node in the doubly linked LRU list of nodes.
|
||||
Node = Struct.new :key, :value, :pred, :succ do
|
||||
def unlink
|
||||
pred.succ = succ if pred
|
||||
succ.pred = pred if succ
|
||||
self.succ = self.pred = nil
|
||||
self
|
||||
end
|
||||
|
||||
def insert_after(node)
|
||||
raise 'Cannot insert after self' if equal? node
|
||||
return self if node.succ.equal? self
|
||||
|
||||
unlink
|
||||
|
||||
self.succ = node.succ
|
||||
self.pred = node
|
||||
|
||||
node.succ.pred = self if node.succ
|
||||
node.succ = self
|
||||
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user