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 +4 -4
- data/CHANGELOG +18 -0
- data/README.md +10 -6
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +22 -12
- data/test/console_test.rb +1 -1
- data/test/namo_test.rb +53 -12
- 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: 611590edfde52f5713b8fe41daf28b5986a26f4df3a5c28631dace995c31b98c
|
|
4
|
+
data.tar.gz: f098e612be772d3232598020963f6b3ed5c9a786f4827de25312e90aff23bc48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 `
|
|
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
|
|
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
|
-
# ]
|
|
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,
|
|
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
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
306
|
-
|
|
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.
|
|
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].
|
|
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]
|
|
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]
|
|
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
|