object_table 0.3.4 → 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/.travis.yml +0 -1
- data/README.md +206 -108
- data/lib/object_table/basic_grid.rb +1 -1
- data/lib/object_table/column.rb +6 -7
- data/lib/object_table/factory.rb +46 -0
- data/lib/object_table/grouping/grid.rb +47 -0
- data/lib/object_table/grouping.rb +109 -0
- data/lib/object_table/joining.rb +71 -0
- data/lib/object_table/masked_column.rb +2 -2
- data/lib/object_table/printing.rb +69 -0
- data/lib/object_table/stacking.rb +66 -0
- data/lib/object_table/static_view.rb +2 -5
- data/lib/object_table/table_methods.rb +35 -22
- data/lib/object_table/util.rb +19 -0
- data/lib/object_table/version.rb +1 -1
- data/lib/object_table/view.rb +7 -5
- data/lib/object_table/view_methods.rb +3 -2
- data/lib/object_table.rb +8 -19
- data/object_table.gemspec +2 -0
- data/spec/object_table/column_spec.rb +2 -2
- data/spec/object_table/grouping_spec.rb +475 -0
- data/spec/object_table/static_view_spec.rb +2 -2
- data/spec/object_table/util_spec.rb +43 -0
- data/spec/object_table/view_spec.rb +6 -16
- data/spec/object_table_spec.rb +45 -3
- data/spec/subclassing_spec.rb +44 -5
- data/spec/support/joining_example.rb +171 -0
- data/spec/support/object_table_example.rb +124 -29
- data/spec/support/stacking_example.rb +111 -0
- data/spec/support/utils.rb +8 -0
- data/spec/support/view_example.rb +10 -13
- metadata +20 -12
- data/lib/object_table/group.rb +0 -10
- data/lib/object_table/grouped.rb +0 -93
- data/lib/object_table/printable.rb +0 -72
- data/lib/object_table/stacker.rb +0 -59
- data/lib/object_table/table_child.rb +0 -19
- data/spec/object_table/grouped_spec.rb +0 -351
- data/spec/support/stacker_example.rb +0 -158
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative 'util'
|
2
|
+
|
3
|
+
class ObjectTable
|
4
|
+
module Joining
|
5
|
+
|
6
|
+
def join(*args)
|
7
|
+
__table_cls__.join(self, *args)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
# -1 in an index indicates a missing value
|
13
|
+
def join(left, right, *keys, type: 'inner')
|
14
|
+
incl_left = incl_right = false
|
15
|
+
case type
|
16
|
+
when 'left' then incl_left = true
|
17
|
+
when 'right' then incl_right = true
|
18
|
+
when 'outer' then incl_left = incl_right = true
|
19
|
+
when 'inner'
|
20
|
+
else raise "Expected one of (inner, left, outer, right), got #{type.inspect}"
|
21
|
+
end
|
22
|
+
|
23
|
+
lkeys = Util.get_rows(left, keys)
|
24
|
+
rkeys = Util.get_rows(right, keys)
|
25
|
+
|
26
|
+
rgroups = Util.group_indices(rkeys)
|
27
|
+
rgroups.default = (incl_left ? [-1] : []) # keep left missing values if incl_left
|
28
|
+
|
29
|
+
lindex = rgroups.values_at(*lkeys)
|
30
|
+
rindex = lindex.flatten
|
31
|
+
lindex = lindex.each_with_index.flat_map{|r, i| r.fill(i)}
|
32
|
+
|
33
|
+
if incl_right
|
34
|
+
# rindex may have missing values
|
35
|
+
# so add a dud value at the end ...
|
36
|
+
missing = NArray.int(right.nrows + 1).fill!(1)
|
37
|
+
missing[rindex] = 0
|
38
|
+
missing[-1] = 0 # ... that we always exclude
|
39
|
+
missing = missing.where
|
40
|
+
rindex.concat( missing.to_a )
|
41
|
+
lindex.concat( Array.new(missing.length, -1) )
|
42
|
+
end
|
43
|
+
|
44
|
+
index = [lindex, rindex].map{|ix| NArray.to_na(ix)}
|
45
|
+
blanks = index.map{|ix| ix.eq(-1).where}
|
46
|
+
|
47
|
+
colnames = [left.colnames, right.colnames - left.colnames]
|
48
|
+
data = [left, right].zip(index, blanks).zip(colnames).flat_map do |args, cols|
|
49
|
+
cols.map{|k| [k, Joining.copy_column(k, *args)] }
|
50
|
+
end
|
51
|
+
|
52
|
+
table = __table_cls__.new(data)
|
53
|
+
if incl_right
|
54
|
+
keys.each do |k|
|
55
|
+
table[k][false, blanks[0]] = right[k][false, missing]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
table
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.copy_column(name, table, slice, blanks)
|
64
|
+
column = table[name][false, slice]
|
65
|
+
column[false, blanks] = [nil]
|
66
|
+
column
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class ObjectTable
|
2
|
+
module Printing
|
3
|
+
|
4
|
+
def inspect(max_section=5, col_padding=2)
|
5
|
+
header = "#{self.class}(#{nrows}, #{ncols})\n"
|
6
|
+
|
7
|
+
return "#{header}(empty table)" if ncols == 0
|
8
|
+
return "#{header}(empty table with columns: #{colnames.join(", ")})" if nrows == 0
|
9
|
+
|
10
|
+
separated = (nrows > max_section * 2)
|
11
|
+
max_section = (nrows / 2.0) unless separated
|
12
|
+
|
13
|
+
head = Printing.format_section(columns, 0...max_section.to_i).transpose[0...-1]
|
14
|
+
tail = Printing.format_section(columns, (nrows - max_section).to_i...nrows).transpose[1..-1]
|
15
|
+
widths = Printing.calc_column_widths(head + tail, col_padding)
|
16
|
+
|
17
|
+
rows = Printing.format_rows(head, widths)
|
18
|
+
rows.push('-' * widths.reduce(:+)) if separated
|
19
|
+
rows.concat Printing.format_rows(tail, widths)
|
20
|
+
|
21
|
+
header + rows.join("\n")
|
22
|
+
|
23
|
+
rescue NoMethodError => e
|
24
|
+
raise Exception.new(e)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def self.format_column(column)
|
29
|
+
return column.to_a.map(&:inspect) if column.rank < 2
|
30
|
+
column.shape[-1].times.map do |i|
|
31
|
+
row = column[false, i]
|
32
|
+
row.inspect.partition("\n")[-1].strip
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.split_column_lines(name, column)
|
37
|
+
[name, *column, name].map{|i| i.split("\n")}
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.calc_column_widths(rows, padding)
|
41
|
+
columns = rows.transpose
|
42
|
+
columns.map{|col| col.flatten.map(&:length).max + padding}
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.format_rows(rows, widths)
|
46
|
+
rows = rows.flat_map do |row|
|
47
|
+
height = row.map(&:length).max
|
48
|
+
row.map{|cell| cell.fill('', cell.length...height)}.transpose
|
49
|
+
end
|
50
|
+
|
51
|
+
format = widths.to_a.map{|w| "%#{w}s"}.join
|
52
|
+
rows.map{|row| format % row }
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.format_section(columns, row_slice)
|
56
|
+
row_slice = row_slice.to_a
|
57
|
+
numbers = split_column_lines('', row_slice.map{|i| "#{i}: "})
|
58
|
+
|
59
|
+
section = columns.map do |name, c|
|
60
|
+
c = (row_slice.empty? ? NArray[] : c.slice(false, row_slice))
|
61
|
+
c = format_column(c)
|
62
|
+
c = split_column_lines(name.to_s, c)
|
63
|
+
end
|
64
|
+
|
65
|
+
[numbers] + section
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class ObjectTable
|
2
|
+
module Stacking
|
3
|
+
|
4
|
+
def stack(*others)
|
5
|
+
__table_cls__.stack(self, *others)
|
6
|
+
end
|
7
|
+
|
8
|
+
module InPlace
|
9
|
+
def stack!(*others)
|
10
|
+
@columns.replace( __table_cls__.stack(self, *others).columns )
|
11
|
+
self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def stack(*grids); _stack(grids); end
|
17
|
+
|
18
|
+
def _stack(grids)
|
19
|
+
keys = nil
|
20
|
+
|
21
|
+
grids = grids.map do |grid|
|
22
|
+
grid = Stacking.process_stackable_grid(grid, keys)
|
23
|
+
keys ||= grid.keys if grid
|
24
|
+
grid
|
25
|
+
end.compact
|
26
|
+
return __table_cls__.new if grids.empty?
|
27
|
+
|
28
|
+
result = keys.map do |k|
|
29
|
+
segments = grids.map{|grid| grid[k]}
|
30
|
+
[k, Stacking.stack_segments(segments)]
|
31
|
+
end
|
32
|
+
|
33
|
+
__table_cls__.new(BasicGrid[result])
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def self.stack_segments(segments)
|
40
|
+
if segments.all?{|seg| seg.is_a? Array}
|
41
|
+
column = NArray.to_na(segments.flatten(1))
|
42
|
+
|
43
|
+
else
|
44
|
+
segments.map!{|seg| NArray.to_na seg}
|
45
|
+
column = Column._stack(segments)
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.process_stackable_grid(grid, keys)
|
51
|
+
case grid
|
52
|
+
when TableMethods
|
53
|
+
grid = grid.columns
|
54
|
+
when BasicGrid
|
55
|
+
grid._ensure_uniform_columns!
|
56
|
+
end
|
57
|
+
|
58
|
+
raise "Don't know how to join a #{grid.class}" unless grid.is_a?(BasicGrid)
|
59
|
+
return if grid.empty?
|
60
|
+
raise 'Mismatch in column names' unless !keys or ( (keys - grid.keys).empty? and (grid.keys - keys).empty? )
|
61
|
+
return grid
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -16,17 +16,14 @@ class ObjectTable::StaticView
|
|
16
16
|
|
17
17
|
def columns
|
18
18
|
unless @fully_cached
|
19
|
-
@parent.columns.
|
19
|
+
@parent.columns.each_key{|k| get_column(k)}
|
20
20
|
@fully_cached = true
|
21
21
|
end
|
22
22
|
@columns
|
23
23
|
end
|
24
24
|
|
25
25
|
def get_column(name)
|
26
|
-
@columns
|
27
|
-
col = super
|
28
|
-
@columns[name] = col if col
|
29
|
-
end
|
26
|
+
@columns[name] ||= super
|
30
27
|
end
|
31
28
|
|
32
29
|
def add_column(name, *args)
|
@@ -1,9 +1,17 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
|
2
|
+
|
3
|
+
require_relative 'printing'
|
4
|
+
require_relative 'util'
|
5
|
+
require_relative "joining"
|
6
|
+
require_relative "stacking"
|
3
7
|
|
4
8
|
module ObjectTable::TableMethods
|
5
|
-
include ObjectTable::
|
9
|
+
include ObjectTable::Printing
|
10
|
+
include ObjectTable::Joining
|
11
|
+
include ObjectTable::Stacking
|
12
|
+
|
6
13
|
extend Forwardable
|
14
|
+
Util = ObjectTable::Util
|
7
15
|
|
8
16
|
attr_reader :R
|
9
17
|
def initialize
|
@@ -16,20 +24,17 @@ module ObjectTable::TableMethods
|
|
16
24
|
end
|
17
25
|
alias_method :eql?, :==
|
18
26
|
|
19
|
-
def colnames
|
20
|
-
columns.keys
|
21
|
-
end
|
22
|
-
|
23
27
|
def nrows
|
24
|
-
columns.empty? ? 0 : (columns.
|
28
|
+
columns.empty? ? 0 : ObjectTable::Column.length_of(columns.first[1])
|
25
29
|
end
|
26
30
|
|
27
31
|
def ncols
|
28
32
|
columns.keys.length
|
29
33
|
end
|
30
34
|
|
35
|
+
def_delegator :columns, :keys, :colnames
|
31
36
|
def_delegator :columns, :include?, :has_column?
|
32
|
-
|
37
|
+
def_delegator :columns, :delete, :pop_column
|
33
38
|
def_delegator :columns, :[], :get_column
|
34
39
|
alias_method :[], :get_column
|
35
40
|
|
@@ -64,31 +69,19 @@ module ObjectTable::TableMethods
|
|
64
69
|
end
|
65
70
|
alias_method :[]=, :set_column
|
66
71
|
|
67
|
-
def pop_column(name)
|
68
|
-
columns.delete name
|
69
|
-
end
|
70
72
|
|
71
73
|
def apply(&block)
|
72
|
-
result =
|
73
|
-
|
74
|
+
result = Util.apply_block(self, block)
|
74
75
|
return result unless result.is_a? ObjectTable::BasicGrid
|
75
76
|
__table_cls__.new(result)
|
76
77
|
end
|
77
78
|
|
78
|
-
def _apply_block(&block)
|
79
|
-
if block.arity == 0
|
80
|
-
result = instance_eval &block
|
81
|
-
else
|
82
|
-
result = block.call(self)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
79
|
def where(&block)
|
87
80
|
__view_cls__.new(self, &block)
|
88
81
|
end
|
89
82
|
|
90
83
|
def group_by(*args, &block)
|
91
|
-
ObjectTable::
|
84
|
+
ObjectTable::Grouping.new(self, *args, &block)
|
92
85
|
end
|
93
86
|
|
94
87
|
def sort_by(*keys)
|
@@ -114,4 +107,24 @@ module ObjectTable::TableMethods
|
|
114
107
|
(0...nrows).zip(columns.map(&:to_a).transpose).sort_by(&:last).map(&:first)
|
115
108
|
end
|
116
109
|
|
110
|
+
def each_row(*cols, row_factory: Struct)
|
111
|
+
return to_enum(:each_row, *cols, row_factory: row_factory) unless block_given?
|
112
|
+
return if ncols == 0
|
113
|
+
|
114
|
+
cls = nil
|
115
|
+
if cols.empty?
|
116
|
+
cls = row_factory.new(*colnames)
|
117
|
+
cols = colnames
|
118
|
+
end
|
119
|
+
|
120
|
+
columns = cols.map{|c| get_column(c)}
|
121
|
+
nrows.times do |i|
|
122
|
+
row = columns.map{|c| c[false, i]}
|
123
|
+
row = cls.new(*row) if cls
|
124
|
+
yield row
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
117
128
|
end
|
129
|
+
|
130
|
+
require_relative "grouping" # put here to avoid circular dependency
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ObjectTable::Util
|
2
|
+
|
3
|
+
def self.apply_block(object, block)
|
4
|
+
if block.arity == 0
|
5
|
+
object.instance_eval(&block)
|
6
|
+
else
|
7
|
+
block.call(object)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.get_rows(table, columns)
|
12
|
+
columns.map{|n| table[n].to_a}.transpose
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.group_indices(keys)
|
16
|
+
keys.length.times.group_by{|i| keys[i]}
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/object_table/version.rb
CHANGED
data/lib/object_table/view.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require_relative 'view_methods'
|
3
3
|
require_relative 'masked_column'
|
4
|
+
require_relative 'util'
|
4
5
|
|
5
6
|
class ObjectTable::View
|
6
7
|
include ObjectTable::ViewMethods
|
8
|
+
Util = ObjectTable::Util
|
7
9
|
|
8
10
|
extend Forwardable
|
9
11
|
def_delegators :make_view, :apply
|
@@ -34,12 +36,12 @@ class ObjectTable::View
|
|
34
36
|
end
|
35
37
|
|
36
38
|
def indices
|
37
|
-
@indices or NArray.int(@parent.nrows).indgen![@parent
|
39
|
+
@indices or NArray.int(@parent.nrows).indgen![Util.apply_block(@parent, @filter)]
|
38
40
|
end
|
39
41
|
|
40
|
-
def cache_indices
|
42
|
+
def cache_indices
|
41
43
|
@indices = indices
|
42
|
-
value =
|
44
|
+
value = yield
|
43
45
|
@indices = nil
|
44
46
|
value
|
45
47
|
end
|
@@ -48,9 +50,9 @@ class ObjectTable::View
|
|
48
50
|
@columns or super
|
49
51
|
end
|
50
52
|
|
51
|
-
def cache_columns
|
53
|
+
def cache_columns
|
52
54
|
@columns = columns
|
53
|
-
value =
|
55
|
+
value = yield
|
54
56
|
@columns = nil
|
55
57
|
value
|
56
58
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'forwardable'
|
2
|
+
|
2
3
|
require_relative 'table_methods'
|
3
|
-
require_relative '
|
4
|
+
require_relative 'factory'
|
4
5
|
|
5
6
|
module ObjectTable::ViewMethods
|
6
7
|
extend Forwardable
|
7
8
|
include ObjectTable::TableMethods
|
8
|
-
include ObjectTable::
|
9
|
+
include ObjectTable::Factory::SubFactory
|
9
10
|
|
10
11
|
def_delegators :@parent, :has_column?
|
11
12
|
|
data/lib/object_table.rb
CHANGED
@@ -4,12 +4,17 @@ require_relative "object_table/table_methods"
|
|
4
4
|
require_relative "object_table/view"
|
5
5
|
require_relative "object_table/static_view"
|
6
6
|
require_relative "object_table/column"
|
7
|
-
require_relative "object_table/
|
8
|
-
require_relative "object_table/
|
7
|
+
require_relative "object_table/stacking"
|
8
|
+
require_relative "object_table/factory"
|
9
|
+
require_relative "object_table/joining"
|
9
10
|
|
10
11
|
class ObjectTable
|
11
12
|
include TableMethods
|
12
|
-
include
|
13
|
+
include Stacking::InPlace
|
14
|
+
include Factory
|
15
|
+
|
16
|
+
extend Stacking::ClassMethods
|
17
|
+
extend Joining::ClassMethods
|
13
18
|
|
14
19
|
attr_reader :columns
|
15
20
|
|
@@ -41,20 +46,4 @@ class ObjectTable
|
|
41
46
|
self
|
42
47
|
end
|
43
48
|
|
44
|
-
def __static_view_cls__
|
45
|
-
self.class::StaticView
|
46
|
-
end
|
47
|
-
|
48
|
-
def __view_cls__
|
49
|
-
self.class::View
|
50
|
-
end
|
51
|
-
|
52
|
-
def __group_cls__
|
53
|
-
self.class::Group
|
54
|
-
end
|
55
|
-
|
56
|
-
def __table_cls__
|
57
|
-
self.class
|
58
|
-
end
|
59
|
-
|
60
49
|
end
|
data/object_table.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.required_ruby_version = '>= 2.0'
|
22
|
+
|
21
23
|
spec.add_runtime_dependency 'narray', "~> 0.6.1"
|
22
24
|
|
23
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
@@ -15,8 +15,8 @@ describe ObjectTable::Column do
|
|
15
15
|
expect(described_class.length_of NArray.float(0)).to eql 0
|
16
16
|
end
|
17
17
|
|
18
|
-
it 'should
|
19
|
-
expect
|
18
|
+
it 'should return nil on other inputs' do
|
19
|
+
expect(described_class.length_of 123456).to be_nil
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|