hash_math 0.0.1 → 1.0.0.pre.alpha
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 +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
|
[](https://badge.fury.io/rb/hash_math) [](https://travis-ci.org/bluemarblepayroll/hash_math) [](https://codeclimate.com/github/bluemarblepayroll/hash_math/maintainability) [](https://codeclimate.com/github/bluemarblepayroll/hash_math/test_coverage) [](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
|