hash_op 0.1.0 → 0.2.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 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.