hash_math 0.0.1 → 1.0.0.pre.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +235 -1
- data/lib/hash_math.rb +5 -0
- data/lib/hash_math/matrix.rb +56 -0
- data/lib/hash_math/matrix/key_value_pair.rb +38 -0
- data/lib/hash_math/record.rb +57 -0
- data/lib/hash_math/table.rb +61 -0
- data/lib/hash_math/version.rb +1 -1
- data/spec/hash_math/matrix_spec.rb +57 -0
- data/spec/hash_math/record_spec.rb +48 -0
- data/spec/hash_math/table_spec.rb +91 -0
- metadata +14 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 298a64b54732cd96f7145ecba9b7e3a8e1424e4513609c75269cff8de9ebd039
|
4
|
+
data.tar.gz: 1fa6896667948c39b05169e232fd916d20a35ac6415e068195560a2552c2051d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7867d22fc5bd88a24adb3031706302621e5e3f2c8aae353cfe83e323fd8b7ba0d25afb86f1403c9e796899b90df6789a823a0139d833ed5ac87d899edef11d44
|
7
|
+
data.tar.gz: cc6703151537b76d44bd322e11a4b698e0829de8c250707611475b9959b7a30260c9aee328bedfea956db6142f339e57d5ca790294006378d4d65bd4f293b58f
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -2,4 +2,238 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/hash_math.svg)](https://badge.fury.io/rb/hash_math) [![Build Status](https://travis-ci.org/bluemarblepayroll/hash_math.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/hash_math) [![Maintainability](https://api.codeclimate.com/v1/badges/9f9a504b3f5df199a253/maintainability)](https://codeclimate.com/github/bluemarblepayroll/hash_math/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/9f9a504b3f5df199a253/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/hash_math/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
|
4
4
|
|
5
|
-
|
5
|
+
Ruby's Hash data structure is ubiquitous and highly flexible. This library contains common Hash patterns widely used within our code-base:
|
6
|
+
|
7
|
+
* Matrix: build a hash up then expand into hash products
|
8
|
+
* Record: define a list of keys and base value to use as a blueprint for hashes
|
9
|
+
* Table: key-value pair two-dimensional array of hash builder
|
10
|
+
|
11
|
+
See examples for more information on each data structure.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
To install through Rubygems:
|
16
|
+
|
17
|
+
````bash
|
18
|
+
gem install install hash_math
|
19
|
+
````
|
20
|
+
|
21
|
+
You can also add this to your Gemfile:
|
22
|
+
|
23
|
+
````bash
|
24
|
+
bundle add hash_math
|
25
|
+
````
|
26
|
+
|
27
|
+
## Examples
|
28
|
+
|
29
|
+
### Matrix: The Hash Combination Calculator
|
30
|
+
|
31
|
+
HashMath::Matrix is a key-value builder that outputs all the different hash combinations.
|
32
|
+
|
33
|
+
Say we have this type of matrix:
|
34
|
+
|
35
|
+
````ruby
|
36
|
+
{
|
37
|
+
a: [1,2,3],
|
38
|
+
b: [4,5,6],
|
39
|
+
c: [7,8,9]
|
40
|
+
}
|
41
|
+
````
|
42
|
+
|
43
|
+
We could code this as:
|
44
|
+
|
45
|
+
````ruby
|
46
|
+
matrix = HashMath::Matrix.new
|
47
|
+
matrix.add_each(:a, [1,2,3])
|
48
|
+
matrix.add_each(:b, [4,5,6])
|
49
|
+
matrix.add_each(:c, [7,8,9])
|
50
|
+
````
|
51
|
+
|
52
|
+
Note: you can also call `Matrix#add` to only add a single value instead of add_each.
|
53
|
+
|
54
|
+
and get the combinations by calling to_a:
|
55
|
+
|
56
|
+
````ruby
|
57
|
+
combinations = matrix.to_a
|
58
|
+
````
|
59
|
+
|
60
|
+
which would yield the following hashes:
|
61
|
+
|
62
|
+
````ruby
|
63
|
+
[
|
64
|
+
{ a: 1, b: 4, c: 7 },
|
65
|
+
{ a: 1, b: 4, c: 8 },
|
66
|
+
{ a: 1, b: 4, c: 9 },
|
67
|
+
{ a: 1, b: 5, c: 7 },
|
68
|
+
{ a: 1, b: 5, c: 8 },
|
69
|
+
{ a: 1, b: 5, c: 9 },
|
70
|
+
{ a: 1, b: 6, c: 7 },
|
71
|
+
{ a: 1, b: 6, c: 8 },
|
72
|
+
{ a: 1, b: 6, c: 9 },
|
73
|
+
|
74
|
+
{ a: 2, b: 4, c: 7 },
|
75
|
+
{ a: 2, b: 4, c: 8 },
|
76
|
+
{ a: 2, b: 4, c: 9 },
|
77
|
+
{ a: 2, b: 5, c: 7 },
|
78
|
+
{ a: 2, b: 5, c: 8 },
|
79
|
+
{ a: 2, b: 5, c: 9 },
|
80
|
+
{ a: 2, b: 6, c: 7 },
|
81
|
+
{ a: 2, b: 6, c: 8 },
|
82
|
+
{ a: 2, b: 6, c: 9 },
|
83
|
+
|
84
|
+
{ a: 3, b: 4, c: 7 },
|
85
|
+
{ a: 3, b: 4, c: 8 },
|
86
|
+
{ a: 3, b: 4, c: 9 },
|
87
|
+
{ a: 3, b: 5, c: 7 },
|
88
|
+
{ a: 3, b: 5, c: 8 },
|
89
|
+
{ a: 3, b: 5, c: 9 },
|
90
|
+
{ a: 3, b: 6, c: 7 },
|
91
|
+
{ a: 3, b: 6, c: 8 },
|
92
|
+
{ a: 3, b: 6, c: 9 },
|
93
|
+
]
|
94
|
+
````
|
95
|
+
|
96
|
+
Notes:
|
97
|
+
|
98
|
+
* Matrix implements Ruby's Enumerable, which means you have the ability to iterate over each hash combination like you would any other Enumerable object.
|
99
|
+
* Values can be arrays and it still works. For example, `#add` (singular form) will honor the array data type: `matrix.add(:a, [1,2,3])` is **not** the same as: `matrix.add_each(:a, [1,2,3])` but it **is** the same as: `matrix.add(:a, [[1,2,3]])`
|
100
|
+
* Keys are type-sensitive and work just like Hash keys work.
|
101
|
+
|
102
|
+
### Record: The Hash Prototype
|
103
|
+
|
104
|
+
HashMath::Record generates a prototype hash and ensures all derived hashes conform to its shape.
|
105
|
+
|
106
|
+
Say we have a person hash, for example:
|
107
|
+
|
108
|
+
````ruby
|
109
|
+
{
|
110
|
+
id: 1,
|
111
|
+
name: 'Matt',
|
112
|
+
location: 'Chicago'
|
113
|
+
}
|
114
|
+
````
|
115
|
+
|
116
|
+
we could create a record modeling this:
|
117
|
+
|
118
|
+
````ruby
|
119
|
+
record = HashMath::Record.new(%i[id name location], 'UNKNOWN')
|
120
|
+
````
|
121
|
+
|
122
|
+
Then, we could shape all other hashes using it to ensure each key is populated. If a key is not populated, the base value will be used ('UNKNOWN' in our case.) For example:
|
123
|
+
|
124
|
+
````ruby
|
125
|
+
records = [
|
126
|
+
record.make(id: 1, name: 'Matt', location: 'Chicago', dob: nil),
|
127
|
+
record.make(id: 2, age: 24),
|
128
|
+
record.make(id: 3, location: 'Los Angeles')
|
129
|
+
]
|
130
|
+
````
|
131
|
+
|
132
|
+
Note: The keys `dob` and `age` appeared in the input but will be ignored as they do not conform to the Record. You could make this strict and raise an error by calling `#make!` instead of `#make`.
|
133
|
+
|
134
|
+
our `records` would now equate to:
|
135
|
+
|
136
|
+
````ruby
|
137
|
+
[
|
138
|
+
{ id: 1, name: 'Matt', location: 'Chicago' },
|
139
|
+
{ id: 2, name: 'UNKNOWN', location: 'UNKNOWN' },
|
140
|
+
{ id: 3, name: 'UNKNOWN', location: 'Los Angeles' },
|
141
|
+
]
|
142
|
+
````
|
143
|
+
|
144
|
+
Notes:
|
145
|
+
|
146
|
+
* keys are type-sensitive and works just like Hash keys work.
|
147
|
+
|
148
|
+
### Table: The Double Hash (Hash of Hashes)
|
149
|
+
|
150
|
+
HashMath::Table builds on top of HashMath::Record. It constructs a table data-structure using a key-value pair builder method: `#add(row_id, field_id, key, value)`. It ultimately outputs an array of Row objects where each Row object has a row_id and fields (field_id => value) hash. The value proposition here is you can iterate over a key-value pair and construct each row any way you wish.
|
151
|
+
|
152
|
+
Building on our Record example above:
|
153
|
+
|
154
|
+
````ruby
|
155
|
+
record = HashMath::Record.new(%i[name location], 'UNKNOWN')
|
156
|
+
|
157
|
+
table = HashMath::Table.new(record)
|
158
|
+
.add(1, :name, 'Matt')
|
159
|
+
.add(1, :location, 'Chicago')
|
160
|
+
.add(2, :name, 'Nick')
|
161
|
+
.add(3, :location, 'Los Angeles')
|
162
|
+
.add(2, :name 'Nicholas') # notice the secondary call to "2, :name"
|
163
|
+
|
164
|
+
rows = table.to_a
|
165
|
+
````
|
166
|
+
|
167
|
+
which would set our variable `rows` to:
|
168
|
+
|
169
|
+
````ruby
|
170
|
+
[
|
171
|
+
HashMath::Row.new(1, { name: 'Matt', location: 'Chicago' }),
|
172
|
+
HashMath::Row.new(2, { name: 'Nicholas', location: 'UNKNOWN' }),
|
173
|
+
HashMath::Row.new(3, { name: 'UNKNOWN', location: 'Los Angeles' })
|
174
|
+
]
|
175
|
+
````
|
176
|
+
|
177
|
+
Notes:
|
178
|
+
|
179
|
+
* `#add` will throw a KeyOutOfBoundsError if the key is not found.
|
180
|
+
* key is type-sensitive and works just like Hash keys work.
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
|
184
|
+
### Development Environment Configuration
|
185
|
+
|
186
|
+
Basic steps to take to get this repository compiling:
|
187
|
+
|
188
|
+
1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check hash_math.gemspec for versions supported)
|
189
|
+
2. Install bundler (gem install bundler)
|
190
|
+
3. Clone the repository (git clone git@github.com:bluemarblepayroll/hash_math.git)
|
191
|
+
4. Navigate to the root folder (cd hash_math)
|
192
|
+
5. Install dependencies (bundle)
|
193
|
+
|
194
|
+
### Running Tests
|
195
|
+
|
196
|
+
To execute the test suite and code-coverage tool, run:
|
197
|
+
|
198
|
+
````bash
|
199
|
+
bundle exec rspec spec --format documentation
|
200
|
+
````
|
201
|
+
|
202
|
+
Alternatively, you can have Guard watch for changes:
|
203
|
+
|
204
|
+
````bash
|
205
|
+
bundle exec guard
|
206
|
+
````
|
207
|
+
|
208
|
+
Also, do not forget to run Rubocop:
|
209
|
+
|
210
|
+
````bash
|
211
|
+
bundle exec rubocop
|
212
|
+
````
|
213
|
+
|
214
|
+
or run all three in one command:
|
215
|
+
|
216
|
+
````bash
|
217
|
+
bundle exec rake
|
218
|
+
````
|
219
|
+
|
220
|
+
### Publishing
|
221
|
+
|
222
|
+
Note: ensure you have proper authorization before trying to publish new versions.
|
223
|
+
|
224
|
+
After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
|
225
|
+
|
226
|
+
1. Merge Pull Request into master
|
227
|
+
2. Update `lib/hash_math/version.rb` using [semantic versioning](https://semver.org/)
|
228
|
+
3. Install dependencies: `bundle`
|
229
|
+
4. Update `CHANGELOG.md` with release notes
|
230
|
+
5. Commit & push master to remote and ensure CI builds master successfully
|
231
|
+
6. 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).
|
232
|
+
|
233
|
+
## Code of Conduct
|
234
|
+
|
235
|
+
Everyone interacting in this codebase, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bluemarblepayroll/hash_math/blob/master/CODE_OF_CONDUCT.md).
|
236
|
+
|
237
|
+
## License
|
238
|
+
|
239
|
+
This project is MIT Licensed.
|
data/lib/hash_math.rb
CHANGED
@@ -7,6 +7,11 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
+
require_relative 'hash_math/matrix'
|
11
|
+
require_relative 'hash_math/record'
|
12
|
+
require_relative 'hash_math/table'
|
13
|
+
|
10
14
|
# Top-level namespace
|
11
15
|
module HashMath
|
16
|
+
class KeyOutOfBoundsError < StandardError; end
|
12
17
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'matrix/key_value_pair'
|
11
|
+
|
12
|
+
module HashMath
|
13
|
+
# A Matrix allows you to build up a hash of key and values, then it will generate the
|
14
|
+
# product of all values.
|
15
|
+
class Matrix
|
16
|
+
extend Forwardable
|
17
|
+
include Enumerable
|
18
|
+
|
19
|
+
def_delegators :pair_products, :each
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@pairs_by_key = {}
|
23
|
+
|
24
|
+
freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_each(key, vals)
|
28
|
+
tap { kvp(key).add_each(vals) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(key, val)
|
32
|
+
tap { kvp(key).add(val) }
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :pairs_by_key
|
38
|
+
|
39
|
+
def kvp(key)
|
40
|
+
pairs_by_key[key] ||= KeyValuePair.new(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def pair_products
|
44
|
+
pair_groups = pairs_by_key.values.map(&:pairs)
|
45
|
+
|
46
|
+
products = pair_groups.inject(pair_groups.shift) { |memo, f| memo.product(f) }
|
47
|
+
&.map { |f| f.is_a?(KeyValuePair::Pair) ? [f] : f.flatten } || []
|
48
|
+
|
49
|
+
products.map { |pairs| recombine(pairs) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def recombine(pairs)
|
53
|
+
pairs.each_with_object({}) { |p, memo| memo[p.key] = p.value }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module HashMath
|
11
|
+
class Matrix
|
12
|
+
# A hash-like structure that allows you to gradually build up keys.
|
13
|
+
class KeyValuePair
|
14
|
+
Pair = Struct.new(:key, :value)
|
15
|
+
|
16
|
+
attr_reader :key, :value
|
17
|
+
|
18
|
+
def initialize(key)
|
19
|
+
@key = key
|
20
|
+
@value = Set.new
|
21
|
+
|
22
|
+
freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_each(vals)
|
26
|
+
tap { vals.each { |val| add(val) } }
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(val)
|
30
|
+
tap { value << val }
|
31
|
+
end
|
32
|
+
|
33
|
+
def pairs
|
34
|
+
value.map { |value| Pair.new(key, value) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module HashMath
|
11
|
+
# A Record serves as a prototype for a Hash. It will allow the output of hashes
|
12
|
+
# conforming to a strict (#make!) or non-strict (#make) shape.
|
13
|
+
class Record
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegators :prototype, :key?, :keys
|
17
|
+
|
18
|
+
def initialize(keys = [], base_value = nil)
|
19
|
+
@prototype = keys.map { |key| [key, base_value] }.to_h
|
20
|
+
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def make!(hash = {})
|
25
|
+
make(hash, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def make(hash = {}, bound = false)
|
29
|
+
hash.each_with_object(shallow_copy_prototype) do |(key, value), memo|
|
30
|
+
next unless assert_key_in_bounds(key, bound)
|
31
|
+
|
32
|
+
memo[key] = value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :prototype
|
39
|
+
|
40
|
+
# raise error if key is not in key set and bound is true
|
41
|
+
# return true if key is in key set and bound is false
|
42
|
+
# return false if key is not in key set and bound is false
|
43
|
+
def assert_key_in_bounds(key, bound)
|
44
|
+
raise KeyOutOfBoundsError, "[#{key}] for: #{keys}" if not_key?(key) && bound
|
45
|
+
|
46
|
+
key?(key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def not_key?(key)
|
50
|
+
!key?(key)
|
51
|
+
end
|
52
|
+
|
53
|
+
def shallow_copy_prototype
|
54
|
+
{}.merge(prototype)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module HashMath
|
11
|
+
# The main data structure for a virtual table that can be treated as a key-value builder.
|
12
|
+
# Basically, it is a hash with a default 'prototype' assigned to it, which serves as the
|
13
|
+
# base record. Then, #add is called over and over passing in row_id, field_id, and value,
|
14
|
+
# which gives it enough information to pinpoint where to insert the data (memory-wise.)
|
15
|
+
# Imagine a two-dimensional table where X is the field_id axis and row is the Y axis.
|
16
|
+
# Since it is essentially backed by a hash, the row_id and field_id can be anything that
|
17
|
+
# implements #hash, #eql? and #== properly.
|
18
|
+
class Table
|
19
|
+
extend Forwardable
|
20
|
+
include Enumerable
|
21
|
+
|
22
|
+
Row = Struct.new(:row_id, :fields)
|
23
|
+
|
24
|
+
attr_reader :lookup, :record
|
25
|
+
|
26
|
+
def_delegators :record, :keys, :key?
|
27
|
+
|
28
|
+
def initialize(record)
|
29
|
+
raise ArgumentError, 'record is required' unless record
|
30
|
+
|
31
|
+
@lookup = {}
|
32
|
+
@record = record
|
33
|
+
|
34
|
+
freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
def add(row_id, field_id, value)
|
38
|
+
raise KeyOutOfBoundsError, "field_id: #{field_id} not allowed." unless key?(field_id)
|
39
|
+
|
40
|
+
tap { set(row_id, field_id, value) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def each
|
44
|
+
return enum_for(:each) unless block_given?
|
45
|
+
|
46
|
+
lookup.map do |row_id, fields|
|
47
|
+
Row.new(row_id, record.make!(fields)).tap { |row| yield(row) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def row(row_id)
|
54
|
+
lookup[row_id] ||= {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def set(row_id, field_id, value)
|
58
|
+
row(row_id)[field_id] = value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/hash_math/version.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'spec_helper'
|
11
|
+
|
12
|
+
describe HashMath::Matrix do
|
13
|
+
let(:examples) do
|
14
|
+
{
|
15
|
+
{} => [],
|
16
|
+
{ a: :b } => [
|
17
|
+
{ a: :b }
|
18
|
+
],
|
19
|
+
{ a: 'a1', b: 'b1', c: 'c1' } => [
|
20
|
+
{ a: 'a1', b: 'b1', c: 'c1' }
|
21
|
+
],
|
22
|
+
{ a: %w[a1 a2], b: 'b1', c: 'c1' } => [
|
23
|
+
{ a: 'a1', b: 'b1', c: 'c1' },
|
24
|
+
{ a: 'a2', b: 'b1', c: 'c1' }
|
25
|
+
],
|
26
|
+
{ a: %w[a1 a2], b: %w[b1 b2], c: 'c1' } => [
|
27
|
+
{ a: 'a1', b: 'b1', c: 'c1' },
|
28
|
+
{ a: 'a1', b: 'b2', c: 'c1' },
|
29
|
+
{ a: 'a2', b: 'b1', c: 'c1' },
|
30
|
+
{ a: 'a2', b: 'b2', c: 'c1' }
|
31
|
+
],
|
32
|
+
{ a: %w[a1 a2], b: %w[b1 b2], c: %w[c1 c2] } => [
|
33
|
+
{ a: 'a1', b: 'b1', c: 'c1' },
|
34
|
+
{ a: 'a1', b: 'b1', c: 'c2' },
|
35
|
+
{ a: 'a1', b: 'b2', c: 'c1' },
|
36
|
+
{ a: 'a1', b: 'b2', c: 'c2' },
|
37
|
+
|
38
|
+
{ a: 'a2', b: 'b1', c: 'c1' },
|
39
|
+
{ a: 'a2', b: 'b1', c: 'c2' },
|
40
|
+
{ a: 'a2', b: 'b2', c: 'c1' },
|
41
|
+
{ a: 'a2', b: 'b2', c: 'c2' }
|
42
|
+
]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
specify '#produce generates correct matrix-expanded hashes' do
|
47
|
+
examples.each_pair do |hash, expanded_hashes|
|
48
|
+
subject = described_class.new
|
49
|
+
|
50
|
+
hash.each_pair do |k, v|
|
51
|
+
v.is_a?(Array) ? subject.add_each(k, v) : subject.add(k, v)
|
52
|
+
end
|
53
|
+
|
54
|
+
expect(subject.to_a).to eq(expanded_hashes)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'spec_helper'
|
11
|
+
|
12
|
+
describe HashMath::Record do
|
13
|
+
let(:base_value) { '*' }
|
14
|
+
|
15
|
+
let(:examples) do
|
16
|
+
{
|
17
|
+
{ a: :b } => { a: :b, c: base_value, 'a' => base_value, 'c' => base_value },
|
18
|
+
{ c: :d } => { a: base_value, c: :d, 'a' => base_value, 'c' => base_value },
|
19
|
+
{ 'a' => :b } => { a: base_value, c: base_value, 'a' => :b, 'c' => base_value },
|
20
|
+
{ 'c' => :d } => { a: base_value, c: base_value, 'a' => base_value, 'c' => :d }
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
subject do
|
25
|
+
described_class.new(examples.keys.map(&:keys).flatten, base_value)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#initialize' do
|
29
|
+
it 'derives prototype' do
|
30
|
+
expected = {
|
31
|
+
a: base_value,
|
32
|
+
c: base_value,
|
33
|
+
'a' => base_value,
|
34
|
+
'c' => base_value
|
35
|
+
}
|
36
|
+
|
37
|
+
expect(subject.make).to eq(expected)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#make' do
|
42
|
+
it 'generates correct hashes' do
|
43
|
+
examples.each_pair do |hash, normalized_hash|
|
44
|
+
expect(subject.make(hash)).to eq(normalized_hash)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'spec_helper'
|
11
|
+
|
12
|
+
describe HashMath::Table do
|
13
|
+
RowId = Struct.new(:id1, :id2)
|
14
|
+
FieldId = Struct.new(:name1, :name2)
|
15
|
+
|
16
|
+
let(:base_value) { 'NOT_ADDED' }
|
17
|
+
|
18
|
+
context 'with simple row and field id types' do
|
19
|
+
let(:prototype) do
|
20
|
+
HashMath::Record.new(%i[name age location], base_value)
|
21
|
+
end
|
22
|
+
|
23
|
+
subject { described_class.new(prototype) }
|
24
|
+
|
25
|
+
it 'works for integer row_id and symbol field_id' do
|
26
|
+
subject.add(1, :name, 'matt')
|
27
|
+
subject.add(2, :age, 990)
|
28
|
+
subject.add(3, :location, 'earth')
|
29
|
+
|
30
|
+
rows = subject.to_a
|
31
|
+
|
32
|
+
expected = [
|
33
|
+
described_class::Row.new(1, name: 'matt', age: base_value, location: base_value),
|
34
|
+
described_class::Row.new(2, name: base_value, age: 990, location: base_value),
|
35
|
+
described_class::Row.new(3, name: base_value, age: base_value, location: 'earth')
|
36
|
+
]
|
37
|
+
|
38
|
+
expect(rows).to eq(expected)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises KeyOutOfBoundsError if field_id is not defined in the record' do
|
42
|
+
expect { subject.add(4, 'name', '') }.to raise_error(HashMath::KeyOutOfBoundsError)
|
43
|
+
expect { subject.add(4, :something_else, '') }.to raise_error(HashMath::KeyOutOfBoundsError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with more complex object subclass row and field id types' do
|
48
|
+
let(:prototype) do
|
49
|
+
HashMath::Record.new(
|
50
|
+
[
|
51
|
+
FieldId.new(:name, :first),
|
52
|
+
FieldId.new(:address, :st1)
|
53
|
+
],
|
54
|
+
base_value
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
subject { described_class.new(prototype) }
|
59
|
+
|
60
|
+
it 'works for more complex row_id and field_id' do
|
61
|
+
subject.add(RowId.new(1, 2), FieldId.new(:name, :first), 'matt')
|
62
|
+
subject.add(RowId.new(3, 4), FieldId.new(:name, :first), 'nick')
|
63
|
+
subject.add(RowId.new(5, 6), FieldId.new(:name, :first), 'sam')
|
64
|
+
|
65
|
+
subject.add(RowId.new(1, 2), FieldId.new(:address, :st1), 'mag mile')
|
66
|
+
subject.add(RowId.new(3, 4), FieldId.new(:address, :st1), 'saturn ln.')
|
67
|
+
|
68
|
+
rows = subject.to_a
|
69
|
+
|
70
|
+
expected = [
|
71
|
+
described_class::Row.new(
|
72
|
+
RowId.new(1, 2),
|
73
|
+
FieldId.new(:name, :first) => 'matt',
|
74
|
+
FieldId.new(:address, :st1) => 'mag mile'
|
75
|
+
),
|
76
|
+
described_class::Row.new(
|
77
|
+
RowId.new(3, 4),
|
78
|
+
FieldId.new(:name, :first) => 'nick',
|
79
|
+
FieldId.new(:address, :st1) => 'saturn ln.'
|
80
|
+
),
|
81
|
+
described_class::Row.new(
|
82
|
+
RowId.new(5, 6),
|
83
|
+
FieldId.new(:name, :first) => 'sam',
|
84
|
+
FieldId.new(:address, :st1) => base_value
|
85
|
+
)
|
86
|
+
]
|
87
|
+
|
88
|
+
expect(rows).to eq(expected)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_math
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 1.0.0.pre.alpha
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Ruggio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: guard-rspec
|
@@ -132,7 +132,14 @@ files:
|
|
132
132
|
- bin/console
|
133
133
|
- hash_math.gemspec
|
134
134
|
- lib/hash_math.rb
|
135
|
+
- lib/hash_math/matrix.rb
|
136
|
+
- lib/hash_math/matrix/key_value_pair.rb
|
137
|
+
- lib/hash_math/record.rb
|
138
|
+
- lib/hash_math/table.rb
|
135
139
|
- lib/hash_math/version.rb
|
140
|
+
- spec/hash_math/matrix_spec.rb
|
141
|
+
- spec/hash_math/record_spec.rb
|
142
|
+
- spec/hash_math/table_spec.rb
|
136
143
|
- spec/spec_helper.rb
|
137
144
|
homepage: https://github.com/bluemarblepayroll/hash_math
|
138
145
|
licenses:
|
@@ -149,13 +156,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
156
|
version: 2.3.8
|
150
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
158
|
requirements:
|
152
|
-
- - "
|
159
|
+
- - ">"
|
153
160
|
- !ruby/object:Gem::Version
|
154
|
-
version:
|
161
|
+
version: 1.3.1
|
155
162
|
requirements: []
|
156
163
|
rubygems_version: 3.0.3
|
157
164
|
signing_key:
|
158
165
|
specification_version: 4
|
159
166
|
summary: Hash-based data structures and algorithms
|
160
167
|
test_files:
|
168
|
+
- spec/hash_math/matrix_spec.rb
|
169
|
+
- spec/hash_math/record_spec.rb
|
170
|
+
- spec/hash_math/table_spec.rb
|
161
171
|
- spec/spec_helper.rb
|