deep_enumerable 0.1.0
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/deep_enumerable.rb +530 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7b6da22194de4650dcbf0368c515d833de4de310
|
4
|
+
data.tar.gz: 68291b67e7d344d808c1ef3f02afe2f66d8867d2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ee73d1a3c19f0cc237c42a03a3b75b42f89aa2c5f7b8f9831ef568fb04bbe997a66c1585cfc3d9660ef5ef3aa02f76275287e722e949d1aaa8ed12420ce28977
|
7
|
+
data.tar.gz: 1469498836041175ea1a32542e84d64cba7c1a879ef8ea7081321dee6c2d3e1c9ee2b5b64fd09d09ec7c46adf724bb492781963d17ff3d00445b6ba073570fb6
|
@@ -0,0 +1,530 @@
|
|
1
|
+
##
|
2
|
+
# A set of general methods that can be applied to any conformant nested structure
|
3
|
+
module DeepEnumerable
|
4
|
+
##
|
5
|
+
# Subtracts the leaves of one DeepEnumerable from another.
|
6
|
+
#
|
7
|
+
# @return a result of the same structure as the primary DeepEnumerable.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# >> alice = {name: "alice", age: 26}
|
11
|
+
# >> bob = {name: "bob", age: 26}
|
12
|
+
# >> alice.deep_diff(bob)
|
13
|
+
# => {:name=>"alice"}
|
14
|
+
#
|
15
|
+
# >> bob = {friends: ["alice","carol"]}
|
16
|
+
# >> carol = {friends: ["alice","bob"]}
|
17
|
+
# >> bob.deep_diff(carol)
|
18
|
+
# => {:friends=>"carol"}
|
19
|
+
#
|
20
|
+
def deep_diff(other, &block)
|
21
|
+
shallow_keys.each_with_object(empty) do |key, res|
|
22
|
+
s_val = (self[key] rescue nil) #TODO don't rely on rescue
|
23
|
+
o_val = (other[key] rescue nil)
|
24
|
+
|
25
|
+
comparator = block || :==.to_proc
|
26
|
+
|
27
|
+
if s_val.respond_to?(:deep_diff) && o_val.respond_to?(:deep_diff)
|
28
|
+
diff = s_val.deep_diff(o_val, &block)
|
29
|
+
res[key] = diff if diff.any?
|
30
|
+
elsif !comparator.call(s_val, o_val)
|
31
|
+
res[key] = s_val
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Computes the compliment of the intersection of two DeepEnumerables.
|
38
|
+
#
|
39
|
+
# @return The common structure of both arguments, with tuples containing differing values in the leaf nodes.
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# >> alice = {:name=>"alice", :age=>26}
|
43
|
+
# >> bob = {:name=>"bob", :age=>26}
|
44
|
+
# >> alice.deep_diff(bob)
|
45
|
+
# => {:name=>["alice", "bob"]}
|
46
|
+
#
|
47
|
+
# >> bob = {:friends=>["alice","carol"]}
|
48
|
+
# >> carol = {:friends=>["alice","bob"]}
|
49
|
+
# >> bob.deep_diff_symmetric(carol)
|
50
|
+
# => {:friends=>{1=>["carol", "bob"]}}
|
51
|
+
#
|
52
|
+
def deep_diff_symmetric(other, &block)
|
53
|
+
(shallow_keys + other.shallow_keys).each_with_object({}) do |key, res|
|
54
|
+
s_val = (self[key] rescue nil) #TODO don't rely on rescue
|
55
|
+
o_val = (other[key] rescue nil)
|
56
|
+
|
57
|
+
comparator = block || :==.to_proc
|
58
|
+
|
59
|
+
if s_val.respond_to?(:deep_diff_symmetric) && o_val.respond_to?(:deep_diff_symmetric)
|
60
|
+
diff = s_val.deep_diff_symmetric(o_val, &block)
|
61
|
+
res[key] = diff if diff.any?
|
62
|
+
elsif !comparator.call(s_val, o_val)
|
63
|
+
res[key] = [s_val, o_val]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias_method :deep_outersect, :deep_diff_symmetric
|
68
|
+
|
69
|
+
##
|
70
|
+
# Deeply copy a DeepEnumerable
|
71
|
+
#
|
72
|
+
# @return the same data structure at a different memory address
|
73
|
+
def deep_dup
|
74
|
+
deep_select{true}
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Iterate elements of a DeepEnumerable
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# >> {event: {id: 1, title: 'bowling'}}.deep_each.to_a
|
82
|
+
# => [[{:event=>:id}, 1], [{:event=>:title}, "bowling"]]
|
83
|
+
#
|
84
|
+
# >> [:a, [:b, :c]].deep_each.to_a
|
85
|
+
# => [[0, :a], [{1=>0}, :b], [{1=>1}, :c]]
|
86
|
+
#
|
87
|
+
# >> {events: [{title: 'movie'}, {title: 'dinner'}]}.deep_each.to_a
|
88
|
+
# => [[{:events=>{0=>:title}}, "movie"], [{:events=>{1=>:title}}, "dinner"]]
|
89
|
+
#
|
90
|
+
# @return an iterator over each deep-key/value pair for every leaf
|
91
|
+
def deep_each(&block)
|
92
|
+
depth_first_map.each(&block)
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Concatenate all the results from the supplied code block together.
|
97
|
+
#
|
98
|
+
# @return an array with the results of running +block+ once for every leaf element in the original structure, all flattened together.
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# >> {a: {b: 1, c: {d: 2, e: 3}, f: 4}, g: 5}.deep_flat_map{|k,v| v*2}
|
102
|
+
# => [2, 4, 6, 8, 10]
|
103
|
+
#
|
104
|
+
# >> {a: {b: 1, c: {d: 2, e: 3}, f: 4}, g: 5}.deep_flat_map{|k,v| [v, v*2]}
|
105
|
+
# => [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]
|
106
|
+
def deep_flat_map(&block)
|
107
|
+
deep_each.flat_map(&block)
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Retrieve a nested element from a DeepEnumerable
|
112
|
+
#
|
113
|
+
# @example
|
114
|
+
#
|
115
|
+
# >> prefix_tree = {"a"=>{"a"=>"aardvark", "b"=>["abacus", "abadon"], "c"=>"actuary"}}
|
116
|
+
#
|
117
|
+
# >> prefix_tree.deep_get("a")
|
118
|
+
# => {"a"=>"aardvark", "b"=>["abacus", "abadon"], "c"=>"actuary"}
|
119
|
+
#
|
120
|
+
# >> prefix_tree.deep_get("a"=>"b")
|
121
|
+
# => ["abacus", "abadon"]
|
122
|
+
#
|
123
|
+
# @return a DeepEnumerable representing the subtree specified by the query key
|
124
|
+
#
|
125
|
+
def deep_get(key)
|
126
|
+
if nested_key?(key)
|
127
|
+
key_head, key_tail = split_key(key)
|
128
|
+
if self[key_head].respond_to?(:deep_get)
|
129
|
+
self[key_head].deep_get(key_tail)
|
130
|
+
else
|
131
|
+
nil #SHOULD? raise an error
|
132
|
+
end
|
133
|
+
else
|
134
|
+
self[key]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Fold over all leaf nodes
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
# >> friends = [{name: 'alice', age: 26}, {name: 'bob', age: 26}]
|
143
|
+
# >> friends.deep_inject(Hash.new{[]}) {|sum, (k, v)| sum[k.values.first] <<= v; sum}
|
144
|
+
# => {:name=>["alice", "bob"], :age=>[26, 26]}
|
145
|
+
#
|
146
|
+
# @return The accumulation of the results of executing the provided block over every element in the DeepEnumerable
|
147
|
+
def deep_inject(initial, &block)
|
148
|
+
deep_each.inject(initial, &block)
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Describes the similarities between two DeepEnumerables.
|
153
|
+
#
|
154
|
+
# @example
|
155
|
+
# >> alice = {:name=>"alice", :age=>26}
|
156
|
+
# >> bob = {:name=>"bob", :age=>26}
|
157
|
+
# >> alice.deep_intersect(bob)
|
158
|
+
# => {:age=>26}
|
159
|
+
#
|
160
|
+
# >> bob = {:friends=>["alice","carol"]}
|
161
|
+
# >> carol = {:friends=>["alice","bob"]}
|
162
|
+
# >> bob.deep_intersect(carol)
|
163
|
+
# => {:friends=>["alice"]}
|
164
|
+
#
|
165
|
+
# @return a result of the same structure as the primary DeepEnumerable.
|
166
|
+
#
|
167
|
+
def deep_intersect(other, &block)
|
168
|
+
(shallow_keys + other.shallow_keys).each_with_object(empty) do |key, res|
|
169
|
+
s_val = (self[key] rescue nil) #TODO don't rely on rescue
|
170
|
+
o_val = (other[key] rescue nil)
|
171
|
+
|
172
|
+
comparator = block || :==.to_proc
|
173
|
+
|
174
|
+
if s_val.respond_to?(:deep_intersect) && o_val.respond_to?(:deep_intersect)
|
175
|
+
int = s_val.deep_intersect(o_val, &block)
|
176
|
+
res[key] = int if int.any?
|
177
|
+
elsif comparator.call(s_val, o_val)
|
178
|
+
res[key] = s_val
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# Returns the result of running block on each leaf of this DeepEnumerable
|
185
|
+
#
|
186
|
+
# @example
|
187
|
+
# >> h = {a: [1, 2]}
|
188
|
+
# >> h.deep_map!{|k, v| [k, v]}
|
189
|
+
# >> h
|
190
|
+
# => {:a=>[[{:a=>0}, 1], [{:a=>1}, 2]]}
|
191
|
+
#
|
192
|
+
# @return The original structure updated by the result of the block
|
193
|
+
def deep_map!(&block)
|
194
|
+
if block_given?
|
195
|
+
deep_each{|k,v| deep_set(k, block.call([k, v]))}
|
196
|
+
self
|
197
|
+
else
|
198
|
+
deep_each
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Create a new nested structure populated by the result of executing +block+ on the deep-keys and values of the original DeepEnumerable
|
204
|
+
#
|
205
|
+
# @example
|
206
|
+
# >> {a: [1, 2]}.deep_map{|k, v| [k, v]}
|
207
|
+
# => {:a=>[[{:a=>0}, 1], [{:a=>1}, 2]]}
|
208
|
+
#
|
209
|
+
# @return A copy of the input, updated by the result of the block
|
210
|
+
def deep_map(&block)
|
211
|
+
deep_dup.deep_map!(&block)
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Modifies this collection to use the result of +block+ as the values
|
216
|
+
#
|
217
|
+
# @example
|
218
|
+
# >> h = {a: [1, 2]}
|
219
|
+
# >> h.deep_map_values!{v| v*2}
|
220
|
+
# >> h
|
221
|
+
# => {:a=>[2, 4]}
|
222
|
+
#
|
223
|
+
# @return The original structure updated by the result of the block
|
224
|
+
def deep_map_values!(&block)
|
225
|
+
deep_map!{|_, v| block.call(v)}
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Creates a new nested structure populated by the result of executing +block+ on the values of the original DeepEnumerable
|
230
|
+
#
|
231
|
+
# @example
|
232
|
+
# >> {a: [1, 2].deep_map_values{v| v*2}
|
233
|
+
# => {:a=>[2, 4]}
|
234
|
+
#
|
235
|
+
# @return A copy of the input, updated by the result of the block
|
236
|
+
def deep_map_values(&block)
|
237
|
+
deep_dup.deep_map_values!(&block)
|
238
|
+
end
|
239
|
+
|
240
|
+
##
|
241
|
+
# Filter leaf nodes by the result of the given block
|
242
|
+
#
|
243
|
+
# @example
|
244
|
+
# >> inventory = {fruit: {apples: 4, oranges: 7}}
|
245
|
+
#
|
246
|
+
# >> inventory.deep_reject{|k, v| v > 5}
|
247
|
+
# => {:fruit=>{:apples=>4}}
|
248
|
+
#
|
249
|
+
# >> inventory.deep_reject(&:even?)
|
250
|
+
# => {:fruit=>{:oranges=>7}}
|
251
|
+
#
|
252
|
+
# @return a copy of the input, filtered by the given predicate
|
253
|
+
#
|
254
|
+
def deep_reject(&block)
|
255
|
+
new_block =
|
256
|
+
case block.arity
|
257
|
+
when 2 then ->(k,v){!block.call(k, v)}
|
258
|
+
else ->(v){ !block.call(v)}
|
259
|
+
end
|
260
|
+
deep_select(&new_block)
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# Filter leaf nodes by the result of the given block
|
265
|
+
#
|
266
|
+
# @example
|
267
|
+
# >> inventory = {fruit: {apples: 4, oranges: 7}}
|
268
|
+
#
|
269
|
+
# >> inventory.deep_select{|k, v| v > 5}
|
270
|
+
# => {:fruit=>{:oranges=>7}}
|
271
|
+
#
|
272
|
+
# >> inventory.deep_select(&:even?)
|
273
|
+
# => {:fruit=>{:apples=>4}}
|
274
|
+
#
|
275
|
+
# @return a copy of the input, filtered by the given predicate
|
276
|
+
def deep_select(&block)
|
277
|
+
copy = self.select{false} # get an empty version of this shallow collection
|
278
|
+
|
279
|
+
# insert/push a selected item into the copied enumerable
|
280
|
+
accept = lambda do |k, v|
|
281
|
+
# Don't insert elements at arbitrary positions in an array if appending is an option
|
282
|
+
if copy.respond_to?('push') # jruby has a Hash#<< method
|
283
|
+
copy.push(v)
|
284
|
+
else
|
285
|
+
copy[k] = v
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
shallow_each do |k, v|
|
290
|
+
if v.respond_to?(:deep_select)
|
291
|
+
selected = v.deep_select(&block)
|
292
|
+
accept.call(k, selected)
|
293
|
+
else
|
294
|
+
res =
|
295
|
+
case block.arity
|
296
|
+
when 2 then block.call(k, v)
|
297
|
+
else block.call(v)
|
298
|
+
end
|
299
|
+
|
300
|
+
if res
|
301
|
+
accept.call(k, (v.dup rescue v)) # FixNum's and Symbol's can't/shouldn't be dup'd
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
copy
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Update a DeepEnumerable, indexed by a deep-key
|
311
|
+
# Intermediate values are created when necessary, with the same type as its parent.
|
312
|
+
#
|
313
|
+
# @example
|
314
|
+
# >> [].deep_set({1 => 2}, 3)
|
315
|
+
# => [nil, [nil, nil, 3]]
|
316
|
+
# >> {}.deep_set({1 => 2}, 3)
|
317
|
+
# => {1=>{2=>3}}
|
318
|
+
#
|
319
|
+
# @return (tentative) returns the object that's been modified. Warning: This behavior is subject to change.
|
320
|
+
#
|
321
|
+
def deep_set(key, val)
|
322
|
+
if nested_key?(key)
|
323
|
+
key_head, key_tail = split_key(key)
|
324
|
+
if self[key_head].respond_to?(:deep_set)
|
325
|
+
self[key_head].deep_set(key_tail, val)
|
326
|
+
self
|
327
|
+
else
|
328
|
+
self[key_head] = empty.deep_set(key_tail, val)
|
329
|
+
self
|
330
|
+
end
|
331
|
+
else
|
332
|
+
self[key] = val
|
333
|
+
self #SHOULD? return val instead of self
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
##
|
338
|
+
# List the values stored at every leaf
|
339
|
+
#
|
340
|
+
# @example
|
341
|
+
# >> prefix_tree = {"a"=>{"a"=>"aardvark", "b"=>["abacus", "abadon"], "c"=>"actuary"}}
|
342
|
+
# >> prefix_tree.deep_values
|
343
|
+
# => ["aardvark", "abacus", "abadon", "actuary"]
|
344
|
+
#
|
345
|
+
# @return a list of every leaf value
|
346
|
+
def deep_values
|
347
|
+
deep_flat_map{|_, v| v}
|
348
|
+
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# Combine two DeepEnumerables into one, with the elements from each joined into tuples
|
352
|
+
#
|
353
|
+
# @example
|
354
|
+
# >> inventory = {fruit: {apples: 4, oranges: 7}}
|
355
|
+
# >> prices = {fruit: {apples: 0.79, oranges: 1.21}}
|
356
|
+
# >> inventory.deep_zip(prices)
|
357
|
+
# => {:fruit=>{:apples=>[4, 0.79], :oranges=>[7, 1.21]}}
|
358
|
+
#
|
359
|
+
# @return one data structure with elements from both arguments joined together
|
360
|
+
#
|
361
|
+
def deep_zip(other)
|
362
|
+
(shallow_keys).inject(empty) do |res, key|
|
363
|
+
s_val = self[key]
|
364
|
+
o_val = (other[key] rescue nil) #TODO don't rely on rescue
|
365
|
+
|
366
|
+
comparator = :==.to_proc
|
367
|
+
|
368
|
+
if s_val.respond_to?(:deep_zip) && o_val.respond_to?(:deep_zip)
|
369
|
+
diff = s_val.deep_zip(o_val)
|
370
|
+
diff.empty? ? res : res.deep_set(key, diff)
|
371
|
+
else
|
372
|
+
res.deep_set(key, [s_val, o_val])
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
##
|
378
|
+
# A copy of the DeepEnumerable containing no elements
|
379
|
+
#
|
380
|
+
# @example
|
381
|
+
# >> inventory = {fruit: {apples: 4, oranges: 7}}
|
382
|
+
# >> inventory.empty
|
383
|
+
# => {}
|
384
|
+
#
|
385
|
+
# @return a new object of the same type as the original collection, only empty
|
386
|
+
#
|
387
|
+
def empty
|
388
|
+
select{false}
|
389
|
+
end
|
390
|
+
|
391
|
+
# Provide a homogenous |k,v| iterator for Arrays/Hashes/DeepEnumerables
|
392
|
+
#TODO test this
|
393
|
+
def shallow_key_value_pairs
|
394
|
+
shallow_keys.map{|k| [k, self[k]]}
|
395
|
+
end
|
396
|
+
|
397
|
+
##
|
398
|
+
# Replaces every top-level element with the result of the given block
|
399
|
+
def shallow_map_keys!(&block)
|
400
|
+
new_kvs = shallow_key_value_pairs.map do |k, v|
|
401
|
+
new_key =
|
402
|
+
if block.arity == 2
|
403
|
+
block.call(k, v)
|
404
|
+
else
|
405
|
+
block.call(k)
|
406
|
+
end
|
407
|
+
|
408
|
+
self.delete(k) #TODO This is not defined on Enumerable!
|
409
|
+
[new_key, v]
|
410
|
+
end
|
411
|
+
|
412
|
+
new_kvs.each do |k, v|
|
413
|
+
self[k] = v
|
414
|
+
end
|
415
|
+
|
416
|
+
self
|
417
|
+
end
|
418
|
+
|
419
|
+
##
|
420
|
+
# Returns a new collection where every top-level element is replaced with the result of the given block
|
421
|
+
def shallow_map_keys(&block)
|
422
|
+
deep_dup.shallow_map_keys!(&block)
|
423
|
+
end
|
424
|
+
|
425
|
+
##
|
426
|
+
# Replaces every top-level element with the result of the given block
|
427
|
+
def shallow_map_values!(&block)
|
428
|
+
shallow_key_value_pairs.each do |k, v|
|
429
|
+
self[k] =
|
430
|
+
if block.arity == 2
|
431
|
+
block.call(k, v)
|
432
|
+
else
|
433
|
+
block.call(v)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
self
|
438
|
+
end
|
439
|
+
|
440
|
+
##
|
441
|
+
# Returns a new collection where every top-level element is replaced with the result of the given block
|
442
|
+
def shallow_map_values(&block)
|
443
|
+
deep_dup.shallow_map_values!(&block)
|
444
|
+
end
|
445
|
+
|
446
|
+
##
|
447
|
+
# The primary iterator of a DeepEnumerable
|
448
|
+
# If this method is implemented DeepEnumerable can construct every other method in terms of shallow_each.
|
449
|
+
#TODO test this
|
450
|
+
def shallow_each(&block)
|
451
|
+
shallow_key_value_pairs.each(&block)
|
452
|
+
end
|
453
|
+
|
454
|
+
# This method is commented out because redefining '.to_a' on Array, for example,
|
455
|
+
# seems like a terrible idea
|
456
|
+
#def to_a
|
457
|
+
# deep_each.to_a
|
458
|
+
#end
|
459
|
+
|
460
|
+
protected
|
461
|
+
|
462
|
+
#def shallow_get(x) # this should technically be defined in Hash/Array individually
|
463
|
+
# self[x]
|
464
|
+
#end
|
465
|
+
|
466
|
+
def depth_first_map(ancestry=[])
|
467
|
+
shallow_each.flat_map do |key, val|
|
468
|
+
full_ancestry = ancestry + [key]
|
469
|
+
full_key = deep_key_from_array(full_ancestry) #TODO this is an n^2 operation
|
470
|
+
|
471
|
+
if val.respond_to?(:depth_first_map, true) # Search protected methods as well
|
472
|
+
val.depth_first_map(full_ancestry)
|
473
|
+
else
|
474
|
+
[[full_key, val]]
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Everything below should be a class method, but Ruby method visibility is a nightmare
|
480
|
+
def deep_key_from_array(array)
|
481
|
+
if array.size > 1
|
482
|
+
{array.first => deep_key_from_array(array.drop(1))}
|
483
|
+
else
|
484
|
+
array.first
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
def nested_key?(key)
|
489
|
+
key.is_a?(Hash)
|
490
|
+
end
|
491
|
+
|
492
|
+
# Disassembles a key into its head and tail elements
|
493
|
+
#
|
494
|
+
# for example: {a: {0 => :a}} goes to [:a, {0 => :a}]
|
495
|
+
def split_key(key)
|
496
|
+
case key
|
497
|
+
when Hash then
|
498
|
+
key_head = key.keys.first
|
499
|
+
key_tail = key[key_head]
|
500
|
+
[key_head, key_tail]
|
501
|
+
when nil then [nil, nil]
|
502
|
+
else [key, nil]
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# Get the lowest-level key
|
507
|
+
#
|
508
|
+
# for example: {a: {b: :c}} goes to :c
|
509
|
+
def self.leaf_key(key)
|
510
|
+
key.is_a?(Hash) ? leaf_key(key) : key
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
##
|
515
|
+
# This class implements the necessary methods to qualify Hash as a DeepEnumerable
|
516
|
+
class Hash
|
517
|
+
include DeepEnumerable
|
518
|
+
|
519
|
+
alias_method :shallow_keys, :keys
|
520
|
+
end
|
521
|
+
|
522
|
+
##
|
523
|
+
# This class implements the necessary methods to qualify Array as a DeepEnumerable
|
524
|
+
class Array
|
525
|
+
include DeepEnumerable
|
526
|
+
|
527
|
+
def shallow_keys
|
528
|
+
(0...size).to_a
|
529
|
+
end
|
530
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deep_enumerable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Gopstein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A port of Enumerable to deeply nested enumerables
|
14
|
+
email: dan@gopstein.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/deep_enumerable.rb
|
20
|
+
homepage: https://github.com/dgopstein/deep_enumerable
|
21
|
+
licenses:
|
22
|
+
- MIT
|
23
|
+
metadata: {}
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
requirements: []
|
39
|
+
rubyforge_project:
|
40
|
+
rubygems_version: 2.4.5
|
41
|
+
signing_key:
|
42
|
+
specification_version: 4
|
43
|
+
summary: A library for manipulating nested collections
|
44
|
+
test_files: []
|
45
|
+
has_rdoc:
|