ttd_set_associative_cache 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/hash_map.rb +42 -0
- data/lib/linked_list.rb +119 -0
- data/lib/linked_list_node.rb +28 -0
- data/lib/set_associative_cache.rb +129 -0
- data/lib/ttd_set_associative_cache.rb +1 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 06a69ab6fc0062a060c62e6dc201d85b2eabb429
|
4
|
+
data.tar.gz: 1f48e20862824159ef183c741e1c85a6526fbd84
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f1fd7b3dc2ce886089797491c9fbce750e2af2a89e88fc3dc27ccf0e51431aa1a71ae596ed1fd5ba20e02809d683c82cfd50cc57e42f8b8dd90f6fd42b8b4117
|
7
|
+
data.tar.gz: 4809aeb717f20502a92992f017b7c2d3cf52cd37c20115cf40944b133e78ea5516ccd47e232384e0b5362597b83f2ec776af9c5759feef37587d6caa65d93226
|
data/lib/hash_map.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
class HashMap
|
2
|
+
def initialize(&key_generator_prc)
|
3
|
+
key_generator_prc ||= Proc.new { |val| val.hash }
|
4
|
+
@cache = {}
|
5
|
+
@key_generator_prc = key_generator_prc
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_key(val)
|
9
|
+
@key_generator_prc.call(val)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(val)
|
13
|
+
key = create_key(val)
|
14
|
+
@cache[key] = val
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove(val)
|
18
|
+
key = create_key(val)
|
19
|
+
@cache.delete(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(val)
|
23
|
+
add(val)
|
24
|
+
end
|
25
|
+
|
26
|
+
def include?(val)
|
27
|
+
key = create_key(val)
|
28
|
+
@cache.include?(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
@cache[key]
|
33
|
+
end
|
34
|
+
|
35
|
+
def length
|
36
|
+
@cache.length
|
37
|
+
end
|
38
|
+
|
39
|
+
def each
|
40
|
+
@cache.each { |key, value| yield key, value }
|
41
|
+
end
|
42
|
+
end
|
data/lib/linked_list.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative './linked_list.rb'
|
2
|
+
|
3
|
+
class LinkedList
|
4
|
+
attr_reader :length
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@head = LinkedListNode.new
|
8
|
+
@tail = LinkedListNode.new
|
9
|
+
@head.next = @tail
|
10
|
+
@tail.prev = @head
|
11
|
+
|
12
|
+
@length = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def first
|
16
|
+
@head.next == @tail ? nil : @head.next
|
17
|
+
end
|
18
|
+
|
19
|
+
def last
|
20
|
+
@tail.prev == @head ? nil : @tail.prev
|
21
|
+
end
|
22
|
+
|
23
|
+
def append(value)
|
24
|
+
node = value.class == LinkedListNode ? value : LinkedListNode.new(value)
|
25
|
+
|
26
|
+
old_last = @tail.prev
|
27
|
+
old_last.next = node
|
28
|
+
@tail.prev = node
|
29
|
+
node.prev = old_last
|
30
|
+
node.next = @tail
|
31
|
+
|
32
|
+
@length += 1
|
33
|
+
node
|
34
|
+
end
|
35
|
+
|
36
|
+
def prepend(value)
|
37
|
+
node = value.class == LinkedListNode ? value : LinkedListNode.new(value)
|
38
|
+
|
39
|
+
old_first = @head.next
|
40
|
+
old_first.prev = node
|
41
|
+
@head.next = node
|
42
|
+
node.next = old_first
|
43
|
+
node.prev = @head
|
44
|
+
|
45
|
+
@length += 1
|
46
|
+
node
|
47
|
+
end
|
48
|
+
|
49
|
+
def pop
|
50
|
+
return nil if @head.next == @tail
|
51
|
+
remove(@tail.prev)
|
52
|
+
end
|
53
|
+
|
54
|
+
def shift
|
55
|
+
return nil if @head.next == @tail
|
56
|
+
remove(@head.next)
|
57
|
+
end
|
58
|
+
|
59
|
+
def remove(node)
|
60
|
+
raise "Error in LinkedList#remove - given node is not a LinkedListNode, class is #{node.class}" unless node.class == LinkedListNode
|
61
|
+
return if node == @tail || node == @head
|
62
|
+
|
63
|
+
node.prev.next = node.next
|
64
|
+
node.next.prev = node.prev
|
65
|
+
node.prev = nil
|
66
|
+
node.next = nil
|
67
|
+
@length -= 1
|
68
|
+
|
69
|
+
node
|
70
|
+
end
|
71
|
+
|
72
|
+
def each
|
73
|
+
current = @head.next
|
74
|
+
|
75
|
+
until current == @tail do
|
76
|
+
yield current
|
77
|
+
current = current.next
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def each_with_index
|
82
|
+
current = @head.next
|
83
|
+
counter = 0
|
84
|
+
|
85
|
+
until current == @tail do
|
86
|
+
yield current, counter
|
87
|
+
current = current.next
|
88
|
+
counter += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def map(&prc)
|
93
|
+
prc ||= Proc.new { |node| node }
|
94
|
+
current = @head.next
|
95
|
+
result = []
|
96
|
+
|
97
|
+
until current == @tail do
|
98
|
+
result << prc.call(current)
|
99
|
+
current = current.next
|
100
|
+
end
|
101
|
+
|
102
|
+
result
|
103
|
+
end
|
104
|
+
|
105
|
+
def map_with_index(&prc)
|
106
|
+
prc ||= Proc.new { |node, idx| node }
|
107
|
+
current = @head.next
|
108
|
+
counter = 0
|
109
|
+
result = []
|
110
|
+
|
111
|
+
until current == @tail do
|
112
|
+
result << prc.call(current, counter)
|
113
|
+
current = current.next
|
114
|
+
counter += 1
|
115
|
+
end
|
116
|
+
|
117
|
+
result
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class LinkedListNode
|
2
|
+
attr_reader :prev, :next
|
3
|
+
attr_accessor :value
|
4
|
+
|
5
|
+
def initialize(value = nil)
|
6
|
+
raise "Error in LinkedListNode::new - value is a LinkedListNode instance" if value.class == LinkedListNode
|
7
|
+
|
8
|
+
@value = value
|
9
|
+
@prev = nil
|
10
|
+
@next = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def prev=(other_node)
|
14
|
+
_validate_node(other_node, :prev)
|
15
|
+
@prev = other_node
|
16
|
+
end
|
17
|
+
|
18
|
+
def next=(other_node)
|
19
|
+
_validate_node(other_node, :next)
|
20
|
+
@next = other_node
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _validate_node(node, function_name)
|
26
|
+
raise "Error in LinkedListNode##{function_name} - other_node is not a LinkedListNode instance" unless node.class == LinkedListNode || node.nil?
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require_relative './linked_list_node.rb'
|
2
|
+
require_relative './linked_list.rb'
|
3
|
+
require_relative './hash_map.rb'
|
4
|
+
require 'byebug'
|
5
|
+
|
6
|
+
class SetAssociativeCache
|
7
|
+
@@lru_replacement_proc = Proc.new { removed_node = @linked_list.pop; @hash_map.remove(removed_node) }
|
8
|
+
@@mru_replacement_proc = Proc.new { @linked_list.remove(@last_addition); @hash_map.remove(@last_addition) }
|
9
|
+
|
10
|
+
@@param_types = {
|
11
|
+
type: Symbol,
|
12
|
+
capacity: Fixnum,
|
13
|
+
key_generator_prc: Proc,
|
14
|
+
replacement_algo: Proc,
|
15
|
+
value_class: Class
|
16
|
+
}
|
17
|
+
|
18
|
+
def initialize(params = {})
|
19
|
+
_validate_params(params)
|
20
|
+
|
21
|
+
@hash_map = _initialize_hash_map(params)
|
22
|
+
@linked_list = LinkedList.new
|
23
|
+
@cache_type = params[:type] || :LRU
|
24
|
+
@replacement_algo = params[:replacement_algo] || _select_replacement_algo
|
25
|
+
@capacity = _initialize_capacity(params)
|
26
|
+
@value_class = params[:value_class]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Handles adding new values and updating existing values.
|
30
|
+
def add(value)
|
31
|
+
search_node = value.class == LinkedListNode ? value : LinkedListNode.new(value)
|
32
|
+
@value_class ||= search_node.value.class
|
33
|
+
|
34
|
+
raise "Error in SetAssociativeCache#add - value class '#{value.class}' should be '#{@value_class}'" \
|
35
|
+
unless search_node.value.class == @value_class
|
36
|
+
key = @hash_map.create_key(search_node)
|
37
|
+
node = @hash_map[key]
|
38
|
+
|
39
|
+
if node
|
40
|
+
# update value if needed (ex. if a part of the value was updated but the key stayed the same)
|
41
|
+
node.value = value;
|
42
|
+
|
43
|
+
@linked_list.remove(node)
|
44
|
+
else
|
45
|
+
node = LinkedListNode.new(value)
|
46
|
+
@hash_map.add(node)
|
47
|
+
instance_exec([], &@replacement_algo) if @hash_map.length > @capacity
|
48
|
+
end
|
49
|
+
|
50
|
+
@linked_list.prepend(node)
|
51
|
+
@last_addition = node
|
52
|
+
|
53
|
+
node
|
54
|
+
end
|
55
|
+
|
56
|
+
# Finds the node whose value matches a the corresponding key if it exists.
|
57
|
+
def get_node(value)
|
58
|
+
node = value.class == LinkedListNode ? value : LinkedListNode.new(value)
|
59
|
+
key = @hash_map.create_key(node)
|
60
|
+
@hash_map[key]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Loops over the nodes from head to tail in the linked list
|
64
|
+
def each
|
65
|
+
@linked_list.each { |node| yield node }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the number of values in the cache
|
69
|
+
def length
|
70
|
+
@hash_map.length
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the newest / most recent node in the cache
|
74
|
+
def first
|
75
|
+
@linked_list.first
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the oldest / least recent node in the cache
|
79
|
+
def last
|
80
|
+
@linked_list.last
|
81
|
+
end
|
82
|
+
|
83
|
+
def include?(value)
|
84
|
+
get_node(value) ? true : false
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def _validate_params(params)
|
90
|
+
raise "Error in SetAssociativeCache#initialize - params input should be a hash" unless params.class == Hash
|
91
|
+
raise "Error in SetAssociativeCache#initialize - params keys should all be symbols" unless params.all? { |key, _| key.class == Symbol }
|
92
|
+
|
93
|
+
params.each do |key, val|
|
94
|
+
raise "Error in SetAssociativeCache#initialize - Unrecognized param '#{key}'" unless @@param_types.has_key?(key)
|
95
|
+
raise "Error in SetAssociativeCache#initialize - Invalid type for params[:#{key}], expected #{@@param_types[key]}, got #{val.class}" \
|
96
|
+
unless @@param_types[key] == val.class
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def _initialize_hash_map(params)
|
101
|
+
if params[:key_generator_prc]
|
102
|
+
raise "Error invalid type for :key_generator_prc, expected Proc, got '#{params[:key_generator_prc].class}'" unless params[:key_generator_prc].class == Proc
|
103
|
+
key_generator_prc = Proc.new { |node| params[:key_generator_prc].call(node.value).hash }
|
104
|
+
else HashMap.new
|
105
|
+
key_generator_prc = Proc.new { |node| node.value.hash }
|
106
|
+
end
|
107
|
+
|
108
|
+
HashMap.new(&key_generator_prc)
|
109
|
+
end
|
110
|
+
|
111
|
+
def _select_replacement_algo
|
112
|
+
if @cache_type == :LRU
|
113
|
+
return @@lru_replacement_proc
|
114
|
+
elsif @cache_type == :MRU
|
115
|
+
return @@mru_replacement_proc
|
116
|
+
else
|
117
|
+
raise "Unrecognized cache :type provided '#{@cache_type}' and no :replacement_algo was given"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def _initialize_capacity(params)
|
122
|
+
if params[:capacity]
|
123
|
+
raise "Error in SetAssociativeCache#initialize - :capacity must be at least 4" unless params[:capacity] >= 4
|
124
|
+
return params[:capacity]
|
125
|
+
end
|
126
|
+
|
127
|
+
Float::INFINITY
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative './set_associative_cache.rb'
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ttd_set_associative_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Travis Ludlum
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A set associative cache
|
14
|
+
email: nequalszero@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/hash_map.rb
|
20
|
+
- lib/linked_list.rb
|
21
|
+
- lib/linked_list_node.rb
|
22
|
+
- lib/set_associative_cache.rb
|
23
|
+
- lib/ttd_set_associative_cache.rb
|
24
|
+
homepage: http://rubygems.org/gems/ttd_set_associative_cache
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.6.8
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: set associative cache
|
48
|
+
test_files: []
|