hash_op 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b2933aa0df964cd1340e8e6ee1ddb7d693b4ee88
4
+ data.tar.gz: 14fecfc284ef874329d595191e1e00608b36817a
5
+ SHA512:
6
+ metadata.gz: be9847903c1c4e2fb2ed8e7ce934a2b5d7ecdd6e4e45a047ae20efd7632ff8924ec3f5dde5c8c21015c7036b452f0c363decddc8b006affdf994f0bede712c77
7
+ data.tar.gz: 3d8c64ef1ab7dbc28787043071ee3e171ad1cccee96943bbafd198830661cab9c26c660c684946756d42721cb50fb2db5de2f9ccc5711ede354dd6ad064dfa37
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
4
+ before_install: gem install bundler -v 1.10.3
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hash_op.gemspec
4
+ gemspec
5
+
6
+ gem "coveralls", require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,88 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ hash_op (0.1.0)
5
+ activesupport
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (4.2.1)
11
+ i18n (~> 0.7)
12
+ json (~> 1.7, >= 1.7.7)
13
+ minitest (~> 5.1)
14
+ thread_safe (~> 0.3, >= 0.3.4)
15
+ tzinfo (~> 1.1)
16
+ awesome_print (1.6.1)
17
+ coderay (1.1.0)
18
+ coveralls (0.8.1)
19
+ json (~> 1.8)
20
+ rest-client (>= 1.6.8, < 2)
21
+ simplecov (~> 0.10.0)
22
+ term-ansicolor (~> 1.3)
23
+ thor (~> 0.19.1)
24
+ diff-lcs (1.2.5)
25
+ docile (1.1.5)
26
+ domain_name (0.5.24)
27
+ unf (>= 0.0.5, < 1.0.0)
28
+ http-cookie (1.0.2)
29
+ domain_name (~> 0.5)
30
+ i18n (0.7.0)
31
+ json (1.8.2)
32
+ method_source (0.8.2)
33
+ mime-types (2.6.1)
34
+ minitest (5.5.1)
35
+ netrc (0.10.3)
36
+ pry (0.10.1)
37
+ coderay (~> 1.1.0)
38
+ method_source (~> 0.8.1)
39
+ slop (~> 3.4)
40
+ rake (10.4.2)
41
+ rest-client (1.8.0)
42
+ http-cookie (>= 1.0.2, < 2.0)
43
+ mime-types (>= 1.16, < 3.0)
44
+ netrc (~> 0.7)
45
+ rspec (3.3.0)
46
+ rspec-core (~> 3.3.0)
47
+ rspec-expectations (~> 3.3.0)
48
+ rspec-mocks (~> 3.3.0)
49
+ rspec-core (3.3.0)
50
+ rspec-support (~> 3.3.0)
51
+ rspec-expectations (3.3.0)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.3.0)
54
+ rspec-mocks (3.3.0)
55
+ diff-lcs (>= 1.2.0, < 2.0)
56
+ rspec-support (~> 3.3.0)
57
+ rspec-support (3.3.0)
58
+ simplecov (0.10.0)
59
+ docile (~> 1.1.0)
60
+ json (~> 1.8)
61
+ simplecov-html (~> 0.10.0)
62
+ simplecov-html (0.10.0)
63
+ slop (3.6.0)
64
+ term-ansicolor (1.3.0)
65
+ tins (~> 1.0)
66
+ thor (0.19.1)
67
+ thread_safe (0.3.5)
68
+ tins (1.5.2)
69
+ tzinfo (1.2.2)
70
+ thread_safe (~> 0.1)
71
+ unf (0.1.4)
72
+ unf_ext
73
+ unf_ext (0.0.7.1)
74
+
75
+ PLATFORMS
76
+ ruby
77
+
78
+ DEPENDENCIES
79
+ awesome_print
80
+ bundler (~> 1.10)
81
+ coveralls
82
+ hash_op!
83
+ pry
84
+ rake (~> 10.0)
85
+ rspec
86
+
87
+ BUNDLED WITH
88
+ 1.10.3
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # HashOp
2
+
3
+
4
+ A Ruby library of functions to access and manipulate hash data structures.
5
+
6
+ [![Build Status](https://travis-ci.org/travis-ci/travis-web.svg?branch=master)](https://travis-ci.org/travis-ci/travis-web)
7
+ [![Code Climate](https://codeclimate.com/github/rchampourlier/hash_op/badges/gpa.svg)](https://codeclimate.com/github/rchampourlier/hash_op)
8
+ [![Coverage Status](https://coveralls.io/repos/rchampourlier/hash_op/badge.svg)](https://coveralls.io/r/rchampourlier/hash_op)
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'hash_op'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install hash_op
25
+
26
+ ## Usage
27
+
28
+ ## Available operations
29
+
30
+ _See specs for more details on each operation._
31
+
32
+ ### Deep Access
33
+
34
+ ```ruby
35
+ HashOp::DeepAccess.fetch({a: {b: {c: 1}}}, :'a.b.c')
36
+ => 1
37
+
38
+ HashOp::DeepAccess.merge({ a: { b: { c: 1 } } }, :'a.b.c', 2)
39
+ => {
40
+ :a => {
41
+ :b => {
42
+ :c => 2
43
+ }
44
+ }
45
+ }
46
+ ```
47
+ TODO: complete with other available operations
48
+
49
+ ## Development
50
+
51
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
52
+
53
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
54
+
55
+ ## Contributing
56
+
57
+ 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).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "hash_op"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "pry"
10
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/hash_op.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hash_op/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hash_op"
8
+ spec.version = HashOp::VERSION
9
+ spec.authors = ["Romain Champourlier"]
10
+ spec.email = ["pro@rchampourlier.com"]
11
+
12
+ spec.summary = %q{A Ruby library of functions to access and manipulate hash data structures.}
13
+ spec.homepage = "https://github.com/rchampourlier/hash_op"
14
+
15
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
16
+ # delete this section to allow pushing this gem to any host.
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
21
+ end
22
+
23
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "activesupport"
29
+ spec.add_development_dependency "bundler", "~> 1.10"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rspec"
32
+ spec.add_development_dependency "pry"
33
+ spec.add_development_dependency "awesome_print"
34
+ end
@@ -0,0 +1,106 @@
1
+ require 'active_support/all'
2
+
3
+ # Provides ::fetch and ::merge methods which allows
4
+ # to perform fetch/merge operations deeply in Hash
5
+ # through a path in the form of 'a.b.c' or an array
6
+ # of segments ['a', 'b', 'c'].
7
+ module HashOp
8
+ module DeepAccess
9
+
10
+ # Examples:
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
18
+ #
19
+ def fetch(hash, path)
20
+ raise 'First argument must be an Hash' unless hash.is_a? Hash
21
+ if path.class.in? [String, Symbol]
22
+ fetch_with_deep_key(hash, path)
23
+ elsif path.is_a? Array
24
+ fetch_with_segments(hash, path)
25
+ else
26
+ raise 'Invalid attribute, must be a String or an Array'
27
+ end
28
+ end
29
+ module_function :fetch
30
+
31
+ def merge(hash, path, value)
32
+ raise 'First argument must be an Hash' unless hash.is_a? Hash
33
+ if path.class.in? [String, Symbol]
34
+ merge_with_deep_key(hash, path, value)
35
+ elsif path.is_a? Array
36
+ merge_with_segments(hash, path, value)
37
+ else
38
+ raise 'Invalid attribute, must be a String or an Array'
39
+ end
40
+ end
41
+ module_function :merge
42
+
43
+ private
44
+
45
+ def fetch_with_deep_key(hash, deep_key)
46
+ segments = deep_key.to_s.split('.')
47
+ segments.map!(&:to_sym) if deep_key.is_a? Symbol
48
+ fetch_with_segments(hash, segments)
49
+ end
50
+ module_function :fetch_with_deep_key
51
+
52
+ def fetch_with_segments(hash, segments)
53
+ return hash if segments.empty?
54
+
55
+ result = hash[segments.first]
56
+ if result.is_a? Hash
57
+ fetch_with_segments(result, segments[1..-1])
58
+ elsif result.is_a? Array
59
+ result.map do |item|
60
+ fetch_with_segments(item, segments[1..-1])
61
+ end
62
+ else
63
+ result
64
+ end
65
+ end
66
+ module_function :fetch_with_segments
67
+
68
+ def merge_with_deep_key(hash, deep_key, value)
69
+ segments = deep_key.to_s.split('.')
70
+ segments.map!(&:to_sym) if deep_key.is_a? Symbol
71
+ merge_with_segments(hash, segments, value)
72
+ end
73
+ module_function :merge_with_deep_key
74
+
75
+ def merge_with_segments(hash, segments, value)
76
+ current_segment = segments.first
77
+ remaining_segments = segments[1..-1]
78
+
79
+ return value if segments.empty?
80
+
81
+ current_value = hash[current_segment]
82
+ new_value = (
83
+ if remaining_segments.length > 0
84
+ if current_value.is_a? Hash
85
+ merge_with_segments(current_value, remaining_segments, value)
86
+ elsif current_value.is_a? Array
87
+ current_value.map do |item|
88
+ merge_with_segments(item, remaining_segments, value)
89
+ end
90
+ else
91
+ build_with_segments remaining_segments, value
92
+ end
93
+ else value
94
+ end
95
+ )
96
+ hash.merge current_segment => new_value
97
+ end
98
+ module_function :merge_with_segments
99
+
100
+ def build_with_segments(segments, value)
101
+ return value if segments.empty?
102
+ { segments.first => build_with_segments(segments[1..-1], value) }
103
+ end
104
+ module_function :build_with_segments
105
+ end
106
+ end
@@ -0,0 +1,77 @@
1
+ require 'hash_op/deep_access'
2
+
3
+ # Performs filtering operation on hash or array of hashes
4
+ module HashOp
5
+ module Filter
6
+
7
+ # Filters an array of hashes according to criteria
8
+ # on the values of each hash.
9
+ #
10
+ # @param [Array] hashes array of hashes to be filtered
11
+ # @param [Hash] criteria the method uses ::match?, see
12
+ # definition for more details
13
+ # @return [Array]
14
+ #
15
+ def filter(hashes, criteria = {})
16
+ hashes.select do |item|
17
+ match?(item, criteria)
18
+ end
19
+ end
20
+ module_function :filter
21
+
22
+ # Applies ::filter on the value of an hash
23
+ #
24
+ # @param [Hash] hash the hash to be filtered
25
+ # @param [String, Symbol] the path of the array of hashes
26
+ # inside hash to be filtered. Accessed through
27
+ # HashOp::DeepAccess (path like 'path.to.some.key').
28
+ # @param [Hash] criteria to filter on (performed through
29
+ # ::filter, so see the method for more details)
30
+ # @return [Hash] the hash with values in array at path
31
+ # filtered according to criteria
32
+ def filter_deep(hash, path, criteria = {})
33
+ array = HashOp::DeepAccess.fetch hash, path
34
+ raise "Can\'t filter hash at path \"#{path}\", value is not an array" unless array.is_a?(Array)
35
+
36
+ filtered_array = filter(array, criteria)
37
+ HashOp::DeepAccess.merge hash, path, filtered_array
38
+ end
39
+ module_function :filter_deep
40
+
41
+ # @param [Hash] hash to match against criteria
42
+ # @param [Hash] criteria to match the hash against
43
+ # each criteria is an hash
44
+ # { path => matching_object }, where:
45
+ # - path [String, Symbol] is used to access the value
46
+ # in the filtered object (through HashOp::DeepAccess::fetch)
47
+ # - matching_object [Object] the object defining the
48
+ # match:
49
+ # * a Proc which will be called with the value and
50
+ # which should return true to indicate a match, else
51
+ # false,
52
+ # * a Regexp will be matched using the regexp match
53
+ # operator,
54
+ # * any other value will be matched against the
55
+ # equality operator.
56
+ def match?(hash, criteria)
57
+ raise ArgumentError.new('First argument must be an Hash') unless hash.is_a?(Hash)
58
+ return true if criteria.blank?
59
+
60
+ criteria.map do |path, matching_object|
61
+ value = HashOp::DeepAccess.fetch(hash, path)
62
+
63
+ case
64
+
65
+ when matching_object.is_a?(Proc)
66
+ matching_object.call(value)
67
+
68
+ when matching_object.is_a?(Regexp)
69
+ !!(value =~ matching_object)
70
+
71
+ else value == matching_object
72
+ end
73
+ end.uniq == [true]
74
+ end
75
+ module_function :match?
76
+ end
77
+ end
@@ -0,0 +1,36 @@
1
+ require 'hash_op/deep_access'
2
+
3
+ # A module to perform group operations on hashes.
4
+ module HashOp
5
+ module Grouping
6
+
7
+ # @param hashes [Array] hashes to be grouped
8
+ # @param paths [Object] path on which the hashes must be
9
+ # grouped
10
+ # @return [Hash]
11
+ def group_on_path(hashes, path)
12
+ hashes.inject({}) do |result, hash|
13
+ value_at_path = HashOp::DeepAccess.fetch(hash, path)
14
+ result[value_at_path] ||= []
15
+ result[value_at_path] << hash
16
+ result
17
+ end
18
+ end
19
+ module_function :group_on_path
20
+
21
+ # @param hashes [Array] hashes to be grouped
22
+ # @param paths [Array] paths on which the hashes must be
23
+ # grouped, by order of grouping (1st group-level first)
24
+ # @return [Hash]
25
+ def group_on_paths(hashes, paths)
26
+ return group_on_path(hashes, paths.first) if paths.length == 1
27
+
28
+ path = paths.first
29
+ path_groups = group_on_path(hashes, path)
30
+ path_groups.each do |group_key, grouped_hashes|
31
+ path_groups[group_key] = group_on_paths(grouped_hashes, paths[1..-1])
32
+ end
33
+ end
34
+ module_function :group_on_paths
35
+ end
36
+ end
@@ -0,0 +1,103 @@
1
+ require 'hash_op/deep_access'
2
+
3
+ # A module to perform mapping from hash to hash.
4
+ module HashOp
5
+ module Mapping
6
+
7
+ # @param hash [Hash] the hash to map
8
+ # @param mapping [Hash] the mapping to use to perform the
9
+ # mapping (see example below)
10
+ # A mapping hash is:
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.
19
+ #
20
+ # Example of a mapping hash:
21
+ # {
22
+ # int: { path: :'root.integer' },
23
+ # str: { path: :'root.string' },
24
+ # time: { path: :'root.deep_1.deep_2.time', type: :time },
25
+ # strings: { path: :'root.array_of_strings' },
26
+ # mapped_hashes: {
27
+ # path: :'root.array_of_mapped_hashes',
28
+ # type: :array,
29
+ # item_mapping: {
30
+ # str: { path: :'root.string' },
31
+ # time: { path: :'root.deep_1.time', type: :time }
32
+ # }
33
+ # }
34
+ # }
35
+ #
36
+ def apply_mapping(hash, mapping)
37
+ mapping.keys.inject({}) do |mapped_hash, key|
38
+ mapping_item = mapping[key]
39
+ path = mapping_item[:path]
40
+ raise "path not found in mapping item #{mapping_item}" if path.nil?
41
+ raw = HashOp::DeepAccess.fetch(hash, path)
42
+ processed = process_with_mapping_item(raw, mapping_item)
43
+ mapped_hash[key] = processed
44
+ mapped_hash
45
+ end
46
+ end
47
+ module_function :apply_mapping
48
+
49
+ def process_with_mapping_item(raw_value, mapping_item)
50
+ type = mapping_item[:type] || :raw
51
+ send :"process_with_mapping_item_#{type}", raw_value, mapping_item
52
+ end
53
+ module_function :process_with_mapping_item
54
+
55
+ def process_with_mapping_item_raw(value, mapping_item)
56
+ value
57
+ end
58
+ module_function :process_with_mapping_item_raw
59
+
60
+ def process_with_mapping_item_time(value, mapping_item)
61
+ return nil if value.nil?
62
+ begin
63
+ Time.parse value
64
+ rescue ArgumentError
65
+ nil
66
+ end
67
+ end
68
+ module_function :process_with_mapping_item_time
69
+
70
+ def process_with_mapping_item_mapped_hash(value, mapping_item)
71
+ return {} if value.nil?
72
+ mapping_item_mapping = mapping_item[:mapping]
73
+ raise "Missing mapping for mapped_hash \"#{value}\"" if mapping_item_mapping.nil?
74
+
75
+ apply_mapping(value, mapping_item_mapping)
76
+ end
77
+ module_function :process_with_mapping_item_mapped_hash
78
+
79
+ def process_with_mapping_item_parseable_string(value, mapping_item)
80
+ return {} if value.nil?
81
+ parsing_mapping = mapping_item[:parsing_mapping]
82
+ raise "Missing parsing_mapping for mapping #{mapping_item}" if parsing_mapping.nil?
83
+ parsing_results = parsing_mapping.map do |parsing_key, parsing_options|
84
+ regexp = Regexp.new parsing_options[:regexp]
85
+ match = regexp.match(value)
86
+ result = match ? process_with_mapping_item(match[1], parsing_options) : nil
87
+ [parsing_key, result]
88
+ end
89
+ Hash[parsing_results]
90
+ end
91
+ module_function :process_with_mapping_item_parseable_string
92
+
93
+ def process_with_mapping_item_array(value, mapping_item)
94
+ return [] if value.nil?
95
+ item_mapping = mapping_item[:item_mapping]
96
+ raise "Missing item mapping for array \"#{value}\"" if item_mapping.nil?
97
+ value.map do |item|
98
+ process_with_mapping_item(item, item_mapping)
99
+ end
100
+ end
101
+ module_function :process_with_mapping_item_array
102
+ end
103
+ end
@@ -0,0 +1,99 @@
1
+ require 'hash_op/deep_access'
2
+
3
+ # A set of functions to perform mathematical operations
4
+ # on Hashes.
5
+ module HashOp
6
+ module Math
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
+ # Sum values in an array of hashes by grouping on a given
35
+ # key.
36
+ #
37
+ # Example:
38
+ # hashes = [
39
+ # { group: :a, value: 1 },
40
+ # { group: :a, value: 1 },
41
+ # { group: :b, value: 1 }
42
+ # ]
43
+ # HashOp::Math.sum_on_groups(hashes, :group, :value)
44
+ # # => [
45
+ # # { group: :a, value: 2 },
46
+ # # { group: :b, value: 1 }
47
+ # # ]
48
+ #
49
+ # @param hashes [Array] the hashes to be summed
50
+ # @param group_key [Object] the key to use to group items on
51
+ # @param value_key [Object] the key of the values to sum
52
+ # @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]
62
+ end
63
+ work
64
+ end
65
+ work_hash.values
66
+ end
67
+ module_function :sum_on_groups
68
+
69
+ # Sum values for the specified hashes at the specified path.
70
+ # The values are added to the specified zero (defaults to numeric
71
+ # 0), and nil values will be coerced to the zero too.
72
+ #
73
+ # @param hashes [Array]
74
+ # @param path [String]
75
+ # @param zero [Object] defaults to [Numeric] 0
76
+ #
77
+ def sum_at_path(hashes, path, zero = 0)
78
+ hashes.inject(zero) do |sum, hash|
79
+ value = HashOp::DeepAccess.fetch(hash, path) || zero
80
+ sum + value
81
+ end
82
+ end
83
+ module_function :sum_at_path
84
+
85
+ # @param [Array] hashes array of Hash
86
+ # @param [String, Symbol] path to deep value in each hash
87
+ def deep_min(hashes, path)
88
+ hashes.map { |hash| HashOp::DeepAccess.fetch hash, path }.min
89
+ end
90
+ module_function :deep_min
91
+
92
+ # @param [Array] hashes array of Hash
93
+ # @param [String, Symbol] path to deep value in each hash
94
+ def deep_max(hashes, path)
95
+ hashes.map { |hash| HashOp::DeepAccess.fetch hash, path }.max
96
+ end
97
+ module_function :deep_max
98
+ end
99
+ end
@@ -0,0 +1,21 @@
1
+ module HashOp
2
+ module Merge
3
+
4
+ # Merge all specified hashes by merging the second
5
+ # in the first, the third in the result, and so on.
6
+ def merge(hashes)
7
+ hashes.inject({}) do |result, hash|
8
+ result.merge hash
9
+ end
10
+ end
11
+ module_function :merge
12
+
13
+ # Merge hashes by grouping them on the
14
+ # specified key value and merging them all together.
15
+ def merge_by_group(hashes, key)
16
+ groups = hashes.group_by { |h| h[key] }
17
+ groups.values.map { |g| merge(g) }
18
+ end
19
+ module_function :merge_by_group
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module HashOp
2
+ VERSION = "0.1.0"
3
+ end
data/lib/hash_op.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "hash_op/version"
2
+
3
+ module HashOp
4
+ end
5
+
6
+ dir = File.expand_path('../hash_op/*.rb', __FILE__)
7
+ Dir[dir].each do |f|
8
+ require f
9
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hash_op
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Romain Champourlier
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: awesome_print
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - pro@rchampourlier.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - CODE_OF_CONDUCT.md
108
+ - Gemfile
109
+ - Gemfile.lock
110
+ - README.md
111
+ - Rakefile
112
+ - bin/console
113
+ - bin/setup
114
+ - hash_op.gemspec
115
+ - lib/hash_op.rb
116
+ - lib/hash_op/deep_access.rb
117
+ - lib/hash_op/filter.rb
118
+ - lib/hash_op/grouping.rb
119
+ - lib/hash_op/mapping.rb
120
+ - lib/hash_op/math.rb
121
+ - lib/hash_op/merge.rb
122
+ - lib/hash_op/version.rb
123
+ homepage: https://github.com/rchampourlier/hash_op
124
+ licenses: []
125
+ metadata:
126
+ allowed_push_host: https://rubygems.org
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.4.6
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: A Ruby library of functions to access and manipulate hash data structures.
147
+ test_files: []