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