ttd_set_associative_cache 0.0.1
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.
- 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: []
|