sarah 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.
@@ -0,0 +1,388 @@
1
+ # Sarah - Combination sequential array/random-access hash
2
+ #
3
+ # Sequential values beginning at key (index) 0 are stored in an array.
4
+ # Values with sparse or non-numeric keys are stored in a hash. Values
5
+ # may migrate between the two if holes in the sequential key sequence
6
+ # are created or removed.
7
+ #
8
+ # @author Brian Katzung <briank@kappacs.com>, Kappa Computer Solutions, LLC
9
+ # @copyright 2013 Brian Katzung and Kappa Computer Solutions, LLC
10
+ # @license MIT License
11
+
12
+ class Sarah
13
+
14
+ # @!attribute default
15
+ # The default value returned for non-existent keys.
16
+ attr_accessor :default
17
+
18
+ # @!attribute default_proc
19
+ # @return [Proc]
20
+ # The default proc to call for non-existent keys. This takes precedence
21
+ # over the default value. It is passed the Sarah and the referenced key
22
+ # (or nil) as parameters.
23
+ attr_accessor :default_proc
24
+
25
+ # Initialize a new instance.
26
+ #
27
+ # If passed a block, the block is called to provide default values
28
+ # instead of using the :default option value. The block is passed the
29
+ # hash and the requested key (or nil for a shift or pop on an empty
30
+ # sequential array).
31
+ #
32
+ # @param opts [Array] Setup options.
33
+ # @option opts :default The default value to return for a non-existent key.
34
+ # @option opts :default_proc The default proc to call for a non-existent
35
+ # key.
36
+ # @option opts :array An array (or hash!) to use for initialization (first).
37
+ # @option opts :hash A hash (or array!) to use for initialization (second).
38
+ # @option opts :from An array or hash to use for initialization (third).
39
+ def initialize (opts = {}, &block)
40
+ clear
41
+ @default = opts[:default]
42
+ @default_proc = block || opts[:default_proc]
43
+ merge! opts[:array], opts[:hash], opts[:from]
44
+ end
45
+
46
+ # Clear all sequential array and random-access hash values.
47
+ #
48
+ # @return [Sarah]
49
+ def clear
50
+ @seq = []
51
+ @rnd = {}
52
+ self
53
+ end
54
+
55
+ # Test key existence.
56
+ #
57
+ # @param key The key to check for existence.
58
+ # @return [Boolean]
59
+ def has_key? (key)
60
+ @rnd.has_key?(key) or (key.is_a? Integer and
61
+ key >= -@seq.size and key < @seq.size)
62
+ end
63
+
64
+ # Get a value by sequential or random-access key. If the key does not
65
+ # exist, the default value or initial block value is returned. If
66
+ # called with a range or an additional length parameter, a slice is
67
+ # returned instead.
68
+ #
69
+ # @param key The key for the value to be returned, or a range.
70
+ # @param len [Integer] An optional slice length.
71
+ # @return [Object, Array]
72
+ def [] (key, len = nil)
73
+ if len
74
+ slice(key, len)
75
+ elsif key.is_a? Range
76
+ slice(key)
77
+ elsif @rnd.has_key? key
78
+ @rnd[key]
79
+ elsif key.is_a? Integer and key >= -@seq.size and key < @seq.size
80
+ @seq[key]
81
+ else
82
+ @default_proc ? @default_proc.call(self, key) : @default
83
+ end
84
+ end
85
+
86
+ # Get a value by sequential or random-access key. If the key does not
87
+ # exist, the local default or block value is returned. If no local or
88
+ # block value is supplied, a KeyError exception is raised instead.
89
+ #
90
+ # If a local block is supplied, it is passed the key as a parameter.
91
+ #
92
+ # @param key The key for the value to be returned.
93
+ # @param default The value to return if the key does not exist.
94
+ def fetch (key, *default)
95
+ if @rnd.has_key? key
96
+ @rnd[key]
97
+ elsif key.is_a? Integer and key >= -@seq.size and key < @seq.size
98
+ @seq[key]
99
+ elsif default.size > 0
100
+ default[0]
101
+ elsif block_given?
102
+ yield key
103
+ else
104
+ raise KeyError.new("key not found: #{key}")
105
+ end
106
+ end
107
+
108
+ # Shift and return the first sequential array value.
109
+ #
110
+ # @return Object
111
+ def shift
112
+ if @seq.size > 0
113
+ @seq.shift
114
+ elsif @default_proc
115
+ @default_proc.call self, nil
116
+ else
117
+ @default
118
+ end
119
+ end
120
+
121
+ # Pop and return the last sequential array value
122
+ def pop
123
+ if @seq.size > 0
124
+ @seq.pop
125
+ elsif @default_proc
126
+ @default_proc.call self, nil
127
+ else
128
+ @default
129
+ end
130
+ end
131
+
132
+ # Iterate a block (required) over each key and value.
133
+ #
134
+ # @return [Sarah]
135
+ def each
136
+ @seq.each_index { |i| yield(i, @seq[i]) }
137
+ @rnd.each { |kv| yield(*kv) }
138
+ self
139
+ end
140
+
141
+ alias_method :each_pair, :each
142
+
143
+ # Iterate a block (required) over each key.
144
+ #
145
+ # @return [Sarah]
146
+ def each_index
147
+ @seq.each_index { |i| yield(i) }
148
+ @rnd.keys.each { |k| yield(k) }
149
+ self
150
+ end
151
+
152
+ # Set a value by sequential or random-access key.
153
+ #
154
+ # @param key The key for which the value should be set.
155
+ # @param value The value to set for the key.
156
+ # @return Returns the value.
157
+ def []= (key, value)
158
+ if key.is_a? Integer and key.abs <= @seq.size
159
+ key += @seq.size if key < 0
160
+ @seq[key] = value
161
+ @rnd.delete key
162
+ else
163
+ @rnd[key] = value
164
+ end
165
+
166
+ # Move adjacent random-access keys to the sequential array
167
+ key = @seq.size
168
+ while @rnd.has_key? key
169
+ @seq[key] = @rnd.delete key
170
+ key += 1
171
+ end
172
+
173
+ value
174
+ end
175
+
176
+ # Set values and/or key/value pairs.
177
+ #
178
+ # <tt>set([val1, ..., valN,] [key1 => kval1, ..., keyN => kvalN])</tt>
179
+ #
180
+ # @param list [Array] A list of sequential values or random-access
181
+ # key/value pairs to set.
182
+ # @return [Sarah]
183
+ def set (*list)
184
+ hash = (list.size > 0 and list[-1].is_a? Hash) ? list.pop : nil
185
+ merge! list
186
+ merge! hash if hash
187
+ self
188
+ end
189
+
190
+ # Set key/value pairs.
191
+ #
192
+ # <tt>set_kv(key1, val1, ..., keyN, valN)</tt>
193
+ #
194
+ # @param kvlist [Array] The list of key/value pairs to set.
195
+ # @return [Sarah]
196
+ def set_pairs (*kvlist)
197
+ kvlist.each_slice(2) { |kv| self.[]=(*kv) }
198
+ self
199
+ end
200
+
201
+ alias_method :set_kv, :set_pairs
202
+
203
+ # Append arrays/Sarahs (or merge hashes) of values.
204
+ #
205
+ # @param ahlist [Array<Array, Hash, Sarah>] The structures to append.
206
+ # @return [Sarah]
207
+ def append! (*ahlist)
208
+ ahlist.each do |ah|
209
+ if ah.respond_to? :seq_values and ah.respond_to? :rnd
210
+ push *ah.seq_values
211
+ merge! ah.rnd
212
+ elsif ah.respond_to? :each_pair
213
+ merge! ah
214
+ elsif ah.respond_to? :each_index
215
+ push *ah
216
+ end
217
+ end
218
+ self
219
+ end
220
+
221
+ # Insert arrays/Sarahs (or merge hashes) of values.
222
+ #
223
+ # @param ahlist [Array<Array, Hash, Sarah>] The structures to insert.
224
+ # @return [Sarah]
225
+ def insert! (*ahlist)
226
+ ahlist.reverse_each do |ah|
227
+ if ah.respond_to? :seq_values and ah.respond_to? :rnd
228
+ unshift @ah.seq_values
229
+ merge! ah.rnd
230
+ elsif ah.respond_to? :each_pair
231
+ merge! ah
232
+ elsif ah.respond_to? :each_index
233
+ unshift *ah
234
+ end
235
+ end
236
+ self
237
+ end
238
+
239
+ # Load/merge from a hash and/or array/Sarah (beginning at key 0).
240
+ #
241
+ # @param ahlist [Array<Array, Hash, Sarah>] The structures to load/merge.
242
+ # @return [Sarah]
243
+ def merge! (*ahlist)
244
+ ahlist.each do |ah|
245
+ if ah.respond_to? :each_pair
246
+ ah.each_pair { |kv| self.[]=(*kv) }
247
+ elsif ah.respond_to? :each_index
248
+ ah.each_index { |i| self[i] = ah[i] }
249
+ end
250
+ end
251
+ self
252
+ end
253
+
254
+ alias_method :update, :merge!
255
+
256
+ # Unshift (insert) sequential values beginning at key 0.
257
+ #
258
+ # @param vlist [Array] A list of values to unshift (insert).
259
+ # @return [Sarah]
260
+ def unshift (*vlist)
261
+ (@seq.size...@seq.size+vlist.size).each { |k| @rnd.delete k }
262
+ @seq.unshift *vlist
263
+ self
264
+ end
265
+
266
+ # Push (append) sequential values.
267
+ #
268
+ # @param vlist [Array] A list of values to push (append).
269
+ # @return [Sarah]
270
+ def push (*vlist)
271
+ (@seq.size...@seq.size+vlist.size).each { |k| @rnd.delete k }
272
+ @seq.push *vlist
273
+ self
274
+ end
275
+
276
+ # Delete by sequential or random-access key, returning any existing value
277
+ # (or the default or block value, otherwise).
278
+ #
279
+ # @param key The key to be deleted.
280
+ # @return The value of the deleted key.
281
+ def delete_key (key)
282
+ return @rnd.delete key if @rnd.has_key? key
283
+ if key.is_a? Integer
284
+ key += @seq.size if key < 0
285
+ if key >= 0 and key < @seq.size
286
+ result = @seq[key]
287
+
288
+ # Move any following keys to the random-access hash
289
+ (key+1...@seq.size).each { |i| @rnd[i] = @seq[i] }
290
+
291
+ # Truncate the sequential array
292
+ @seq = @seq[0...key]
293
+
294
+ return result
295
+ end
296
+ end
297
+ @default_proc ? @default_proc.call(self, nil) : @default
298
+ end
299
+
300
+ # Return the sequential array size.
301
+ #
302
+ # @return [Integer]
303
+ def seq_size; @seq.size; end
304
+
305
+ # Return the random-access hash size.
306
+ #
307
+ # @return [Integer]
308
+ def rnd_size; @rnd.size; end
309
+
310
+ # Return the total size.
311
+ #
312
+ # @return [Integer]
313
+ def size; @seq.size + @rnd.size; end
314
+
315
+ alias_method :seq_length, :seq_size
316
+ alias_method :rnd_length, :rnd_size
317
+ alias_method :length, :size
318
+
319
+ # Return the sequential-access array.
320
+ #
321
+ # @return [Array]
322
+ def seq; @seq; end
323
+
324
+ # Return the random-access hash.
325
+ #
326
+ # @return [Hash]
327
+ def rnd; @rnd; end
328
+
329
+ # Return the sequential array keys (indexes).
330
+ #
331
+ # @return [Array<Integer>]
332
+ def seq_keys; 0...@seq.size; end
333
+
334
+ # Return the random-access hash keys.
335
+ #
336
+ # @return [Array]
337
+ def rnd_keys; @rnd.keys; end
338
+
339
+ # Return all the keys.
340
+ #
341
+ # @return [Array]
342
+ def keys; seq_keys.to_a + rnd_keys; end
343
+
344
+ # Return the hash values.
345
+ def rnd_values; @rnd.values; end
346
+
347
+ # Return all the values.
348
+ def values; @seq + @rnd.values; end
349
+
350
+ alias_method :seq_values, :seq
351
+
352
+ # Slice all.
353
+ #
354
+ # @return [Sarah, Object]
355
+ def slice (*params)
356
+ res = @seq.slice *params
357
+ (res.is_a? Array) ? (self.class.new :array => res, :hash => @rnd,
358
+ :default_proc => @default_proc, :default => @default) : res
359
+ end
360
+
361
+ # Slice all in place.
362
+ #
363
+ # @return [Sarah, Object]
364
+ def slice! (*params)
365
+ res = @seq.slice! *params
366
+ (res.is_a? Array) ? (self.class.new :array => res, :hash => @rnd,
367
+ :default_proc => @default_proc, :default => @default) : res
368
+ end
369
+
370
+ # Slice sequential.
371
+ #
372
+ # @return [Sarah, Object]
373
+ def seq_slice (*params)
374
+ res = @seq.slice *params
375
+ (res.is_a? Array) ? (self.class.new :array => res,
376
+ :default_proc => @default_proc, :default => @default) : res
377
+ end
378
+
379
+ # Slice sequential in place.
380
+ #
381
+ # @return [Sarah, Object]
382
+ def seq_slice! (*params)
383
+ res = @seq.slice! *params
384
+ (res.is_a? Array) ? (self.class.new :array => res,
385
+ :default_proc => @default_proc, :default => @default) : res
386
+ end
387
+
388
+ end
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "sarah"
3
+ s.version = "0.0.1"
4
+ s.date = "2013-07-08"
5
+ s.authors = ["Brian Katzung"]
6
+ s.email = ["briank@kappacs.com"]
7
+ s.homepage = "http://rubygems.org/gems/sarah"
8
+ s.summary = "Sequential array/random-access hash"
9
+ s.description = "Implements a hybrid data structure composed of a sequential array and random-access hash"
10
+ s.license = "MIT"
11
+
12
+ s.files = Dir.glob("lib/**/*") + %w{sarah.gemspec}
13
+ s.test_files = Dir.glob("test/**/*")
14
+ s.require_path = 'lib'
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'minitest/autorun'
2
+ require 'sarah'
3
+
4
+ class TestSarah < MiniTest::Unit::TestCase
5
+
6
+ def test_cmethod_new
7
+ assert_respond_to Sarah, :new
8
+ end
9
+
10
+ def test_new_1
11
+ assert Sarah.new, "Failed to create new Sarah"
12
+ end
13
+
14
+ end
15
+
16
+ # END
@@ -0,0 +1,31 @@
1
+ require 'minitest/autorun'
2
+ require 'sarah'
3
+
4
+ class TestSarah < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @s = Sarah.new
8
+ end
9
+
10
+ def test_imethods_accessors
11
+ [
12
+ :default, :default=, :default_proc, :default_proc=
13
+ ].each { |method| assert_respond_to @s, method }
14
+ end
15
+
16
+ def test_imethods_user_api
17
+ [
18
+ :clear, :has_key?, :[], :[]=, :fetch,
19
+ :shift, :pop, :each, :each_pair, :each_index,
20
+ :set, :set_pairs, :set_kv, :append!, :merge!,
21
+ :unshift, :push, :delete_key,
22
+ :size, :seq_size, :rnd_size, :length, :seq_length, :rnd_length,
23
+ :seq, :rnd, :keys, :seq_keys, :rnd_keys,
24
+ :values, :seq_values, :rnd_values,
25
+ :slice, :slice!, :seq_slice, :seq_slice!
26
+ ].each { |method| assert_respond_to @s, method }
27
+ end
28
+
29
+ end
30
+
31
+ # END
@@ -0,0 +1,39 @@
1
+ require 'minitest/autorun'
2
+ require 'sarah'
3
+
4
+ class TestSarah < MiniTest::Unit::TestCase
5
+
6
+ def test_new_array
7
+ s = Sarah.new(:array => [1, 2])
8
+ assert_equal([1, 2], s.seq, "new array seq [...]")
9
+ assert_equal({}, s.rnd, "new array rnd {}")
10
+ assert_equal(2, s.seq_size, "new array seq_size")
11
+ assert_equal(0, s.rnd_size, "new array rnd_size")
12
+ end
13
+
14
+ def test_new_hash
15
+ s = Sarah.new(:hash => { :one => 1, :two => 2 })
16
+ assert_equal([], s.seq, "new hash seq []")
17
+ assert_equal({ :one => 1, :two => 2 }, s.rnd, "new hash rnd {...}")
18
+ assert_equal(0, s.seq_size, "new hash seq_size")
19
+ assert_equal(2, s.rnd_size, "new hash rnd_size")
20
+ end
21
+
22
+ def test_new_mixed
23
+ s = Sarah.new(:array => [1, 2], :hash => { :one => 1, :two => 2 })
24
+ assert_equal([1, 2], s.seq, "new mixed seq [...]")
25
+ assert_equal({ :one => 1, :two => 2 }, s.rnd, "new mixed rnd {...}")
26
+ assert_equal(2, s.seq_size, "new mixed seq_size")
27
+ assert_equal(2, s.rnd_size, "new mixed rnd_size")
28
+ end
29
+
30
+ def test_new_consecutive
31
+ s = Sarah.new(:array => [0, 1], :hash => { 2 => :two, 3 => :three })
32
+ assert_equal([0, 1, :two, :three], s.seq, "new consecutive [...]")
33
+ assert_equal(4, s.seq_size, "new consecutive seq_size")
34
+ assert_equal(0, s.rnd_size, "new consecutive rnd_size")
35
+ end
36
+
37
+ end
38
+
39
+ # END
@@ -0,0 +1,59 @@
1
+ require 'minitest/autorun'
2
+ require 'sarah'
3
+
4
+ class TestSarah < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @s = Sarah.new(:array => [1, 2], :hash => { :a => 3, :b => 4 })
8
+ end
9
+
10
+ def test_clear
11
+ assert_equal(4, @s.size, "has size 4 before clear")
12
+ @s.clear
13
+ assert_equal(0, @s.size, "has size 0 after clear")
14
+ end
15
+
16
+ def test_has_key
17
+ assert_equal(true, @s.has_key?(0), "has key 0")
18
+ assert_equal(true, @s.has_key?(-1), "has key -1")
19
+ assert_equal(true, @s.has_key?(:a), "has key :a")
20
+ assert_equal(false, @s.has_key?(2), "has no key 2")
21
+ assert_equal(false, @s.has_key?(:c), "has no key :c")
22
+ end
23
+
24
+ def test_get_set
25
+ assert_equal(1, @s[0], "get key 0 value 1")
26
+ assert_equal(2, @s[1], "get key 1 value 2")
27
+ assert_equal(3, @s[:a], "get key :a value 3")
28
+ assert_equal(4, @s[:b], "get key :b value 4")
29
+
30
+ @s[2] = 5
31
+ @s[:c] = 6
32
+ assert_equal(5, @s[2], "set/get key 2 value 5")
33
+ assert_equal(6, @s[:c], "set/get key :c value 6")
34
+ end
35
+
36
+ def test_default
37
+ assert_equal(1, @s[0], "confirm key 0 value 1")
38
+ assert_equal(nil, @s[2], "default default nil")
39
+
40
+ s = Sarah.new(:array => [0], :default => false)
41
+ assert_equal(0, s[0], "confirm key 0 value 0")
42
+ assert_equal(false, s[1], "explicit default false")
43
+
44
+ s = Sarah.new(:array => [0]) { |s, key| key }
45
+ assert_equal(0, s[0], "confirm key 0 value 0")
46
+ assert_equal(1, s[1], "block default 1")
47
+ assert_equal(2, s[2], "block default 2")
48
+ end
49
+
50
+ def test_fetch
51
+ assert_equal(1, @s.fetch(0), "fetch key 0 value 0")
52
+ assert_raises(KeyError, "fetch with exception") { @s.fetch 2 }
53
+ assert_equal(false, @s.fetch(2, false), "fetch with default")
54
+ assert_equal(2, @s.fetch(2) { |key| key }, "fetch with block")
55
+ end
56
+
57
+ end
58
+
59
+ # END
@@ -0,0 +1,34 @@
1
+ require 'minitest/autorun'
2
+ require 'sarah'
3
+
4
+ class TestSarah < MiniTest::Unit::TestCase
5
+
6
+ def test_stack
7
+ s = Sarah.new
8
+ s.push 1, 2, 3
9
+ s.unshift 4, 5, 6
10
+ assert_equal([4, 5, 6, 1, 2, 3], s.seq, "push + unshift")
11
+ assert_equal(4, s.shift, "shift")
12
+ assert_equal(3, s.pop, "pop")
13
+ assert_equal([5, 6, 1, 2], s.seq, "after shift, pop")
14
+ end
15
+
16
+ def test_append
17
+ s = Sarah.new
18
+ s.append! [1], { :one => 1 }, [2], [3]
19
+ s.append! [4], { :two => 2 }, [5], [6]
20
+ assert_equal([1, 2, 3, 4, 5, 6], s.seq, "append ary")
21
+ assert_equal({ :one => 1, :two => 2 }, s.rnd, "append hsh")
22
+ end
23
+
24
+ def test_insert
25
+ s = Sarah.new
26
+ s.insert! [1], { :one => 1 }, [2], [3]
27
+ s.insert! [4], { :two => 2 }, [5], [6]
28
+ assert_equal([4, 5, 6, 1, 2, 3], s.seq, "insert ary")
29
+ assert_equal({ :one => 1, :two => 2 }, s.rnd, "insert hsh")
30
+ end
31
+
32
+ end
33
+
34
+ # END
@@ -0,0 +1,8 @@
1
+ #!/bin/sh
2
+
3
+ export RUBYLIB=../lib
4
+
5
+ # Run the specified tests (or all of them)
6
+ for t in ${*:-[0-9]*.rb}
7
+ do ruby $t
8
+ done
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sarah
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Brian Katzung
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2013-07-08 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Implements a hybrid data structure composed of a sequential array and random-access hash
22
+ email:
23
+ - briank@kappacs.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/sarah.rb
32
+ - sarah.gemspec
33
+ - test/02new.rb
34
+ - test/00class.rb
35
+ - test/04stack.rb
36
+ - test/01instance.rb
37
+ - test/run_tests.sh
38
+ - test/03set_get.rb
39
+ has_rdoc: true
40
+ homepage: http://rubygems.org/gems/sarah
41
+ licenses:
42
+ - MIT
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.7
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Sequential array/random-access hash
71
+ test_files:
72
+ - test/02new.rb
73
+ - test/00class.rb
74
+ - test/04stack.rb
75
+ - test/01instance.rb
76
+ - test/run_tests.sh
77
+ - test/03set_get.rb