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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b2933aa0df964cd1340e8e6ee1ddb7d693b4ee88
4
- data.tar.gz: 14fecfc284ef874329d595191e1e00608b36817a
3
+ metadata.gz: e4ee4ebeea9d4222628d2e8bc1a35ac27ff6b219
4
+ data.tar.gz: 9cc047ece2b3c87a1baf144f17f3a3434da43afc
5
5
  SHA512:
6
- metadata.gz: be9847903c1c4e2fb2ed8e7ce934a2b5d7ecdd6e4e45a047ae20efd7632ff8924ec3f5dde5c8c21015c7036b452f0c363decddc8b006affdf994f0bede712c77
7
- data.tar.gz: 3d8c64ef1ab7dbc28787043071ee3e171ad1cccee96943bbafd198830661cab9c26c660c684946756d42721cb50fb2db5de2f9ccc5711ede354dd6ad064dfa37
6
+ metadata.gz: bb145f094b7bd06a0f77ad34b81e0b674d6bb74daa5358a1b60655dc171be67e1113ce39980aee0a99dd26aafaf289e8a04c293930ebebaf722a874868c8fd7f
7
+ data.tar.gz: 302dede0a9005b1284bb3d405e35937defebc45ad6744f635b7bcb8b15a351460ec43487031a46e6439ff795296fb8da87c3c4571691b0f690e78ce32ab93fec
data/.codeclimate.yml ADDED
@@ -0,0 +1,4 @@
1
+ languages:
2
+ Ruby: true
3
+ # exclude_paths:
4
+ # - "foo/bar.rb"
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
@@ -3,4 +3,6 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in hash_op.gemspec
4
4
  gemspec
5
5
 
6
- gem "coveralls", require: false
6
+ group :test do
7
+ gem "coveralls", require: false
8
+ end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hash_op (0.1.0)
4
+ hash_op (0.2.0)
5
5
  activesupport
6
6
 
7
7
  GEM
@@ -85,4 +85,4 @@ DEPENDENCIES
85
85
  rspec
86
86
 
87
87
  BUNDLED WITH
88
- 1.10.3
88
+ 1.10.6
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/travis-ci/travis-web.svg?branch=master)](https://travis-ci.org/travis-ci/travis-web)
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::DeepAccess.fetch({a: {b: {c: 1}}}, :'a.b.c')
33
+ HashOp::Deep.fetch({a: {b: {c: 1}}}, :'a.b.c')
36
34
  => 1
37
35
 
38
- HashOp::DeepAccess.merge({ a: { b: { c: 1 } } }, :'a.b.c', 2)
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
- TODO: complete with other available operations
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 DeepAccess
8
+ module Deep
9
9
 
10
10
  # Examples:
11
11
  # h = {a: {b: {c: 1}}}
12
- # HashOp::DeepAccess.fetch(h, :a) # => {:b=>{:c=>1}}
13
- # HashOp::DeepAccess.fetch(h, :'a.b') # => {:c=>1}
14
- # HashOp::DeepAccess.fetch(h, :'a.b.c') # => 1
15
- # HashOp::DeepAccess.fetch(h, [:a]) # => {:b=>{:c=>1}}
16
- # HashOp::DeepAccess.fetch(h, [:a, :b, :c]) # => 1
17
- # HashOp::DeepAccess.fetch(h, :'b.c.a') # => nil
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
- raise 'First argument must be an Hash' unless hash.is_a? Hash
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
- raise 'First argument must be an Hash' unless hash.is_a? Hash
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)
@@ -1,4 +1,4 @@
1
- require 'hash_op/deep_access'
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::DeepAccess (path like 'path.to.some.key').
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::DeepAccess.fetch hash, path
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::DeepAccess.merge hash, path, filtered_array
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::DeepAccess::fetch)
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
- raise ArgumentError.new('First argument must be an Hash') unless hash.is_a?(Hash)
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::DeepAccess.fetch(hash, path)
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
- !!(value =~ matching_object)
70
-
70
+ (value =~ matching_object).present?
71
71
  else value == matching_object
72
72
  end
73
73
  end.uniq == [true]
@@ -1,4 +1,4 @@
1
- require 'hash_op/deep_access'
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::DeepAccess.fetch(hash, path)
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
@@ -1,4 +1,4 @@
1
- require 'hash_op/deep_access'
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: [:time or :array]
15
- # item_mapping: [Hash] for a mapping item whose type
16
- # is :array when the array items are hashes to be
17
- # mapped again. This mapping can be done with any
18
- # level of recursion.
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
- # int: { path: :'root.integer' },
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::DeepAccess.fetch(hash, path)
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/deep_access'
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
- # { group: :a, value: 1 },
40
- # { group: :a, value: 1 },
41
- # { group: :b, value: 1 }
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, group_key, value_key)
54
- work_hash = hashes.inject({}) do |work, hash|
55
- if work[hash[group_key]].nil?
56
- work[hash[group_key]] = {
57
- group_key => hash[group_key],
58
- value_key => hash[value_key]
59
- }
60
- else
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
- work
47
+ Hash[[grouping_paths, group_path].transpose].merge(Merge.flat(group_values))
64
48
  end
65
- work_hash.values
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::DeepAccess.fetch(hash, path) || zero
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::DeepAccess.fetch hash, path }.min
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::DeepAccess.fetch hash, path }.max
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 merge(hashes)
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 :merge
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 merge_by_group(hashes, key)
15
+ def by_group(hashes, key)
16
16
  groups = hashes.group_by { |h| h[key] }
17
- groups.values.map { |g| merge(g) }
17
+ groups.values.map { |g| flat(g) }
18
18
  end
19
- module_function :merge_by_group
19
+ module_function :by_group
20
20
  end
21
21
  end
@@ -1,3 +1,3 @@
1
1
  module HashOp
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
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.1.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-06-13 00:00:00.000000000 Z
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/deep_access.rb
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.6
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.