hash_op 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +4 -0
- data/.rubocop.yml +12 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +2 -2
- data/README.md +273 -6
- data/lib/hash_op/{deep_access.rb → deep.rb} +35 -9
- data/lib/hash_op/filter.rb +12 -12
- data/lib/hash_op/grouping.rb +2 -2
- data/lib/hash_op/mapping.rb +27 -12
- data/lib/hash_op/math.rb +33 -50
- data/lib/hash_op/merge.rb +5 -5
- data/lib/hash_op/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4ee4ebeea9d4222628d2e8bc1a35ac27ff6b219
|
4
|
+
data.tar.gz: 9cc047ece2b3c87a1baf144f17f3a3434da43afc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb145f094b7bd06a0f77ad34b81e0b674d6bb74daa5358a1b60655dc171be67e1113ce39980aee0a99dd26aafaf289e8a04c293930ebebaf722a874868c8fd7f
|
7
|
+
data.tar.gz: 302dede0a9005b1284bb3d405e35937defebc45ad6744f635b7bcb8b15a351460ec43487031a46e6439ff795296fb8da87c3c4571691b0f690e78ce32ab93fec
|
data/.codeclimate.yml
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Metrics/LineLength:
|
2
|
+
Max: 120
|
3
|
+
Style/StringLiterals:
|
4
|
+
EnforcedStyle: single_quotes
|
5
|
+
Style/EmptyLinesAroundBlockBody:
|
6
|
+
Enabled: false
|
7
|
+
Style/EmptyLinesAroundClassBody:
|
8
|
+
Enabled: false
|
9
|
+
Style/EmptyLinesAroundClassBody:
|
10
|
+
Enabled: false
|
11
|
+
Style/AlignParameters:
|
12
|
+
EnforcedStyle: with_fixed_indentation
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
A Ruby library of functions to access and manipulate hash data structures.
|
5
5
|
|
6
|
-
[![Build Status](https://travis-ci.org/
|
6
|
+
[![Build Status](https://travis-ci.org/rchampourlier/hash_op.svg?branch=master)](https://travis-ci.org/rchampourlier/hash_op)
|
7
7
|
[![Code Climate](https://codeclimate.com/github/rchampourlier/hash_op/badges/gpa.svg)](https://codeclimate.com/github/rchampourlier/hash_op)
|
8
8
|
[![Coverage Status](https://coveralls.io/repos/rchampourlier/hash_op/badge.svg)](https://coveralls.io/r/rchampourlier/hash_op)
|
9
9
|
|
@@ -27,15 +27,13 @@ Or install it yourself as:
|
|
27
27
|
|
28
28
|
## Available operations
|
29
29
|
|
30
|
-
_See specs for more details on each operation._
|
31
|
-
|
32
30
|
### Deep Access
|
33
31
|
|
34
32
|
```ruby
|
35
|
-
HashOp::
|
33
|
+
HashOp::Deep.fetch({a: {b: {c: 1}}}, :'a.b.c')
|
36
34
|
=> 1
|
37
35
|
|
38
|
-
HashOp::
|
36
|
+
HashOp::Deep.merge({ a: { b: { c: 1 } } }, :'a.b.c', 2)
|
39
37
|
=> {
|
40
38
|
:a => {
|
41
39
|
:b => {
|
@@ -44,7 +42,264 @@ HashOp::DeepAccess.merge({ a: { b: { c: 1 } } }, :'a.b.c', 2)
|
|
44
42
|
}
|
45
43
|
}
|
46
44
|
```
|
47
|
-
|
45
|
+
|
46
|
+
### Filter
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
hashes = [
|
50
|
+
{ value: 123, regexp: "itsamatch", proc: "1+1" },
|
51
|
+
{ value: 123, regexp: "abcdef", proc: "1+2" },
|
52
|
+
{ value: 234, regexp: "abcdef", proc: "1+2" }
|
53
|
+
]
|
54
|
+
criteria = {
|
55
|
+
path: :value,
|
56
|
+
matching_object: 123
|
57
|
+
}
|
58
|
+
HashOp::Filter.filter(hashes, { value: 123 })
|
59
|
+
=> [
|
60
|
+
[0] {
|
61
|
+
:proc => "1+1",
|
62
|
+
:regexp => "itsamatch",
|
63
|
+
:value => 123
|
64
|
+
},
|
65
|
+
[1] {
|
66
|
+
:proc => "1+2",
|
67
|
+
:regexp => "abcdef",
|
68
|
+
:value => 123
|
69
|
+
}
|
70
|
+
]
|
71
|
+
HashOp::Filter.filter(hashes, { value: 123, regexp: /match/ })
|
72
|
+
=> [
|
73
|
+
[0] {
|
74
|
+
:proc => "1+1",
|
75
|
+
:regexp => "itsamatch",
|
76
|
+
:value => 123
|
77
|
+
}
|
78
|
+
]
|
79
|
+
HashOp::Filter.filter(hashes, { proc: ->(x) { eval(x) == 2 } })
|
80
|
+
=> [
|
81
|
+
[0] {
|
82
|
+
:proc => "1+1",
|
83
|
+
:regexp => "itsamatch",
|
84
|
+
:value => 123
|
85
|
+
}
|
86
|
+
]
|
87
|
+
|
88
|
+
Internally, `HashOp::Filter::filter` uses
|
89
|
+
`HashOp::Filter::match?(hash, criteria)` which you can
|
90
|
+
use too.
|
91
|
+
```
|
92
|
+
|
93
|
+
### Mapping
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
hash = {a: { b: { c: 1 } } }
|
97
|
+
mapping = { r: { path: :'a.b.c' } }
|
98
|
+
HashOp::Mapping.apply_mapping(hash, mapping)
|
99
|
+
=> {
|
100
|
+
:r => 1
|
101
|
+
}
|
102
|
+
|
103
|
+
hash = {
|
104
|
+
raw: { deep: 'raw_value' },
|
105
|
+
time: '2015-07-06 03:37:13 +0200',
|
106
|
+
mapped_hash: {
|
107
|
+
raw: { deep: 'deep_raw_value' },
|
108
|
+
time: '2014-07-06 03:37:13 +0200'
|
109
|
+
},
|
110
|
+
parseable_string: 'a=1;b=2;t=2013-07-06 03:37:13 +0200',
|
111
|
+
array: [
|
112
|
+
'2015-07-06 03:37:13 +0200',
|
113
|
+
'2014-07-06 03:37:13 +0200',
|
114
|
+
'2013-07-06 03:37:13 +0200'
|
115
|
+
]
|
116
|
+
}
|
117
|
+
mapping = {
|
118
|
+
raw: { path: :'raw.deep' },
|
119
|
+
time: { path: :time, type: :time },
|
120
|
+
raw_from_mapped_hash: {
|
121
|
+
path: :'mapped_hash.raw.deep',
|
122
|
+
},
|
123
|
+
time_from_mapped_hash: {
|
124
|
+
path: :'mapped_hash.time',
|
125
|
+
type: :time
|
126
|
+
},
|
127
|
+
values_from_parseable_string: {
|
128
|
+
path: :parseable_string,
|
129
|
+
type: :parseable_string,
|
130
|
+
parsing_mapping: {
|
131
|
+
value: { regexp: 'a=(\d)+;' },
|
132
|
+
time: {
|
133
|
+
regexp: 't=(.*)$',
|
134
|
+
type: :time
|
135
|
+
}
|
136
|
+
}
|
137
|
+
},
|
138
|
+
times_from_array: {
|
139
|
+
type: :array,
|
140
|
+
path: :array,
|
141
|
+
item_mapping: { type: :time }
|
142
|
+
}
|
143
|
+
}
|
144
|
+
HashOp::Mapping.apply_mapping(hash, mapping)
|
145
|
+
=> {
|
146
|
+
:raw => "raw_value",
|
147
|
+
:raw_from_mapped_hash => "deep_raw_value",
|
148
|
+
:time => 2015-07-06 03:37:13 +0200,
|
149
|
+
:time_from_mapped_hash => 2014-07-06 03:37:13 +0200,
|
150
|
+
:times_from_array => [
|
151
|
+
[0] 2015-07-06 03:37:13 +0200,
|
152
|
+
[1] 2014-07-06 03:37:13 +0200,
|
153
|
+
[2] 2013-07-06 03:37:13 +0200
|
154
|
+
],
|
155
|
+
:values_from_parseable_string => {
|
156
|
+
:time => 2013-07-06 03:37:13 +0200,
|
157
|
+
:value => "1"
|
158
|
+
}
|
159
|
+
}
|
160
|
+
```
|
161
|
+
|
162
|
+
### Grouping
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
hashes = [
|
166
|
+
{
|
167
|
+
grouping_path: 'A',
|
168
|
+
value: 1,
|
169
|
+
node: { 'deep_grouping_path': 'AA' }
|
170
|
+
},
|
171
|
+
{
|
172
|
+
grouping_path: 'B',
|
173
|
+
value: 2,
|
174
|
+
node: { 'deep_grouping_path': 'BB' }
|
175
|
+
},
|
176
|
+
{
|
177
|
+
grouping_path: 'A',
|
178
|
+
value: 3,
|
179
|
+
node: { 'deep_grouping_path': 'AB' }
|
180
|
+
},
|
181
|
+
{
|
182
|
+
grouping_path: 'A',
|
183
|
+
value: 4,
|
184
|
+
node: { 'deep_grouping_path': 'AA' }
|
185
|
+
}
|
186
|
+
]
|
187
|
+
HashOp::Grouping.group_on_path(hashes, :grouping_path)
|
188
|
+
=> {
|
189
|
+
"A" => [
|
190
|
+
[0] {
|
191
|
+
:grouping_path => "A",
|
192
|
+
:node => {
|
193
|
+
:deep_grouping_path => "AA"
|
194
|
+
},
|
195
|
+
:value => 1
|
196
|
+
},
|
197
|
+
[1] {
|
198
|
+
:grouping_path => "A",
|
199
|
+
:node => {
|
200
|
+
:deep_grouping_path => "AB"
|
201
|
+
},
|
202
|
+
:value => 3
|
203
|
+
},
|
204
|
+
[2] {
|
205
|
+
:grouping_path => "A",
|
206
|
+
:node => {
|
207
|
+
:deep_grouping_path => "AA"
|
208
|
+
},
|
209
|
+
:value => 4
|
210
|
+
}
|
211
|
+
],
|
212
|
+
"B" => [
|
213
|
+
[0] {
|
214
|
+
:grouping_path => "B",
|
215
|
+
:node => {
|
216
|
+
:deep_grouping_path => "BB"
|
217
|
+
},
|
218
|
+
:value => 2
|
219
|
+
}
|
220
|
+
]
|
221
|
+
}
|
222
|
+
HashOp::Grouping.group_on_path(hashes, :'node.deep_grouping_path')
|
223
|
+
=> {
|
224
|
+
"AA" => [
|
225
|
+
[0] {
|
226
|
+
:grouping_path => "A",
|
227
|
+
:node => {
|
228
|
+
:deep_grouping_path => "AA"
|
229
|
+
},
|
230
|
+
:value => 1
|
231
|
+
},
|
232
|
+
[1] {
|
233
|
+
:grouping_path => "A",
|
234
|
+
:node => {
|
235
|
+
:deep_grouping_path => "AA"
|
236
|
+
},
|
237
|
+
:value => 4
|
238
|
+
}
|
239
|
+
],
|
240
|
+
"AB" => [
|
241
|
+
[0] {
|
242
|
+
:grouping_path => "A",
|
243
|
+
:node => {
|
244
|
+
:deep_grouping_path => "AB"
|
245
|
+
},
|
246
|
+
:value => 3
|
247
|
+
}
|
248
|
+
],
|
249
|
+
"BB" => [
|
250
|
+
[0] {
|
251
|
+
:grouping_path => "B",
|
252
|
+
:node => {
|
253
|
+
:deep_grouping_path => "BB"
|
254
|
+
},
|
255
|
+
:value => 2
|
256
|
+
}
|
257
|
+
]
|
258
|
+
}
|
259
|
+
HashOp::Grouping.group_on_paths(hashes, [:grouping_path, :'node.deep_grouping_path'])
|
260
|
+
=> {
|
261
|
+
"A" => {
|
262
|
+
"AA" => [
|
263
|
+
[0] {
|
264
|
+
:grouping_path => "A",
|
265
|
+
:node => {
|
266
|
+
:deep_grouping_path => "AA"
|
267
|
+
},
|
268
|
+
:value => 1
|
269
|
+
},
|
270
|
+
[1] {
|
271
|
+
:grouping_path => "A",
|
272
|
+
:node => {
|
273
|
+
:deep_grouping_path => "AA"
|
274
|
+
},
|
275
|
+
:value => 4
|
276
|
+
}
|
277
|
+
],
|
278
|
+
"AB" => [
|
279
|
+
[0] {
|
280
|
+
:grouping_path => "A",
|
281
|
+
:node => {
|
282
|
+
:deep_grouping_path => "AB"
|
283
|
+
},
|
284
|
+
:value => 3
|
285
|
+
}
|
286
|
+
]
|
287
|
+
},
|
288
|
+
"B" => {
|
289
|
+
"BB" => [
|
290
|
+
[0] {
|
291
|
+
:grouping_path => "B",
|
292
|
+
:node => {
|
293
|
+
:deep_grouping_path => "BB"
|
294
|
+
},
|
295
|
+
:value => 2
|
296
|
+
}
|
297
|
+
]
|
298
|
+
}
|
299
|
+
}
|
300
|
+
```
|
301
|
+
|
302
|
+
_See specs for more details on each operation and operations not documented here._
|
48
303
|
|
49
304
|
## Development
|
50
305
|
|
@@ -55,3 +310,15 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
55
310
|
## Contributing
|
56
311
|
|
57
312
|
Bug reports and pull requests are welcome on GitHub at https://github.com/rchampourlier/hash_op. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) [code of conduct](CODE_OF_CONDUCT.md).
|
313
|
+
|
314
|
+
## Revisions
|
315
|
+
|
316
|
+
### 0.2.0
|
317
|
+
|
318
|
+
- Removed some operations that made no real sense (`Math.sum` and `Math.sum_two`).
|
319
|
+
- Renamed `DeepAccess` to `Deep`.
|
320
|
+
- Renamed `Merge.merge` to `Merge.flat` and `Merge.merge_by_group` to `Merge.by_group`.
|
321
|
+
|
322
|
+
### 0.1.0
|
323
|
+
|
324
|
+
Initial version
|
@@ -5,19 +5,19 @@ require 'active_support/all'
|
|
5
5
|
# through a path in the form of 'a.b.c' or an array
|
6
6
|
# of segments ['a', 'b', 'c'].
|
7
7
|
module HashOp
|
8
|
-
module
|
8
|
+
module Deep
|
9
9
|
|
10
10
|
# Examples:
|
11
11
|
# h = {a: {b: {c: 1}}}
|
12
|
-
# HashOp::
|
13
|
-
# HashOp::
|
14
|
-
# HashOp::
|
15
|
-
# HashOp::
|
16
|
-
# HashOp::
|
17
|
-
# HashOp::
|
12
|
+
# HashOp::Deep.fetch(h, :a) # => {:b=>{:c=>1}}
|
13
|
+
# HashOp::Deep.fetch(h, :'a.b') # => {:c=>1}
|
14
|
+
# HashOp::Deep.fetch(h, :'a.b.c') # => 1
|
15
|
+
# HashOp::Deep.fetch(h, [:a]) # => {:b=>{:c=>1}}
|
16
|
+
# HashOp::Deep.fetch(h, [:a, :b, :c]) # => 1
|
17
|
+
# HashOp::Deep.fetch(h, :'b.c.a') # => nil
|
18
18
|
#
|
19
19
|
def fetch(hash, path)
|
20
|
-
|
20
|
+
fail ArgumentError, 'First argument must be an Hash' unless hash.is_a?(Hash)
|
21
21
|
if path.class.in? [String, Symbol]
|
22
22
|
fetch_with_deep_key(hash, path)
|
23
23
|
elsif path.is_a? Array
|
@@ -28,8 +28,16 @@ module HashOp
|
|
28
28
|
end
|
29
29
|
module_function :fetch
|
30
30
|
|
31
|
+
# Examples:
|
32
|
+
# h = {}
|
33
|
+
# h = HashOp::Deep.merge(h, :a, 1)
|
34
|
+
# => { :a => 1 }
|
35
|
+
# h = HashOp::Deep.merge(h, :'a.b', 2)
|
36
|
+
# => { :a => { :b => 2 }
|
37
|
+
# h = HashOp::Deep.merge(h, :b, 3)
|
38
|
+
# => { :a => { :b => 2 }, :b => 3 }
|
31
39
|
def merge(hash, path, value)
|
32
|
-
|
40
|
+
fail ArgumentError, 'First argument must be an Hash' unless hash.is_a? Hash
|
33
41
|
if path.class.in? [String, Symbol]
|
34
42
|
merge_with_deep_key(hash, path, value)
|
35
43
|
elsif path.is_a? Array
|
@@ -40,6 +48,24 @@ module HashOp
|
|
40
48
|
end
|
41
49
|
module_function :merge
|
42
50
|
|
51
|
+
# Example:
|
52
|
+
# hash = { a: { :b => 1, 'c' => 2 }, d: 0 }
|
53
|
+
# HashOp::Deep.paths(hash)
|
54
|
+
# => [[:a, :b], [:a, 'c' ], [:d]]
|
55
|
+
def paths(hash)
|
56
|
+
r = []
|
57
|
+
hash.each do |key, value|
|
58
|
+
if value.is_a?(Hash)
|
59
|
+
paths(value).each do |deep_key|
|
60
|
+
r << [key] + Array(deep_key)
|
61
|
+
end
|
62
|
+
else r << Array(key)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
r.uniq
|
66
|
+
end
|
67
|
+
module_function :paths
|
68
|
+
|
43
69
|
private
|
44
70
|
|
45
71
|
def fetch_with_deep_key(hash, deep_key)
|
data/lib/hash_op/filter.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'hash_op/
|
1
|
+
require 'hash_op/deep'
|
2
2
|
|
3
3
|
# Performs filtering operation on hash or array of hashes
|
4
4
|
module HashOp
|
@@ -7,6 +7,8 @@ module HashOp
|
|
7
7
|
# Filters an array of hashes according to criteria
|
8
8
|
# on the values of each hash.
|
9
9
|
#
|
10
|
+
# Check the README for examples.
|
11
|
+
#
|
10
12
|
# @param [Array] hashes array of hashes to be filtered
|
11
13
|
# @param [Hash] criteria the method uses ::match?, see
|
12
14
|
# definition for more details
|
@@ -24,17 +26,17 @@ module HashOp
|
|
24
26
|
# @param [Hash] hash the hash to be filtered
|
25
27
|
# @param [String, Symbol] the path of the array of hashes
|
26
28
|
# inside hash to be filtered. Accessed through
|
27
|
-
# HashOp::
|
29
|
+
# HashOp::Deep (path like 'path.to.some.key').
|
28
30
|
# @param [Hash] criteria to filter on (performed through
|
29
31
|
# ::filter, so see the method for more details)
|
30
32
|
# @return [Hash] the hash with values in array at path
|
31
33
|
# filtered according to criteria
|
32
34
|
def filter_deep(hash, path, criteria = {})
|
33
|
-
array = HashOp::
|
35
|
+
array = HashOp::Deep.fetch hash, path
|
34
36
|
raise "Can\'t filter hash at path \"#{path}\", value is not an array" unless array.is_a?(Array)
|
35
37
|
|
36
38
|
filtered_array = filter(array, criteria)
|
37
|
-
HashOp::
|
39
|
+
HashOp::Deep.merge hash, path, filtered_array
|
38
40
|
end
|
39
41
|
module_function :filter_deep
|
40
42
|
|
@@ -43,7 +45,7 @@ module HashOp
|
|
43
45
|
# each criteria is an hash
|
44
46
|
# { path => matching_object }, where:
|
45
47
|
# - path [String, Symbol] is used to access the value
|
46
|
-
# in the filtered object (through HashOp::
|
48
|
+
# in the filtered object (through HashOp::Deep::fetch)
|
47
49
|
# - matching_object [Object] the object defining the
|
48
50
|
# match:
|
49
51
|
# * a Proc which will be called with the value and
|
@@ -54,20 +56,18 @@ module HashOp
|
|
54
56
|
# * any other value will be matched against the
|
55
57
|
# equality operator.
|
56
58
|
def match?(hash, criteria)
|
57
|
-
|
59
|
+
unless hash.is_a?(Hash)
|
60
|
+
fail ArgumentError, 'First argument must be an Hash'
|
61
|
+
end
|
58
62
|
return true if criteria.blank?
|
59
63
|
|
60
64
|
criteria.map do |path, matching_object|
|
61
|
-
value = HashOp::
|
62
|
-
|
65
|
+
value = HashOp::Deep.fetch(hash, path)
|
63
66
|
case
|
64
|
-
|
65
67
|
when matching_object.is_a?(Proc)
|
66
68
|
matching_object.call(value)
|
67
|
-
|
68
69
|
when matching_object.is_a?(Regexp)
|
69
|
-
|
70
|
-
|
70
|
+
(value =~ matching_object).present?
|
71
71
|
else value == matching_object
|
72
72
|
end
|
73
73
|
end.uniq == [true]
|
data/lib/hash_op/grouping.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'hash_op/
|
1
|
+
require 'hash_op/deep'
|
2
2
|
|
3
3
|
# A module to perform group operations on hashes.
|
4
4
|
module HashOp
|
@@ -10,7 +10,7 @@ module HashOp
|
|
10
10
|
# @return [Hash]
|
11
11
|
def group_on_path(hashes, path)
|
12
12
|
hashes.inject({}) do |result, hash|
|
13
|
-
value_at_path = HashOp::
|
13
|
+
value_at_path = HashOp::Deep.fetch(hash, path)
|
14
14
|
result[value_at_path] ||= []
|
15
15
|
result[value_at_path] << hash
|
16
16
|
result
|
data/lib/hash_op/mapping.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'hash_op/
|
1
|
+
require 'hash_op/deep'
|
2
2
|
|
3
3
|
# A module to perform mapping from hash to hash.
|
4
4
|
module HashOp
|
@@ -9,20 +9,35 @@ module HashOp
|
|
9
9
|
# mapping (see example below)
|
10
10
|
# A mapping hash is:
|
11
11
|
# a_key [String or Symbol] => a_mapping_item [Hash]
|
12
|
-
# A mapping item is:
|
13
|
-
# path: [String or Symbol] the path to the value
|
14
|
-
# type: [
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
12
|
+
# A mapping item is composed of:
|
13
|
+
# - path: [String or Symbol] the path to the value
|
14
|
+
# - type: [Symbol] defines how the item will be processed
|
15
|
+
# before being added to the result
|
16
|
+
# - `raw` (defaults, if no type specified): the value
|
17
|
+
# is passed with no transformation
|
18
|
+
# - `time`: the value is parsed using `Time.parse`
|
19
|
+
# (if the parse fails, nil is passed in the result,
|
20
|
+
# no exception raised)
|
21
|
+
# - `mapped_hash`: the value is mapped recursively,
|
22
|
+
# using the `mapping` key as the mapping
|
23
|
+
# - `parseable_string`: the string value is analyzed
|
24
|
+
# using regexps and each value extracted by a regexp
|
25
|
+
# can be recursively processed using mappings
|
26
|
+
# applicable to strings (e.g. time); the mapping
|
27
|
+
# to use for the parsing is defined in
|
28
|
+
# `parsing_mapping` (see the README for an example)
|
29
|
+
# - `array`: recursively apply a mapping over each
|
30
|
+
# item in the array; the mapping for each item is
|
31
|
+
# defined in `item_mapping`
|
32
|
+
# - mapping: for a mapping item of type `mapped_hash`
|
33
|
+
# - parsing_mapping: for a mapping item of type
|
34
|
+
# `parseable_string`
|
35
|
+
# - item_mapping: for a mapping item of type `array`
|
19
36
|
#
|
20
37
|
# Example of a mapping hash:
|
21
38
|
# {
|
22
|
-
#
|
23
|
-
# str: { path: :'root.string' },
|
39
|
+
# raw: { path: :'root.value' },
|
24
40
|
# time: { path: :'root.deep_1.deep_2.time', type: :time },
|
25
|
-
# strings: { path: :'root.array_of_strings' },
|
26
41
|
# mapped_hashes: {
|
27
42
|
# path: :'root.array_of_mapped_hashes',
|
28
43
|
# type: :array,
|
@@ -38,7 +53,7 @@ module HashOp
|
|
38
53
|
mapping_item = mapping[key]
|
39
54
|
path = mapping_item[:path]
|
40
55
|
raise "path not found in mapping item #{mapping_item}" if path.nil?
|
41
|
-
raw = HashOp::
|
56
|
+
raw = HashOp::Deep.fetch(hash, path)
|
42
57
|
processed = process_with_mapping_item(raw, mapping_item)
|
43
58
|
mapped_hash[key] = processed
|
44
59
|
mapped_hash
|
data/lib/hash_op/math.rb
CHANGED
@@ -1,69 +1,52 @@
|
|
1
|
-
require 'hash_op/
|
1
|
+
require 'hash_op/deep'
|
2
2
|
|
3
3
|
# A set of functions to perform mathematical operations
|
4
4
|
# on Hashes.
|
5
5
|
module HashOp
|
6
6
|
module Math
|
7
7
|
|
8
|
-
# @param hashes [Array] of Hash instances
|
9
|
-
# @return [Hash] summing values of the same key
|
10
|
-
def sum(*hashes)
|
11
|
-
hashes.flatten!
|
12
|
-
case hashes.length
|
13
|
-
when 0 then {}
|
14
|
-
when 1 then hashes.first
|
15
|
-
when 2 then sum_two(*hashes)
|
16
|
-
else
|
17
|
-
sum(*[sum_two(*hashes[0..1])] + hashes[2..-1])
|
18
|
-
end
|
19
|
-
end
|
20
|
-
module_function :sum
|
21
|
-
|
22
|
-
def sum_two(hash_a, hash_b)
|
23
|
-
hash_b.each do |key, hash_b_value|
|
24
|
-
if hash_a[key]
|
25
|
-
hash_a[key] += hash_b_value
|
26
|
-
else
|
27
|
-
hash_a[key] = hash_b_value
|
28
|
-
end
|
29
|
-
end
|
30
|
-
hash_a
|
31
|
-
end
|
32
|
-
module_function :sum_two
|
33
|
-
|
34
8
|
# Sum values in an array of hashes by grouping on a given
|
35
9
|
# key.
|
36
10
|
#
|
37
11
|
# Example:
|
38
12
|
# hashes = [
|
39
|
-
# {
|
40
|
-
# {
|
41
|
-
# {
|
13
|
+
# { group_1: :a, group_2: :a, value_1: 1, value_2: 1 },
|
14
|
+
# { group_1: :a, group_2: :b, value_1: 1, value_2: 2 },
|
15
|
+
# { group_1: :a, group_2: :b, value_1: 1, value_2: 2 },
|
16
|
+
# { group_1: :b, group_2: :c, value_1: 1, value_2: 3 }
|
17
|
+
# ]
|
18
|
+
# HashOp::Math.sum_on_groups(hashes,
|
19
|
+
# [:group_1], [:value_1, :value_2]
|
20
|
+
# )
|
21
|
+
# => [
|
22
|
+
# { group_1: :a, value_1: 3, value_2: 5 },
|
23
|
+
# { group_1: :b, value_1: 1, value_2: 3 }
|
24
|
+
# ]
|
25
|
+
# HashOp::Math.sum_on_groups(hashes,
|
26
|
+
# [:group_1, :group_2], [:value_1, :value_2]
|
27
|
+
# )
|
28
|
+
# => [
|
29
|
+
# { group_1: :a, group_2: :a, value_1: 1, value_2: 1 },
|
30
|
+
# { group_1: :a, group_2: :b, value_1: 2, value_2: 4 },
|
31
|
+
# { group_1: :b, group_2: :c, value_1: 1, value_2: 3 }
|
42
32
|
# ]
|
43
|
-
# HashOp::Math.sum_on_groups(hashes, :group, :value)
|
44
|
-
# # => [
|
45
|
-
# # { group: :a, value: 2 },
|
46
|
-
# # { group: :b, value: 1 }
|
47
|
-
# # ]
|
48
33
|
#
|
49
34
|
# @param hashes [Array] the hashes to be summed
|
50
35
|
# @param group_key [Object] the key to use to group items on
|
51
36
|
# @param value_key [Object] the key of the values to sum
|
52
37
|
# @return [Array]
|
53
|
-
def sum_on_groups(hashes,
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
work[hash[group_key]][value_key] += hash[value_key]
|
38
|
+
def sum_on_groups(hashes, grouping_paths, value_paths)
|
39
|
+
grouped_hashes = Grouping.group_on_paths(hashes, grouping_paths)
|
40
|
+
group_paths = Deep.paths(grouped_hashes)
|
41
|
+
result = group_paths.map do |group_path|
|
42
|
+
group_hashes = HashOp::Deep.fetch(grouped_hashes, group_path)
|
43
|
+
group_values = value_paths.map do |value_path|
|
44
|
+
group_value = HashOp::Math.sum_at_path(group_hashes, value_path)
|
45
|
+
{ value_path => group_value }
|
62
46
|
end
|
63
|
-
|
47
|
+
Hash[[grouping_paths, group_path].transpose].merge(Merge.flat(group_values))
|
64
48
|
end
|
65
|
-
|
66
|
-
end
|
49
|
+
end
|
67
50
|
module_function :sum_on_groups
|
68
51
|
|
69
52
|
# Sum values for the specified hashes at the specified path.
|
@@ -76,7 +59,7 @@ module HashOp
|
|
76
59
|
#
|
77
60
|
def sum_at_path(hashes, path, zero = 0)
|
78
61
|
hashes.inject(zero) do |sum, hash|
|
79
|
-
value = HashOp::
|
62
|
+
value = HashOp::Deep.fetch(hash, path) || zero
|
80
63
|
sum + value
|
81
64
|
end
|
82
65
|
end
|
@@ -85,14 +68,14 @@ module HashOp
|
|
85
68
|
# @param [Array] hashes array of Hash
|
86
69
|
# @param [String, Symbol] path to deep value in each hash
|
87
70
|
def deep_min(hashes, path)
|
88
|
-
hashes.map { |hash| HashOp::
|
71
|
+
hashes.map { |hash| HashOp::Deep.fetch hash, path }.min
|
89
72
|
end
|
90
73
|
module_function :deep_min
|
91
74
|
|
92
75
|
# @param [Array] hashes array of Hash
|
93
76
|
# @param [String, Symbol] path to deep value in each hash
|
94
77
|
def deep_max(hashes, path)
|
95
|
-
hashes.map { |hash| HashOp::
|
78
|
+
hashes.map { |hash| HashOp::Deep.fetch hash, path }.max
|
96
79
|
end
|
97
80
|
module_function :deep_max
|
98
81
|
end
|
data/lib/hash_op/merge.rb
CHANGED
@@ -3,19 +3,19 @@ module HashOp
|
|
3
3
|
|
4
4
|
# Merge all specified hashes by merging the second
|
5
5
|
# in the first, the third in the result, and so on.
|
6
|
-
def
|
6
|
+
def flat(hashes)
|
7
7
|
hashes.inject({}) do |result, hash|
|
8
8
|
result.merge hash
|
9
9
|
end
|
10
10
|
end
|
11
|
-
module_function :
|
11
|
+
module_function :flat
|
12
12
|
|
13
13
|
# Merge hashes by grouping them on the
|
14
14
|
# specified key value and merging them all together.
|
15
|
-
def
|
15
|
+
def by_group(hashes, key)
|
16
16
|
groups = hashes.group_by { |h| h[key] }
|
17
|
-
groups.values.map { |g|
|
17
|
+
groups.values.map { |g| flat(g) }
|
18
18
|
end
|
19
|
-
module_function :
|
19
|
+
module_function :by_group
|
20
20
|
end
|
21
21
|
end
|
data/lib/hash_op/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_op
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Romain Champourlier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -101,8 +101,10 @@ executables: []
|
|
101
101
|
extensions: []
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
|
+
- ".codeclimate.yml"
|
104
105
|
- ".gitignore"
|
105
106
|
- ".rspec"
|
107
|
+
- ".rubocop.yml"
|
106
108
|
- ".travis.yml"
|
107
109
|
- CODE_OF_CONDUCT.md
|
108
110
|
- Gemfile
|
@@ -113,7 +115,7 @@ files:
|
|
113
115
|
- bin/setup
|
114
116
|
- hash_op.gemspec
|
115
117
|
- lib/hash_op.rb
|
116
|
-
- lib/hash_op/
|
118
|
+
- lib/hash_op/deep.rb
|
117
119
|
- lib/hash_op/filter.rb
|
118
120
|
- lib/hash_op/grouping.rb
|
119
121
|
- lib/hash_op/mapping.rb
|
@@ -140,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
142
|
version: '0'
|
141
143
|
requirements: []
|
142
144
|
rubyforge_project:
|
143
|
-
rubygems_version: 2.4.
|
145
|
+
rubygems_version: 2.4.5.1
|
144
146
|
signing_key:
|
145
147
|
specification_version: 4
|
146
148
|
summary: A Ruby library of functions to access and manipulate hash data structures.
|