namo 0.18.0 → 0.19.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
  SHA256:
3
- metadata.gz: 1ed38a77d14075cbe3c28ff53097d539c2fefe4a91ff515e165e02c280311fd3
4
- data.tar.gz: ba009835a59174f2e60327f3583753d2e6c2b0e81b06f23de4a15dd792981526
3
+ metadata.gz: 35ca3cdc2c06b2da96ad636fa5ac1a593204a335ea93ff3994fc8bf187629aa5
4
+ data.tar.gz: 29639054075323f96fd0b6fe76bd733265ea155949965ec552c13ea30995efb8
5
5
  SHA512:
6
- metadata.gz: 7328696a6b4032561fa1b68c0eb4d3e7d3582ee4d5fc24926779112413867f1713100d9c21a3aa2f18c299e50802c866b5c95e874e569f4ef41f40e7b3924f58
7
- data.tar.gz: 8028776be37f1f183ca2cefc3c9de7229d6918cf37d05b5f925675dd2ffd7d05603cc21ba53665f74094991b0c8d08f22479de2f50ee35e36ba772777bdbfb6e
6
+ metadata.gz: 8e0be2ae49cd9cc493e580174b3edf8ac874592b45bd3de08ab1b5db3e7a83daa6d6f9c8efb97089b1653d61a92248a0ec7de60c7ab26beef98c0e9b3fab7a0f
7
+ data.tar.gz: 49a00bf247a8e286ee45e8619253d36ae8f77d2def369d377d5c46fa8c9ebad734513333d23472d9b8c074800f49a42292458505ad34281466079a73096e33aa
data/CHANGELOG CHANGED
@@ -1,6 +1,23 @@
1
1
  CHANGELOG
2
2
  _________
3
3
 
4
+ 20260614
5
+ 0.19.0: ~ row-multiset equality — replace the sorted canonical form with a row multiset, so ==, eql?, hash, and the subset operators compare without sorting: nil/NaN-safe, row- and dimension-order blind, and consistently type-strict.
6
+
7
+ 1. ~ lib/namo.rb: replace canonical_data (which sorted @data by row.values_at(*data_dimensions.sort))
8
+ with row_multiset (@data.tally). ==, eql?, hash, and subset_of_rows? now compare row
9
+ multisets keyed by Hash#hash/#eql?. The sort was only ever a mechanism for reaching the
10
+ already-decided multiset semantics (0.6.0); a multiset reaches them directly and needs no
11
+ ordering, so the latent ArgumentError when a dimension mixed nil (or NaN) with present
12
+ values — neither has a <=> against the other — is gone. The relation is now uniformly
13
+ eql?-based, so == agrees with the subset operators, which already tallied: {a: 1} and
14
+ {a: 1.0} are consistently distinct (== previously called them equal via Hash#==, while <=
15
+ called them distinct).
16
+ 2. ~ test/namo_test.rb: + four "#==" cases — equality across row order and across dimension
17
+ (key) order when a dimension mixes nil and non-nil values; inequality when such rows
18
+ differ; and type-strictness on values, consistent with <=.
19
+ 3. ~ Namo::VERSION: /0.18.0/0.19.0/
20
+
4
21
  20260613
5
22
  0.18.0: + Namo::Collection — hierarchical aggregate of named Namos with summary/detail views.
6
23
 
data/README.md CHANGED
@@ -1006,7 +1006,7 @@ gt.as_detail(:assembly) # gt's data becomes the detail; retur
1006
1006
  - If `by` is **already a dimension** in a member's rows, the row passes through untouched — the dimension is intrinsic.
1007
1007
  - If `by` is **not** present, `detail` injects it (`row.merge(by => member.name)`), promoting the member's name into a dimension.
1008
1008
 
1009
- This single conditional is where assembly (`<<`, members named extrinsically) and partition (`group_by`, members named by an intrinsic value — 0.19.0) meet. For an assembled Collection, `as_detail(:assembly)` is the dimension-creating step: it promotes the member name into real data and **retains** it. From then on the structure is intrinsic and round-trips are exact; the promoted dimension is removed only by explicit contraction (`gt[-:assembly]`), never automatically.
1009
+ This single conditional is where assembly (`<<`, members named extrinsically) and partition (`group_by`, members named by an intrinsic value — 0.20.0) meet. For an assembled Collection, `as_detail(:assembly)` is the dimension-creating step: it promotes the member name into real data and **retains** it. From then on the structure is intrinsic and round-trips are exact; the promoted dimension is removed only by explicit contraction (`gt[-:assembly]`), never automatically.
1010
1010
 
1011
1011
  #### `<<` and unnamed members
1012
1012
 
@@ -1038,7 +1038,7 @@ gt << front_suspension # re-materialises detail
1038
1038
  gt.values(:weight) # => [200, 80, 150, 60, ...] (line items again)
1039
1039
  ```
1040
1040
 
1041
- Freeze-gated memoisation is a 2.x optimisation — opt-in via `freeze`, transparent, and never changing this observable behaviour. `group_by` (0.19.0) is the partition-side constructor for the same type: it splits a Namo into a `Collection`, the mirror of assembling one with `<<`.
1041
+ Freeze-gated memoisation is a 2.x optimisation — opt-in via `freeze`, transparent, and never changing this observable behaviour. `group_by` (0.20.0) is the partition-side constructor for the same type: it splits a Namo into a `Collection`, the mirror of assembling one with `<<`.
1042
1042
 
1043
1043
  ## Why?
1044
1044
 
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.18.0'
5
+ VERSION = '0.19.0'
6
6
  end
data/lib/namo.rb CHANGED
@@ -164,7 +164,7 @@ class Namo
164
164
 
165
165
  def ==(other)
166
166
  return false unless other.is_a?(Namo)
167
- canonical_data == other.canonical_data
167
+ row_multiset == other.row_multiset
168
168
  end
169
169
 
170
170
  def ===(other)
@@ -175,12 +175,12 @@ class Namo
175
175
 
176
176
  def eql?(other)
177
177
  self.class == other.class &&
178
- canonical_data == other.canonical_data &&
178
+ row_multiset == other.row_multiset &&
179
179
  @formulae.keys.sort == other.formulae.keys.sort
180
180
  end
181
181
 
182
182
  def hash
183
- [self.class, canonical_data, @formulae.keys.sort].hash
183
+ [self.class, row_multiset, @formulae.keys.sort].hash
184
184
  end
185
185
 
186
186
  def <(other)
@@ -217,13 +217,13 @@ class Namo
217
217
 
218
218
  protected
219
219
 
220
- def canonical_data
221
- @data.sort_by{|row| row.values_at(*data_dimensions.sort)}
220
+ def row_multiset
221
+ @data.tally
222
222
  end
223
223
 
224
224
  def subset_of_rows?(other)
225
- self_counts = canonical_data.tally
226
- other_counts = other.canonical_data.tally
225
+ self_counts = row_multiset
226
+ other_counts = other.row_multiset
227
227
  self_counts.all?{|row, count| (other_counts[row] || 0) >= count}
228
228
  end
229
229
 
data/test/namo_test.rb CHANGED
@@ -2312,6 +2312,31 @@ describe Namo do
2312
2312
  _(a == 'string').must_equal false
2313
2313
  _(a == nil).must_equal false
2314
2314
  end
2315
+
2316
+ it "compares rows whose dimension mixes nil and non-nil values" do
2317
+ a = Namo.new([{symbol: 'BHP', sector: 'Mining'}, {symbol: 'CBA', sector: nil}])
2318
+ b = Namo.new([{symbol: 'CBA', sector: nil}, {symbol: 'BHP', sector: 'Mining'}])
2319
+ _(a == b).must_equal true
2320
+ end
2321
+
2322
+ it "is false for differing rows when a dimension contains nil" do
2323
+ a = Namo.new([{symbol: 'BHP', sector: 'Mining'}, {symbol: 'CBA', sector: nil}])
2324
+ b = Namo.new([{symbol: 'BHP', sector: 'Mining'}, {symbol: 'CBA', sector: 'Banking'}])
2325
+ _(a == b).must_equal false
2326
+ end
2327
+
2328
+ it "ignores dimension (key) order within a row" do
2329
+ a = Namo.new([{a: 1, b: 2, c: 3}])
2330
+ b = Namo.new([{c: 3, b: 2, a: 1}])
2331
+ _(a == b).must_equal true
2332
+ end
2333
+
2334
+ it "is type-strict on values, consistent with the subset operators" do
2335
+ a = Namo.new([{x: 1}])
2336
+ b = Namo.new([{x: 1.0}])
2337
+ _(a == b).must_equal false
2338
+ _(a <= b).must_equal false
2339
+ end
2315
2340
  end
2316
2341
 
2317
2342
  describe "#===" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: namo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran