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