namo 0.31.1 → 0.31.3

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: e5235177577fcae1dd01a8a3172fc3933e77a5483f123ba6f436bd58a6eca68e
4
- data.tar.gz: b2c023063fa95cca3f6ec38f324a116bf013339003d4051338a7c84b57251944
3
+ metadata.gz: 611590edfde52f5713b8fe41daf28b5986a26f4df3a5c28631dace995c31b98c
4
+ data.tar.gz: f098e612be772d3232598020963f6b3ed5c9a786f4827de25312e90aff23bc48
5
5
  SHA512:
6
- metadata.gz: 3278fdcba0888a5899930abb399c3cc3b1aff49d7bb27ceefc223524c4247cb4c85ca0274ba58254965229e9cec131c014a544fce61444e8d6e34221896047e5
7
- data.tar.gz: 94cdb60c0bc852b456ded744a63f8e7fda0dbf884d2ff085b0908cfb80d26f48588b4254b7d389cb68b4bce0f668d3a528ce5eeba22bd80ea4a50331c6ccb2e8
6
+ metadata.gz: a28b1d267f69eb14a9ee8d7a9cbdba9de57132be4577a39892e3f679e0ccee293a88a2d526ceda0ff6232317bdb8028ae3f04858160c7a38cbc4cb640c5fff16
7
+ data.tar.gz: e615d6be239ccd8a563922e3b51f5044a745733a4ef3f120b78ab5b07f84a4ff5be37373386853fb9d1484146471fc7034d58af9aee9172e4ae967e341ba0c39
data/CHANGELOG CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## 20260824
4
4
 
5
+ 0.31.3: ~ Namo#to_a: derived dimensions are carried alongside the stored ones.
6
+
7
+ 1. ~ Namo#to_a: each row carries the derived dimensions which can be materialised without arguments, in among its stored keys, so to_a agrees with to_h and with dimensions about what the Namo holds. A formula wanting arguments is omitted, the rule values, coordinates and to_h already follow. Namo#data is unchanged and remains the stored rows; Namo::Row#to_h is unchanged, Row#==, Row#eql? and Namo#<< all reading through it.
8
+ 2. ~ README.md: the Extracting data section, and the parameterised-formula rule which now names to_a.
9
+ 3. ~ ROADMAP.md: + 0.31.3; the open question on which conversions materialise closes for to_a and stays open for Namo::Row.
10
+ 4. ~ Namo::VERSION: /0.31.2/0.31.3/
11
+
12
+ Rebuilding a Namo from to_a now carries the derived dimensions in as stored data. Namo.new(other.to_a) followed by attaching the formulary which produced them raises, the 0.24.1 collision guard refusing the name twice over; []= accepts it, evicting the data. Where the stored rows are what is meant, data is the accessor for them. A formula whose inputs a projection has cut raises through to_a as it has always raised through to_h and values, which 0.16.0 settled as the caller's own choice.
13
+
14
+
15
+ 0.31.2: ~ Namo#inspect: the derived: suffix names only what the rows do not carry.
16
+
17
+ 1. ~ Namo#inspect: the derived: suffix names only the derived dimensions the rendered rows do not already carry, and is omitted where they all do. A formula which raises, a parameterised one which cannot be materialised without its arguments, and any formula on a Namo with no rows to render, are named as before, being nowhere else in the output. Namo::Collection renders no rows and so names every one of its own. Namo::Row is unchanged, naming its formulae in every case.
18
+ 2. ~ README.md: the Inspection output section, and the block whose suffix goes.
19
+ 3. ~ ROADMAP.md: + 0.31.2.
20
+ 4. ~ Namo::VERSION: /0.31.1/0.31.2/
21
+
22
+
5
23
  0.31.1: ~ Namo#inspect, Namo::Row#inspect: derived dimensions render their values.
6
24
 
7
25
  1. ~ Namo#inspect: a derived dimension renders with its value, in among the stored ones, rather than being named only. Where there is no value to show it is omitted and the derived: suffix names it alone: a formula which raises, and a parameterised one which cannot be materialised without its arguments.
data/README.md CHANGED
@@ -717,7 +717,7 @@ prices.last[:sma] # ArgumentError: wrong number of arguments for :sma
717
717
  prices.last[:close, 20] # ArgumentError: wrong number of arguments for :close (given 1, expected 0)
718
718
  ```
719
719
 
720
- A formula that requires arguments can't be materialised without them. `values(:sma)`, `coordinates(:sma)`, naming `:sma` in a projection, and selecting on it all raise the same `ArgumentError`; the no-argument `values`, `coordinates`, and `to_h` omit the dimension, returning everything that can be materialised. `dimensions` and `derived_dimensions` still list it — it is queryable, with arguments. To materialise particular values, bind the arguments in a one-arity wrapper and ask for that:
720
+ A formula that requires arguments can't be materialised without them. `values(:sma)`, `coordinates(:sma)`, naming `:sma` in a projection, and selecting on it all raise the same `ArgumentError`; the no-argument `values`, `coordinates`, `to_h` and `to_a` omit the dimension, returning everything that can be materialised. `dimensions` and `derived_dimensions` still list it — it is queryable, with arguments. To materialise particular values, bind the arguments in a one-arity wrapper and ask for that:
721
721
 
722
722
  ```ruby
723
723
  prices[:sma_close_20] = proc{|row| row[:sma, :close, 20]}
@@ -947,7 +947,7 @@ The transforming and reducing methods are deliberately left as Enumerable's defa
947
947
 
948
948
  ### Extracting data
949
949
 
950
- `to_a` returns an array of hashes — the row-oriented form:
950
+ `to_a` returns an array of hashes — the row-oriented form, carrying derived dimensions alongside the stored ones, so it agrees with `to_h` and with `dimensions` about what the Namo holds:
951
951
 
952
952
  ```ruby
953
953
  sales[:product, :quarter, :revenue].to_a
@@ -959,6 +959,8 @@ sales[:product, :quarter, :revenue].to_a
959
959
  # ]
960
960
  ```
961
961
 
962
+ `data` is the stored rows, without derived dimensions and without copying — it is the accessor for what the Namo was built from, where `to_a` is the conversion of what it can answer. A formula whose inputs a projection has cut raises through `to_a` as it does through `to_h` and `values`, which is [the caller's own choice](#projection-of-derived-dimensions) rather than a failure of the conversion.
963
+
962
964
  `to_h` returns a hash of arrays — the columnar form (see [Coordinates and values](#coordinates-and-values) above):
963
965
 
964
966
  ```ruby
@@ -972,7 +974,7 @@ sales[:product, :quarter, :revenue].to_h
972
974
 
973
975
  ### Inspection output
974
976
 
975
- `inspect` renders the class, the stored rows with any derived dimensions among them, and the names of those derived dimensions:
977
+ `inspect` renders the class and the stored rows, with any derived dimensions among them:
976
978
 
977
979
  ```ruby
978
980
  sales.inspect
@@ -981,18 +983,20 @@ sales.inspect
981
983
  # {product: "Widget", quarter: "Q2", price: 10.0, quantity: 150, revenue: 1500.0},
982
984
  # {product: "Gadget", quarter: "Q1", price: 25.0, quantity: 40, revenue: 1000.0},
983
985
  # {product: "Gadget", quarter: "Q2", price: 25.0, quantity: 60, revenue: 1500.0}
984
- # ] derived: [:revenue]>
986
+ # ]>
985
987
  ```
986
988
 
987
989
  A name, where the Namo carries one, follows the class: `#<Namo :sales [`. A subclass reports its own name in place of `Namo`.
988
990
 
989
- A derived dimension shows its value, in among the stored ones, and the `derived:` suffix names which of the keys are computed. Where there is no value to show it is omitted rather than the rendering failing: a formula which raises, and a parameterised one which cannot be materialised without its arguments, each render nothing and are named by the suffix alone.
991
+ A derived dimension shows its value, in among the stored ones, so a computed dimension renders exactly as a stored one does — which is the claim the library makes about it. Where there is no value to show, the rendering omits it rather than failing, and the `derived:` suffix names it: a formula which raises, a parameterised one which cannot be materialised without its arguments, and any formula at all on a Namo with no rows to render.
992
+
993
+ The suffix carries only those. Where every derived dimension rendered a value it has nothing left to say and does not appear. Ask `derived_dimensions` when you want the distinction named.
990
994
 
991
995
  Evaluation is bounded by the row cap rather than by the data — at most ten rows are rendered, so at most ten evaluations per formula. A collection-scoped formula makes each of those a pass over the Namo, so inspecting a large one that carries such a formula is not free.
992
996
 
993
997
  Rows beyond `Namo::INSPECTED_ROWS` — ten — are elided with a count of the remainder, so the output stays bounded however large the data: a thousand-row Namo renders its first ten rows and then `... 990 more rows`.
994
998
 
995
- A `Row` renders itself, its derived values and its formula names, never the Namo it came from. A `Collection` renders its member names and its row count, the members being its substance and the data view derived from them.
999
+ A `Row` renders itself, its derived values and its formula names in every case, having no `derived_dimensions` of its own to be asked, and never the Namo it came from. A `Collection` renders its member names and its row count, the members being its substance and the data view derived from them.
996
1000
 
997
1001
  ```ruby
998
1002
  sales.first
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.31.1'
5
+ VERSION = '0.31.3'
6
6
  end
data/lib/namo.rb CHANGED
@@ -241,16 +241,23 @@ class Namo
241
241
  other.subset_of_rows?(self)
242
242
  end
243
243
 
244
+ # Each row's own keys, in their own order, and then the derived dimensions which
245
+ # can be materialised without arguments — the rule values, coordinates and to_h
246
+ # already follow. data is the stored rows, and stays so.
244
247
  def to_a
245
- @data.map do |row|
246
- row.keys.each_with_object({}) do |key, hash|
247
- hash[key] = row[key]
248
+ materialising do
249
+ derived = materialisable_dimensions - data_dimensions
250
+ @data.map do |row|
251
+ subject = Row.new(row, @formulae, self)
252
+ stored = row.keys.each_with_object({}){|key, hash| hash[key] = row[key]}
253
+ derived.each_with_object(stored){|dimension, hash| hash[dimension] = subject[dimension]}
248
254
  end
249
255
  end
250
256
  end
251
257
 
252
258
  def inspect
253
- "#<#{self.class}#{inspected_name}#{inspected_rows}#{inspected_derived}>"
259
+ rendered = @data.first(INSPECTED_ROWS).map{|row| [row, inspected_derivations(row)]}
260
+ "#<#{self.class}#{inspected_name}#{inspected_rows(rendered)}#{inspected_derived(rendered)}>"
254
261
  end
255
262
 
256
263
  protected
@@ -278,17 +285,13 @@ class Namo
278
285
  # The rows as they are stored, never the derived values: inspect is called
279
286
  # for every result in a console, and evaluating a formula there would cost a
280
287
  # pass over the data per access and raise whatever the formula raises.
281
- def inspected_rows
288
+ def inspected_rows(rendered)
282
289
  return ' []' if @data.empty?
283
- shown = @data.first(INSPECTED_ROWS).map{|row| " #{inspected_row(row)}"}.join(",\n")
290
+ shown = rendered.map{|row, derived| " #{row.merge(derived).inspect}"}.join(",\n")
284
291
  shown += "\n ... #{@data.length - INSPECTED_ROWS} more rows" if @data.length > INSPECTED_ROWS
285
292
  " [\n#{shown}\n]"
286
293
  end
287
294
 
288
- def inspected_row(row)
289
- row.merge(inspected_derivations(row)).inspect
290
- end
291
-
292
295
  # A derived dimension is shown with its value, in among the stored ones. A
293
296
  # formula wanting arguments has no value to show without them, and one which
294
297
  # raises has none to show either; the derived list still names both.
@@ -302,8 +305,15 @@ class Namo
302
305
  end
303
306
  end
304
307
 
305
- def inspected_derived
306
- derived_dimensions.empty? ? '' : " derived: #{derived_dimensions.inspect}"
308
+ # Only those the rows above do not already carry. A formula which raises, and a
309
+ # parameterised one which cannot be materialised without its arguments, render
310
+ # no value and would otherwise be nowhere in the output at all. A Collection
311
+ # renders no rows and so passes none, naming every one of them.
312
+ def inspected_derived(rendered = [])
313
+ unshown = derived_dimensions.reject do |dimension|
314
+ rendered.any? && rendered.all?{|_, derived| derived.key?(dimension)}
315
+ end
316
+ unshown.empty? ? '' : " derived: #{unshown.inspect}"
307
317
  end
308
318
 
309
319
  def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
data/test/console_test.rb CHANGED
@@ -33,7 +33,7 @@ describe 'script/console' do
33
33
 
34
34
  it "holds what the fixtures hold, so it cannot drift from the demo" do
35
35
  expected = eval(Fixtures.readings)
36
- _(console('readings.to_a == ' + expected.inspect)).must_match(/true/)
36
+ _(console('readings.data == ' + expected.inspect)).must_match(/true/)
37
37
  end
38
38
 
39
39
  it "starts without echoing its own source" do
data/test/namo_test.rb CHANGED
@@ -488,8 +488,8 @@ describe Namo do
488
488
  sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
489
489
  result = sales[revenue: ->(v){v >= 1500.0}]
490
490
  _(result.to_a).must_equal [
491
- {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
492
- {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
491
+ {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150, revenue: 1500.0},
492
+ {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60, revenue: 1500.0}
493
493
  ]
494
494
  end
495
495
  end
@@ -571,8 +571,8 @@ describe Namo do
571
571
  sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
572
572
  result = sales[revenue: 1200.0..]
573
573
  _(result.to_a).must_equal [
574
- {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
575
- {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
574
+ {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150, revenue: 1500.0},
575
+ {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60, revenue: 1500.0}
576
576
  ]
577
577
  end
578
578
  end
@@ -602,7 +602,7 @@ describe Namo do
602
602
  sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
603
603
  sales[:cost] = proc{|r| r[:quantity] * 4.0}
604
604
  sales[:profit] = proc{|r| r[:revenue] - r[:cost]}
605
- _(sales[:product, :quarter, :profit].to_a).must_equal [
605
+ _(sales[:product, :quarter, :profit].data).must_equal [
606
606
  {product: "Widget", quarter: "Q1", profit: 600.0},
607
607
  {product: "Widget", quarter: "Q2", profit: 900.0},
608
608
  {product: "Gadget", quarter: "Q1", profit: 840.0},
@@ -1657,8 +1657,8 @@ describe Namo do
1657
1657
  sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
1658
1658
  result = sales.select{|row| row[:revenue] >= 1500.0}
1659
1659
  _(result.to_a).must_equal [
1660
- {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
1661
- {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
1660
+ {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150, revenue: 1500.0},
1661
+ {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60, revenue: 1500.0}
1662
1662
  ]
1663
1663
  end
1664
1664
 
@@ -2458,7 +2458,7 @@ describe Namo do
2458
2458
  dated[:cutoff] = proc{|r| r[:date]}
2459
2459
  result = dated.*(quarterly){|row, candidates| candidates[quarter_end: ->(qe){qe <= row[:cutoff]}].sort_by{|f| f[:quarter_end]}.last(1)}
2460
2460
  _(result.to_a).must_equal [
2461
- {symbol: 'BHP', date: '2025-05-20', close: 44.0, quarter_end: '2025-03-31', eps: 1.2}
2461
+ {symbol: 'BHP', date: '2025-05-20', close: 44.0, quarter_end: '2025-03-31', eps: 1.2, cutoff: '2025-05-20'}
2462
2462
  ]
2463
2463
  end
2464
2464
 
@@ -2638,8 +2638,8 @@ describe Namo do
2638
2638
  weighted_tiers[:premium] = proc{|t| t[:max_weight] > 15}
2639
2639
  result = orders.**(weighted_tiers){|row, candidates| candidates[premium: ->(v){v}]}
2640
2640
  _(result.to_a).must_equal [
2641
- {order: 'A', weight: 5, tier: 'heavy', max_weight: 20},
2642
- {order: 'B', weight: 15, tier: 'heavy', max_weight: 20}
2641
+ {order: 'A', weight: 5, tier: 'heavy', max_weight: 20, premium: true},
2642
+ {order: 'B', weight: 15, tier: 'heavy', max_weight: 20, premium: true}
2643
2643
  ]
2644
2644
  end
2645
2645
 
@@ -3067,6 +3067,41 @@ describe Namo do
3067
3067
  it "returns the data as an array of hashes" do
3068
3068
  _(sales.to_a).must_equal sample_data
3069
3069
  end
3070
+
3071
+ it "carries a derived dimension alongside the stored ones" do
3072
+ sales[:revenue] = proc{|row| row[:price] * row[:quantity]}
3073
+ _(sales.to_a.first).must_equal(
3074
+ {product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100, revenue: 1000.0})
3075
+ end
3076
+
3077
+ it "agrees with to_h about which dimensions there are" do
3078
+ sales[:revenue] = proc{|row| row[:price] * row[:quantity]}
3079
+ _(sales.to_a.first.keys).must_equal sales.to_h.keys
3080
+ _(sales.to_a.first.keys).must_equal sales.dimensions
3081
+ end
3082
+
3083
+ it "omits a formula which cannot be materialised without its arguments" do
3084
+ sales[:revenue] = proc{|row| row[:price] * row[:quantity]}
3085
+ sales[:scaled] = proc{|row, namo, factor| row[:price] * factor}
3086
+ _(sales.to_a.first.keys).wont_include :scaled
3087
+ _(sales.to_a.first.keys).must_include :revenue
3088
+ _(sales.derived_dimensions).must_include :scaled
3089
+ end
3090
+
3091
+ it "raises where a carried formula's inputs were projected away, as 0.16.0 settled" do
3092
+ sales[:revenue] = proc{|row| row[:price] * row[:quantity]}
3093
+ _(->{sales[:product, :quarter].to_a}).must_raise NoMethodError
3094
+ end
3095
+
3096
+ it "leaves data as the stored rows" do
3097
+ sales[:revenue] = proc{|row| row[:price] * row[:quantity]}
3098
+ _(sales.data).must_equal sample_data
3099
+ _(sales.data.first.keys).wont_include :revenue
3100
+ end
3101
+
3102
+ it "returns fresh rows rather than the stored ones" do
3103
+ _(sales.to_a.first).wont_be_same_as sales.data.first
3104
+ end
3070
3105
  end
3071
3106
 
3072
3107
  describe "#group_by" do
@@ -3234,13 +3269,19 @@ describe Namo do
3234
3269
  it "shows a derived dimension with its value, in among the stored ones" do
3235
3270
  namo = Namo.new([{a: 1}])
3236
3271
  namo[:b] = proc{|row| row[:a] + 1}
3237
- _(namo.inspect).must_equal "#<Namo [\n {a: 1, b: 2}\n] derived: [:b]>"
3272
+ _(namo.inspect).must_equal "#<Namo [\n {a: 1, b: 2}\n]>"
3238
3273
  end
3239
3274
 
3240
3275
  it "shows a collection-scoped derived value" do
3241
3276
  namo = Namo.new([{a: 1}, {a: 2}])
3242
3277
  namo[:total] = proc{|row, namo| namo.values(:a).sum}
3243
- _(namo.inspect).must_equal "#<Namo [\n {a: 1, total: 3},\n {a: 2, total: 3}\n] derived: [:total]>"
3278
+ _(namo.inspect).must_equal "#<Namo [\n {a: 1, total: 3},\n {a: 2, total: 3}\n]>"
3279
+ end
3280
+
3281
+ it "names a derived dimension on a Namo with no rows, there being nowhere else to learn of it" do
3282
+ namo = Namo.new([])
3283
+ namo[:b] = proc{|row| row[:a] + 1}
3284
+ _(namo.inspect).must_equal "#<Namo [] derived: [:b]>"
3244
3285
  end
3245
3286
 
3246
3287
  it "names a derived dimension whose formula raises, without a value" 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.31.1
4
+ version: 0.31.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran