deep_enumerable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|