persistent_tree 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
+ SHA256:
3
+ metadata.gz: 0f943246939cd4aac7047ee312fa7160b76295ae4577c2d3ad5e28a3684e2c03
4
+ data.tar.gz: dc98aca9c41d1c150489985b68fdc7faee841cbe6fb2742e00046a0f05567d03
5
+ SHA512:
6
+ metadata.gz: 1d119db599794dd0499bf19421327c312a4db271bf1dd71da24fb0e87fdd0528efaa7165e36317855323fae1ec37ad4b7d8dd3fe8e7838e7414145d3c07fd91b
7
+ data.tar.gz: c81de3f1c7ccedac2e25c69aea715710f35cafd03252bdad863c6e4e0ad4dde81fa69a618e0729733bbb5666a66c0aeb799f0b60ed89533a9e50a46bb4145241
@@ -0,0 +1,4 @@
1
+ require_relative 'persistent_tree/algorithms'
2
+ require_relative 'persistent_tree/map'
3
+ require_relative 'persistent_tree/node'
4
+ require_relative 'persistent_tree/tree'
@@ -0,0 +1,258 @@
1
+ require_relative 'tree'
2
+
3
+ module PersistentTree
4
+ class MapEnumeratorDecorator
5
+ def enumerator(subject)
6
+ @subject = subject
7
+ end
8
+
9
+ def respond_to_missing?(method_name, *args)
10
+ @subject.respond_to?(method_name) || super
11
+ end
12
+
13
+ def method_missing(method_name, *args, &block)
14
+ if @subject.respond_to? method_name
15
+ @subject.send method_name, *args, &block
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+
22
+ # Read only view of a Map at a given version
23
+ class MapView
24
+ include Enumerable
25
+
26
+ # Internal data structure used to store key-value pairs
27
+ Entry = Struct.new(:key, :value)
28
+ private_constant :Entry
29
+
30
+ # Allow Entry objects to be ordered by their key or by comparing their key to other values
31
+ class Entry
32
+ include Comparable
33
+
34
+ def <=>(other)
35
+ if other.is_a? Entry
36
+ key <=> other.key
37
+ else
38
+ key <=> other
39
+ end
40
+ end
41
+ end
42
+
43
+ def initialize(tree, version, default_value, default_proc)
44
+ @tree = tree
45
+ @version = version
46
+ @default_value = default_value
47
+ @default_proc = default_proc
48
+ end
49
+
50
+ def [](key)
51
+ found, value = fetch_without_default(key)
52
+ if found
53
+ value
54
+ elsif @default_proc
55
+ @default_proc.call(self, key)
56
+ else
57
+ @default_value
58
+ end
59
+ end
60
+
61
+ def each
62
+ if block_given?
63
+ @tree.each(@version) do |item|
64
+ yield item.key, item.value
65
+ end
66
+ self
67
+ else
68
+ to_enum
69
+ end
70
+ end
71
+
72
+ def each_key(&block)
73
+ if block_given?
74
+ keys.each(&block)
75
+ self
76
+ else
77
+ keys.each
78
+ end
79
+ end
80
+
81
+ def fetch(*args)
82
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 1..2)" \
83
+ unless args.length.between?(1, 2)
84
+
85
+ warn('block supersedes default value argument') if args.length == 2 && block_given?
86
+
87
+ key = args[0]
88
+ found, value = fetch_without_default(key)
89
+ if found
90
+ value
91
+ elsif block_given?
92
+ yield key
93
+ elsif args.length == 2
94
+ args[1]
95
+ else
96
+ raise KeyError, "key not found: #{key}"
97
+ end
98
+ end
99
+
100
+ def flatten(*args)
101
+ # avoid unnecessary work if possible
102
+ return flatten_key_value_pairs if args.empty?
103
+
104
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 0..1)" if args.length > 1
105
+
106
+ raise TypeError, "no implicit conversion of #{args[0].class.name} into Integer" \
107
+ unless args[0].respond_to?(:to_int)
108
+
109
+ level = args[0].to_int
110
+ return to_a if level.zero? # Rely on implicit array conversion behaviour
111
+
112
+ result = flatten_key_value_pairs # Otherwise, flatten recursively
113
+ if (level - 1).positive?
114
+ result.flatten!(level - 1)
115
+ elsif level.negative?
116
+ result.flatten!
117
+ end
118
+ result
119
+ end
120
+
121
+ def keys
122
+ @tree.map(@version, &:key)
123
+ end
124
+
125
+ def include?(key)
126
+ @tree.include?(@version, key)
127
+ end
128
+
129
+ def select
130
+ if block_given?
131
+ new_map = Map.new
132
+ @tree.each(@version) do |item|
133
+ new_map.store item.key, item.value if yield item.key, item.value
134
+ end
135
+ new_map
136
+ else
137
+ to_enum(:select)
138
+ end
139
+ end
140
+
141
+ def size
142
+ @tree.size(@version)
143
+ end
144
+
145
+ alias each_pair each
146
+ alias has_key? include?
147
+ alias key? include?
148
+ alias member? include?
149
+
150
+ private
151
+
152
+ def fetch_without_default(key)
153
+ [true, @tree.find(@version, key).value]
154
+ rescue KeyError
155
+ [false, nil]
156
+ end
157
+
158
+ def flatten_key_value_pairs
159
+ result = Array.new(size * 2)
160
+ index = 0
161
+ each do |key, value|
162
+ result[index] = key
163
+ result[index + 1] = value
164
+ index += 2
165
+ end
166
+ result
167
+ end
168
+ end
169
+
170
+ class Map < MapView
171
+ attr_reader :default_proc
172
+
173
+ def initialize(*args, &block)
174
+ default_proc = nil
175
+ default_value = nil
176
+ if block_given?
177
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
178
+
179
+ if block.is_a? Proc
180
+ default_proc = block
181
+ else
182
+ default_proc = Proc.new
183
+ end
184
+
185
+ raise TypeError, "default_proc takes two arguments (2 for #{default_proc.arity})" \
186
+ if default_proc.lambda? && default_proc.arity != 2
187
+
188
+ elsif args.length == 1
189
+ default_value = args[0]
190
+ elsif !args.empty?
191
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 0..1)"
192
+ end
193
+ super(PersistentTree::Tree.new, 0, default_value, default_proc)
194
+ end
195
+
196
+ def []=(key, value)
197
+ store(key, value)
198
+ end
199
+
200
+ def default(key = nil)
201
+ if @default_value
202
+ @default_value
203
+ elsif @default_proc && key
204
+ @default_proc.call(self, key)
205
+ end
206
+ end
207
+
208
+ def default=(value)
209
+ @default_proc = nil
210
+ @default_value = value
211
+ end
212
+
213
+ def default_proc=(proc)
214
+ raise TypeError, "wrong default_proc type #{proc.class.name} (expected Proc)" \
215
+ if proc.nil? || !proc.respond_to?(:to_proc)
216
+
217
+ default_proc = proc.to_proc
218
+ unless default_proc.is_a?(Proc)
219
+ raise TypeError,
220
+ "can't convert #{proc.class.name} to Proc (#{proc.class.name}#to_proc gives #{default_proc.class.name})"
221
+ end
222
+
223
+ raise TypeError, "default_proc takes two arguments (2 for #{default_proc.arity})" \
224
+ if default_proc.lambda? && default_proc.arity != 2
225
+
226
+ @default_value = nil
227
+ @default_proc = default_proc
228
+ end
229
+
230
+ def store(key, value)
231
+ if key.is_a?(String) && !key.frozen?
232
+ key_to_store = key.freeze
233
+ else
234
+ key_to_store = key
235
+ end
236
+ @tree.insert_or_replace(Entry.new(key_to_store, value)).value
237
+ @version = @tree.version
238
+ value
239
+ end
240
+
241
+ def version(*args)
242
+ if args.length == 1
243
+ requested_version = args[0]
244
+ raise TypeError, 'requested version should be an integer' \
245
+ unless requested_version.is_a? Integer
246
+
247
+ raise RangeError, "requested version (#{requested_version}) exceeds current version (#{@tree.version})" \
248
+ if requested_version > @tree.version
249
+
250
+ MapView.new(@tree, requested_version, @default_value, @default_proc)
251
+ elsif args.empty?
252
+ @tree.version
253
+ else
254
+ raise ArgumentError, "wrong number of arguments(#{args.length} for 0..1)"
255
+ end
256
+ end
257
+ end
258
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: persistent_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tristan Penman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Partially Persistent Tree, based on Okasaki's 'Purely Functional Data
70
+ Structures'
71
+ email: tristan@tristanpenman.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/persistent_tree.rb
77
+ - lib/persistent_tree/map.rb
78
+ homepage: http://rubygems.org/gems/persistent_tree
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.0.9
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Partially Persistent Tree for Ruby
101
+ test_files: []