namo 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG +9 -0
- data/README.md +52 -0
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +14 -0
- data/test/Namo_test.rb +86 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0dbfe699c8bbb334e0f72bbfd1859b0eee910be2c72eaab988da633b58b438e6
|
|
4
|
+
data.tar.gz: 40238ef4593d7820bddfb6cb708823fa9c269a51def947e89cdb43e4b69cbae0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e6dfd0bcf4b8d370373a43e507a4d208e4ccb4478e044eb138bf5f2bfa61612cbb4f10735be691c852d2f8bf672eca70720cc6a5c35cd5fb02fae224388e010
|
|
7
|
+
data.tar.gz: 2b147c6e7faf7b9f92e8e2457fcd657734a35946c326857e3b7c09177ceaa7f3ce4f0892bdcbb68ec251d673e1b298c1ed06416fe43918656734a58bd0c639ea
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
_________
|
|
3
3
|
|
|
4
|
+
20260415
|
|
5
|
+
0.4.0: + concatenation (+) and row removal (-)
|
|
6
|
+
|
|
7
|
+
1. + Namo#+: Concatenate two Namo instances with matching dimensions. Appends rows from the second to the first. Raises ArgumentError when dimensions differ.
|
|
8
|
+
2. + Namo#-: Remove rows from the first Namo that appear exactly in the second. Whole-row set difference. Raises ArgumentError when dimensions differ.
|
|
9
|
+
3. ~ Namo_test.rb: Add tests for #+ and #-.
|
|
10
|
+
4. ~ README.md: + Concatenation section, + Row removal section.
|
|
11
|
+
5. ~ Namo::VERSION: /0.3.0/0.4.0/
|
|
12
|
+
|
|
4
13
|
20260415
|
|
5
14
|
0.3.0: + contraction
|
|
6
15
|
|
data/README.md
CHANGED
|
@@ -153,6 +153,58 @@ sales[-:price, -:quantity, product: 'Widget']
|
|
|
153
153
|
|
|
154
154
|
Selection, projection, and contraction always return a new Namo instance, so everything chains.
|
|
155
155
|
|
|
156
|
+
### Concatenation
|
|
157
|
+
|
|
158
|
+
`+` combines two Namo objects that share the same dimensions by appending the rows of the second to the first:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
q1_sales = Namo.new([
|
|
162
|
+
{product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100},
|
|
163
|
+
{product: 'Gadget', quarter: 'Q1', price: 25.0, quantity: 40}
|
|
164
|
+
])
|
|
165
|
+
|
|
166
|
+
q2_sales = Namo.new([
|
|
167
|
+
{product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
|
|
168
|
+
{product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
|
|
169
|
+
])
|
|
170
|
+
|
|
171
|
+
all_sales = q1_sales + q2_sales
|
|
172
|
+
# => #<Namo [
|
|
173
|
+
# {product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100},
|
|
174
|
+
# {product: 'Gadget', quarter: 'Q1', price: 25.0, quantity: 40},
|
|
175
|
+
# {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
|
|
176
|
+
# {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
|
|
177
|
+
# ]>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The dimensions must match — concatenating Namo objects with different dimensions raises an `ArgumentError`. Formulae carry through from the left-hand side.
|
|
181
|
+
|
|
182
|
+
### Row Removal
|
|
183
|
+
|
|
184
|
+
`-` removes from the first Namo any row that appears exactly in the second:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
sales = Namo.new([
|
|
188
|
+
{product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100},
|
|
189
|
+
{product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
|
|
190
|
+
{product: 'Gadget', quarter: 'Q1', price: 25.0, quantity: 40},
|
|
191
|
+
{product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
|
|
192
|
+
])
|
|
193
|
+
|
|
194
|
+
discontinued = Namo.new([
|
|
195
|
+
{product: 'Gadget', quarter: 'Q1', price: 25.0, quantity: 40},
|
|
196
|
+
{product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
|
|
197
|
+
])
|
|
198
|
+
|
|
199
|
+
sales - discontinued
|
|
200
|
+
# => #<Namo [
|
|
201
|
+
# {product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100},
|
|
202
|
+
# {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150}
|
|
203
|
+
# ]>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Removal is exact — every dimension, every value must match. The dimensions must match; different dimensions raise an `ArgumentError`. Formulae carry through from the left-hand side.
|
|
207
|
+
|
|
156
208
|
### Formulae
|
|
157
209
|
|
|
158
210
|
Define computed dimensions using `[]=`:
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -56,6 +56,20 @@ class Namo
|
|
|
56
56
|
@data.each{|row_data| block.call(Row.new(row_data, @formulae))}
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
def +(other)
|
|
60
|
+
unless dimensions == other.dimensions
|
|
61
|
+
raise ArgumentError, "dimensions do not match"
|
|
62
|
+
end
|
|
63
|
+
self.class.new(@data + other.data, formulae: other.formulae.merge(@formulae))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def -(other)
|
|
67
|
+
unless dimensions == other.dimensions
|
|
68
|
+
raise ArgumentError, "dimensions do not match"
|
|
69
|
+
end
|
|
70
|
+
self.class.new(@data - other.data, formulae: @formulae.dup)
|
|
71
|
+
end
|
|
72
|
+
|
|
59
73
|
def to_a
|
|
60
74
|
@data.map do |row|
|
|
61
75
|
row.keys.each_with_object({}) do |key, hash|
|
data/test/Namo_test.rb
CHANGED
|
@@ -251,6 +251,92 @@ describe Namo do
|
|
|
251
251
|
end
|
|
252
252
|
end
|
|
253
253
|
|
|
254
|
+
describe "#+" do
|
|
255
|
+
let(:more_data) do
|
|
256
|
+
[
|
|
257
|
+
{product: 'Widget', quarter: 'Q3', price: 10.0, quantity: 200},
|
|
258
|
+
{product: 'Gadget', quarter: 'Q3', price: 25.0, quantity: 80}
|
|
259
|
+
]
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
let(:more_sales) do
|
|
263
|
+
Namo.new(more_data)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "concatenates rows" do
|
|
267
|
+
result = sales + more_sales
|
|
268
|
+
_(result.to_a.count).must_equal 6
|
|
269
|
+
_(result.to_a).must_equal(sample_data + more_data)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "preserves dimensions" do
|
|
273
|
+
result = sales + more_sales
|
|
274
|
+
_(result.dimensions).must_equal [:product, :quarter, :price, :quantity]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it "carries formulae through from self" do
|
|
278
|
+
sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
279
|
+
result = sales + more_sales
|
|
280
|
+
_(result.map{|row| row[:revenue]}).must_equal [1000.0, 1500.0, 1000.0, 1500.0, 2000.0, 2000.0]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "merges formulae from other" do
|
|
284
|
+
more_sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
285
|
+
result = sales + more_sales
|
|
286
|
+
_(result.map{|row| row[:revenue]}).must_equal [1000.0, 1500.0, 1000.0, 1500.0, 2000.0, 2000.0]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "prefers self's formulae on conflict" do
|
|
290
|
+
sales[:label] = proc{|r| "self: #{r[:product]}"}
|
|
291
|
+
more_sales[:label] = proc{|r| "other: #{r[:product]}"}
|
|
292
|
+
result = sales + more_sales
|
|
293
|
+
_(result.map{|row| row[:label]}).must_equal [
|
|
294
|
+
'self: Widget', 'self: Widget', 'self: Gadget', 'self: Gadget',
|
|
295
|
+
'self: Widget', 'self: Gadget'
|
|
296
|
+
]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it "raises when dimensions differ" do
|
|
300
|
+
other = Namo.new([{product: 'Widget', quarter: 'Q1'}])
|
|
301
|
+
_ { sales + other }.must_raise ArgumentError
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
describe "#-" do
|
|
306
|
+
let(:to_remove) do
|
|
307
|
+
Namo.new([
|
|
308
|
+
{product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100},
|
|
309
|
+
{product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
|
|
310
|
+
])
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "removes matching rows" do
|
|
314
|
+
result = sales - to_remove
|
|
315
|
+
_(result.to_a.count).must_equal 2
|
|
316
|
+
_(result.to_a).must_equal [
|
|
317
|
+
{product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
|
|
318
|
+
{product: 'Gadget', quarter: 'Q1', price: 25.0, quantity: 40}
|
|
319
|
+
]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it "preserves non-matching rows" do
|
|
323
|
+
other = Namo.new([{product: 'Thingo', quarter: 'Q4', price: 99.0, quantity: 1}])
|
|
324
|
+
result = sales - other
|
|
325
|
+
_(result.to_a).must_equal sample_data
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it "carries formulae through from self" do
|
|
329
|
+
sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
330
|
+
result = sales - to_remove
|
|
331
|
+
_(result.map{|row| row[:revenue]}).must_equal [1500.0, 1000.0]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it "raises when dimensions differ" do
|
|
335
|
+
other = Namo.new([{product: 'Widget', quarter: 'Q1'}])
|
|
336
|
+
_ { sales - other }.must_raise ArgumentError
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
254
340
|
describe "#to_a" do
|
|
255
341
|
it "returns the data as an array of hashes" do
|
|
256
342
|
_(sales.to_a).must_equal sample_data
|