object_table 0.2.0 → 0.2.2

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
  SHA1:
3
- metadata.gz: 4b1327f62f465f8305a265b6a7f865c5ca43a66b
4
- data.tar.gz: 8dc9d380fecb0ac14556f3cbe38acae87ec943b3
3
+ metadata.gz: 47a4b77bf7199464216dd309096200c6870cdda1
4
+ data.tar.gz: d34284715a8a0340f48e19bfb522c22bd570e5da
5
5
  SHA512:
6
- metadata.gz: 666c0551cef6acff77df23fee41986c49b05e2959bb770d0c1ed06cee357774c9e57a31f359eb50603be8d9f87e03b216eec4272a1cb6e2b8ab420a934513c40
7
- data.tar.gz: 18841d8933d04755b955fd6882fb7d8480d199d442306c0a02d44b041984f7f131f0f81d7bd4e5c4da277b4fca91847bae7d35e2a8fc63771119f8a14989e222
6
+ metadata.gz: cdb8e5894a5880b36635847962d9fabfb6e998be2f46d633c9d4e0c9519f678b20e9c61906a15cc6ee15b6bf8a1ec896d26c0c6e9a970abe07412b16ea8b48db
7
+ data.tar.gz: a42440c3488fecaf1e68b0d5ff2f5d0b49ec98a7f899758f13b1bddf9040c4920574a37814219ad312c875c9c0fb646fa375ff9c2ff0bef28830768e8d4d3554
data/README.md CHANGED
@@ -464,6 +464,7 @@ The act of subclassing itself is easy, but any methods you add won't be availabl
464
464
  a + b
465
465
  end
466
466
  end
467
+ ...
467
468
 
468
469
  >>> data = BrokenTable.new(a: 1..3, b: 4..6)
469
470
  >>> data.a_plus_b
@@ -475,14 +476,12 @@ The act of subclassing itself is easy, but any methods you add won't be availabl
475
476
  NoMethodError: undefined method `a_plus_b' for #<ObjectTable::View:0x000000011d4dd0>
476
477
  ```
477
478
 
478
- To make it work, you'll need to subclass `View`, `StaticView` and `Group` too. Then set the `Table` constant on each. The easiest way is just to include a module.
479
+ To make it work, you'll need to subclass `View`, `StaticView` and `Group` too and assign those subclasses under your ObjectTable subclass.
480
+ The easiest way is just to include a module with your common methods.
479
481
 
480
482
  ```ruby
481
483
  >>> class WorkingTable < ObjectTable
482
484
  module Mixin
483
- # this mixin will set the Table constant on each of the subclasses
484
- Table = WorkingTable
485
-
486
485
  def a_plus_b
487
486
  a + b
488
487
  end
@@ -495,6 +494,7 @@ To make it work, you'll need to subclass `View`, `StaticView` and `Group` too. T
495
494
  class View < View; include Mixin; end
496
495
  class Group < Group; include Mixin; end
497
496
  end
497
+ ...
498
498
 
499
499
  >>> data = WorkingTable.new(a: 1..3, b: 4..6)
500
500
  >>> data.a_plus_b
@@ -510,6 +510,8 @@ To make it work, you'll need to subclass `View`, `StaticView` and `Group` too. T
510
510
  >>> data.group_by{{odd: a % 2}}.each do
511
511
  p "when a % 2 == #{@K[:odd]}, a + b == #{a_plus_b.to_a}"
512
512
  end
513
+ ...
514
+
513
515
  "when a % 2 == 1, a + b == [5, 9]"
514
516
  "when a % 2 == 0, a + b == [7]"
515
517
  ```
@@ -1,6 +1,12 @@
1
1
  require 'narray'
2
2
 
3
3
  class ObjectTable::Column < NArray
4
+ NARRAY_ENUMERATORS = [:to_a, :collect, :map, :sort, :all?, :any?, :max, :min, :none?]
5
+
6
+ NARRAY_ENUMERATORS.each{|method| alias_method "narray_#{method}", method}
7
+ include Enumerable
8
+ NARRAY_ENUMERATORS.each{|method| alias_method method, "narray_#{method}"}
9
+
4
10
  def self.make(value)
5
11
  value = case value
6
12
  when self
@@ -25,17 +31,25 @@ class ObjectTable::Column < NArray
25
31
  self.class.make super
26
32
  end
27
33
 
28
- def [](*)
34
+ def [](*a)
29
35
  result = super
30
36
  result.is_a?(NArray) ? self.class.make(result) : result
31
37
  end
32
38
 
33
- def get_rows(rows, slice=false)
34
- if slice
35
- slice(*([nil] * (rank - 1)), rows)
36
- else
37
- self[*([nil] * (rank - 1)), rows]
39
+ def []=(*args)
40
+ if (args[-1].is_a?(Array) or args[-1].is_a?(NArray)) and args[-1].empty? and self.empty?
41
+ return args[-1]
38
42
  end
43
+
44
+ super
45
+ end
46
+
47
+ def to_object
48
+ to_type('object')
49
+ end
50
+
51
+ def to_bool
52
+ map{|i| i ? 1 : 0}.to_type('byte')
39
53
  end
40
54
 
41
55
  def uniq
@@ -77,4 +91,20 @@ class ObjectTable::Column < NArray
77
91
  # # end
78
92
  # end
79
93
 
94
+ def stack(*others)
95
+ columns = [self] + others
96
+ new_rows = columns.map{|x| x.shape[-1]}.reduce(:+)
97
+ new_col = self.class.new(typecode, *shape[0...-1], new_rows)
98
+
99
+ padding = [nil] * (rank - 1)
100
+
101
+ row = 0
102
+ columns.each do |col|
103
+ new_col[*padding, row ... (row + col.shape[-1])] = col
104
+ row += col.shape[-1]
105
+ end
106
+
107
+ new_col
108
+ end
109
+
80
110
  end
@@ -1,32 +1,67 @@
1
1
  require_relative 'group'
2
+ require_relative 'table_child'
2
3
 
3
4
  class ObjectTable::Grouped
4
5
  DEFAULT_VALUE_PREFIX = 'v_'
6
+ include ObjectTable::TableChild
5
7
 
6
- def initialize(parent, names, groups)
8
+ def initialize(parent, *names, &grouper)
7
9
  @parent = parent
10
+ @grouper = grouper
8
11
  @names = names
9
- @groups = groups
10
12
  end
11
13
 
12
- def each(&block)
13
- group_cls = @parent.class::Table::Group
14
14
 
15
- @groups.each do |k, v|
16
- names = @names.zip(k)
17
- group_cls.new(@parent, Hash[names], v).apply &block
15
+ def _groups
16
+ names, keys = _keys()
17
+ groups = (0...@parent.nrows).zip(keys).group_by{|row, key| key}
18
+ groups.each do |k, v|
19
+ groups[k] = NArray.to_na(v.map(&:first))
20
+ end
21
+ [names, groups]
22
+ end
23
+
24
+ def _keys
25
+ if @names.empty?
26
+ keys = @parent.apply(&@grouper)
27
+ raise 'Group keys must be hashes' unless keys.is_a?(Hash)
28
+ keys = ObjectTable::BasicGrid.new.replace keys
29
+ else
30
+ keys = ObjectTable::BasicGrid[@names.map{|n| [n, @parent.get_column(n)]}]
31
+ end
32
+
33
+ keys._ensure_uniform_columns!(@parent.nrows)
34
+ names = keys.keys
35
+ keys = keys.values.map(&:to_a).transpose
36
+ [names, keys]
37
+ end
38
+
39
+ %w{ all? any? collect collect_concat count flat_map map none? one? }.each do |method|
40
+ define_method(method) do |*args, &block|
41
+ names, groups = _groups()
42
+ groups.send(method, *args) do |k, v|
43
+ keys = names.zip(k)
44
+ __group_cls__.new(@parent, Hash[keys], v).apply &block
45
+ end
46
+ end
47
+ end
48
+
49
+ def each(&block)
50
+ names, groups = _groups()
51
+ groups.each do |k, v|
52
+ keys = names.zip(k)
53
+ __group_cls__.new(@parent, Hash[keys], v).apply &block
18
54
  end
19
55
  @parent
20
56
  end
21
57
 
22
58
  def apply(&block)
23
- table_cls = @parent.class::Table
24
- group_cls = table_cls::Group
25
- value_key = self.class._generate_name(DEFAULT_VALUE_PREFIX, @names).to_sym
59
+ names, groups = _groups()
60
+ value_key = self.class._generate_name(DEFAULT_VALUE_PREFIX, names).to_sym
26
61
 
27
- data = @groups.map do |k, v|
28
- names = @names.zip(k)
29
- value = group_cls.new(@parent, Hash[names], v).apply &block
62
+ data = groups.map do |k, v|
63
+ keys = names.zip(k)
64
+ value = __group_cls__.new(@parent, Hash[keys], v).apply &block
30
65
 
31
66
  if value.is_a?(ObjectTable::TableMethods)
32
67
  value = value.columns
@@ -34,14 +69,14 @@ class ObjectTable::Grouped
34
69
 
35
70
  grid = case value
36
71
  when ObjectTable::BasicGrid
37
- ObjectTable::BasicGrid[names].merge!(value)
72
+ ObjectTable::BasicGrid[keys].merge!(value)
38
73
  else
39
- ObjectTable::BasicGrid[names + [[value_key, value]]]
74
+ ObjectTable::BasicGrid[keys + [[value_key, value]]]
40
75
  end
41
76
  grid._ensure_uniform_columns!
42
77
  end
43
78
 
44
- table_cls.stack(*data)
79
+ __table_cls__.stack(*data)
45
80
  end
46
81
 
47
82
  def self._generate_name(prefix, existing_names)
@@ -4,7 +4,11 @@ class ObjectTable::MaskedColumn < ObjectTable::Column
4
4
  attr_accessor :indices, :parent, :padded_dims
5
5
 
6
6
  def self.mask(parent, indices)
7
- padded_dims = [nil] * (parent.rank - 1)
7
+ if parent.rank > 1
8
+ padded_dims = [nil] * (parent.rank - 1)
9
+ else
10
+ padded_dims = []
11
+ end
8
12
  masked = parent.slice(*padded_dims, indices)
9
13
 
10
14
  if masked.rank <= 0
@@ -28,27 +32,21 @@ class ObjectTable::MaskedColumn < ObjectTable::Column
28
32
  alias_method :super_slice_assign, :[]=
29
33
 
30
34
  def []=(*keys, value)
31
- unless (value.is_a?(Array) or value.is_a?(NArray)) and value.empty?
35
+ unless parent.nil? or ((value.is_a?(Array) or value.is_a?(NArray)) and value.empty?)
32
36
  parent[*padded_dims, indices[*keys]] = value
33
- super
34
37
  end
38
+ super
35
39
  end
36
40
 
37
41
  # make destructive methods affect parent
38
42
  %w{ fill! indgen! indgen random! map! collect! conj! imag= mod! add! div! sbt! mul! }.each do |op|
39
43
  define_method(op) do |*args, &block|
40
44
  result = super(*args, &block)
41
- parent[*padded_dims, indices] = result
45
+ parent[*padded_dims, indices] = result if parent
42
46
  result
43
47
  end
44
48
  end
45
49
 
46
- %w{ + - / * % ** to_type not abs -@ ~ }.each do |op|
47
- define_method(op) do |*args|
48
- ObjectTable::Column.cast super(*args)
49
- end
50
- end
51
-
52
50
  def clone
53
51
  ObjectTable::Column.cast(self).clone
54
52
  end
@@ -0,0 +1,72 @@
1
+ module ObjectTable::Printable
2
+
3
+ def self.get_printable_column(name, column)
4
+ padding = [nil] * (column.rank - 1)
5
+ rows = column.shape[-1].times.map do |i|
6
+ row = column[*padding, i]
7
+ str = row.is_a?(NArray) ? row.inspect.partition("\n")[-1].strip : row.inspect
8
+ str.split("\n")
9
+ end
10
+
11
+ name = name.to_s
12
+ [[name]] + rows + [[name]]
13
+ end
14
+
15
+ def self.get_printable_line_numbers(numbers)
16
+ rows = numbers.map do |i|
17
+ ["#{i}: "]
18
+ end
19
+
20
+ [['']] + rows + [['']]
21
+ end
22
+
23
+ def inspect(max_section = 5, col_padding = 2)
24
+ header = "#{self.class}(#{nrows}, #{ncols})\n"
25
+
26
+ return (header + "(empty table)") if ncols == 0
27
+ return (header + "(empty table with columns: #{colnames.join(", ")})") if nrows == 0
28
+
29
+ printed_columns = []
30
+
31
+ if nrows > max_section * 2
32
+ head = (0...max_section)
33
+ tail = ((nrows - max_section)...nrows)
34
+
35
+ printed_columns.push ObjectTable::Printable.get_printable_line_numbers(head.to_a + tail.to_a)
36
+
37
+ printed_columns += columns.map do |name, c|
38
+ padding = [nil] * (c.rank - 1)
39
+ c = c.slice(*padding, [head, tail])
40
+ ObjectTable::Printable.get_printable_column(name, c)
41
+ end
42
+ else
43
+ max_section = -1
44
+ printed_columns.push ObjectTable::Printable.get_printable_line_numbers(0...nrows)
45
+ printed_columns += columns.map do |name, c|
46
+ ObjectTable::Printable.get_printable_column(name, c)
47
+ end
48
+ end
49
+
50
+ widths = printed_columns.map{|row| row.flat_map{|c| c.map(&:length)}.max + col_padding}
51
+
52
+ header + printed_columns.transpose.each_with_index.map do |row, index|
53
+ height = row.map(&:length).max
54
+
55
+ row = row.zip(widths).map do |cell, width|
56
+ cell += [" "] * (height - cell.length)
57
+ cell.map{|i| i.rjust(width)}
58
+ end
59
+
60
+ row = row.transpose.map{|i| i.join('')}.join("\n")
61
+
62
+ if index == max_section
63
+ row += "\n" + '-'*widths.reduce(:+)
64
+ end
65
+ row
66
+ end.join("\n")
67
+
68
+ rescue NoMethodError => e
69
+ raise Exception.new(e)
70
+ end
71
+
72
+ end
@@ -0,0 +1,19 @@
1
+ module ObjectTable::TableChild
2
+
3
+ def __static_view_cls__
4
+ @__static_view_cls__ ||= @parent.__static_view_cls__
5
+ end
6
+
7
+ def __view_cls__
8
+ @__view_cls__ ||= @parent.__view_cls__
9
+ end
10
+
11
+ def __group_cls__
12
+ @__group_cls__ ||= @parent.__group_cls__
13
+ end
14
+
15
+ def __table_cls__
16
+ @__table_cls__ ||= @parent.__table_cls__
17
+ end
18
+
19
+ end
@@ -1,11 +1,8 @@
1
1
  require 'forwardable'
2
+ require_relative 'printable'
2
3
 
3
4
  module ObjectTable::TableMethods
4
- # this line is important!! which classes to use to make Views/StaticViews/Groups
5
- # are taken from this constant, e.g. Table::View
6
- Table = ObjectTable
7
-
8
-
5
+ include ObjectTable::Printable
9
6
  extend Forwardable
10
7
 
11
8
  attr_reader :R
@@ -77,23 +74,23 @@ module ObjectTable::TableMethods
77
74
  end
78
75
 
79
76
  if result.is_a? ObjectTable::BasicGrid
80
- result = self.class::Table.new(result)
77
+ result = __table_cls__.new(result)
81
78
  end
82
79
  result
83
80
  end
84
81
 
85
82
  def where(&block)
86
- self.class::Table::View.new(self, &block)
83
+ __view_cls__.new(self, &block)
87
84
  end
88
85
 
89
86
  def group_by(*args, &block)
90
- ObjectTable::TempGrouped.new(self, *args, &block)
87
+ ObjectTable::Grouped.new(self, *args, &block)
91
88
  end
92
89
 
93
90
  def sort_by(*keys)
94
91
  sort_index = _get_sort_index(keys)
95
92
  cols = ObjectTable::BasicGrid[columns.map{|k, v| [k, v[sort_index]]}]
96
- self.class::Table.new(cols)
93
+ __table_cls__.new(cols)
97
94
  end
98
95
 
99
96
  def method_missing(meth, *args, &block)
@@ -104,51 +101,9 @@ module ObjectTable::TableMethods
104
101
  super or has_column?(meth)
105
102
  end
106
103
 
107
- def inspect(max_section = 5)
108
- header = "#{self.class}(#{nrows}, #{ncols})\n"
109
- printed_columns = []
110
-
111
- if nrows > max_section * 2
112
- head = (0...max_section)
113
- tail = ((nrows - max_section)...nrows)
114
-
115
- printed_columns.push [''] + (head.to_a + tail.to_a).map{|i| "#{i}: "} + ['']
116
- printed_columns += columns.map do |name, c|
117
- c = c.get_rows([head, tail], true)
118
- strings = c.shape[-1].times.map do |i|
119
- row = c.get_rows(i)
120
- row.is_a?(NArray) ? row.inspect.partition("\n")[-1].strip : row.inspect
121
- end
122
-
123
- [name.to_s] + strings + [name.to_s]
124
- end
125
- else
126
- max_section = -1
127
- printed_columns.push [''] + (0...nrows).map{|i| "#{i}: "} + ['']
128
- printed_columns += columns.map do |name, c|
129
- [name.to_s] + c.to_a.map(&:inspect) + [name.to_s]
130
- end
131
- end
132
-
133
- widths = printed_columns.map{|col| col.map(&:length).max + 2}
134
-
135
- header + printed_columns.transpose.each_with_index.map do |row, index|
136
- row = row.zip(widths).map do |cell, width|
137
- cell.rjust(width)
138
- end.join('')
139
-
140
- if index == max_section
141
- row += "\n" + '-'*widths.reduce(:+)
142
- end
143
- row
144
- end.join("\n")
145
- rescue NoMethodError => e
146
- raise Exception.new(e)
147
- end
148
-
149
104
  def clone
150
105
  cols = ObjectTable::BasicGrid[columns.map{|k, v| [k, v.clone]}]
151
- self.class::Table.new(cols)
106
+ __table_cls__.new(cols)
152
107
  end
153
108
 
154
109
  def _get_sort_index(columns)
@@ -1,3 +1,3 @@
1
1
  class ObjectTable
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -3,7 +3,6 @@ require_relative 'view_methods'
3
3
  require_relative 'masked_column'
4
4
 
5
5
  class ObjectTable::View
6
- Table = ObjectTable
7
6
  include ObjectTable::ViewMethods
8
7
 
9
8
  extend Forwardable
@@ -24,12 +23,12 @@ class ObjectTable::View
24
23
  alias_method :[], :get_column
25
24
 
26
25
  def make_view
27
- self.class::Table::StaticView.new @parent, indices
26
+ __static_view_cls__.new @parent, indices
28
27
  end
29
28
 
30
29
  def clone
31
30
  cols = ObjectTable::BasicGrid[@parent.columns.map{|k, v| [k, v[indices]]}]
32
- self.class::Table.new(cols)
31
+ __table_cls__.new(cols)
33
32
  end
34
33
 
35
34
  def inspect(*args)
@@ -1,9 +1,11 @@
1
1
  require 'forwardable'
2
2
  require_relative 'table_methods'
3
+ require_relative 'table_child'
3
4
 
4
5
  module ObjectTable::ViewMethods
5
6
  extend Forwardable
6
7
  include ObjectTable::TableMethods
8
+ include ObjectTable::TableChild
7
9
 
8
10
  def columns
9
11
  ObjectTable::BasicGrid[@parent.columns.map{|k, v| [k, ObjectTable::MaskedColumn.mask(v, indices)]}]
data/lib/object_table.rb CHANGED
@@ -5,7 +5,6 @@ require_relative "object_table/view"
5
5
  require_relative "object_table/static_view"
6
6
  require_relative "object_table/column"
7
7
  require_relative "object_table/grouped"
8
- require_relative "object_table/temp_grouped"
9
8
 
10
9
  class ObjectTable
11
10
  include TableMethods
@@ -32,7 +31,7 @@ class ObjectTable
32
31
  end
33
32
 
34
33
  def stack!(*others)
35
- new_values = Hash.new{ [] }
34
+ new_values = Hash[colnames.zip(ncols.times.map{[]})]
36
35
 
37
36
  others.each do |x|
38
37
  case x
@@ -47,15 +46,17 @@ class ObjectTable
47
46
  raise 'Mismatch in column names' unless (colnames | x.keys) == (colnames & x.keys)
48
47
 
49
48
  x.each do |k, v|
50
- v = v.to_a if v.is_a? NArray
51
- new_values[k] += v
49
+ new_values[k].push NArray.to_na(v)
52
50
  end
53
51
  end
54
52
 
55
53
  return self if new_values.empty?
54
+ new_rows = new_values.values.first.map{|x| x.shape[-1]}.reduce(:+)
55
+ return self unless (new_rows and new_rows != 0)
56
+ new_rows += nrows
56
57
 
57
58
  new_values.each do |k, v|
58
- @columns[k] = ObjectTable::Column.make(@columns[k].to_a + v)
59
+ @columns[k] = @columns[k].stack(*v)
59
60
  end
60
61
  self
61
62
  end
@@ -84,4 +85,89 @@ class ObjectTable
84
85
  self
85
86
  end
86
87
 
88
+ def join(other, key, options={})
89
+ type = (options[:type] || 'inner')
90
+ key = [key] unless key.is_a?(Array)
91
+
92
+ lkeys = key.map{|k| get_column(k).to_a}.transpose
93
+ rkeys = key.map{|k| other[k].to_a}.transpose
94
+
95
+ rgroups = rkeys.each_with_index.group_by(&:first)
96
+ rgroups.each{|k, v| rgroups[k] = v.transpose[-1]}
97
+
98
+ rindex = rgroups.values_at(*lkeys)
99
+ lindex = lkeys.each_with_index.zip(rindex).flat_map{|(k, i), r| [i] * r.length if r}
100
+ lindex.compact!
101
+
102
+ right_cols = other.colnames - key
103
+ left_cols = colnames
104
+
105
+ if type == 'left' or type == 'outer'
106
+ lmissing = NArray.to_na(rindex.map{|x| x ? 0 : 1}).where.to_a
107
+ lindex += lmissing
108
+ rindex += [-1] * lmissing.length
109
+ end
110
+
111
+ if type == 'right' or type == 'outer'
112
+ left_cols -= key
113
+ rmissing = rgroups.values - rindex
114
+ rmissing.flatten!
115
+ lindex += [-1] * rmissing.length
116
+ rindex += rmissing
117
+ end
118
+
119
+ rindex.flatten!.compact!
120
+
121
+ lindex = NArray.to_na(lindex)
122
+ rindex = NArray.to_na(rindex)
123
+ lblank = lindex.eq(-1)
124
+ rblank = rindex.eq(-1)
125
+
126
+ left = left_cols.map do |k|
127
+ col = get_column(k)
128
+ padding = [nil] * (col.rank - 1)
129
+ col = col[*padding, lindex]
130
+ col[*padding, lblank] = [nil]
131
+ [k, col]
132
+ end
133
+
134
+ right = right_cols.map do |k|
135
+ col = other[k]
136
+ padding = [nil] * (col.rank - 1)
137
+ col = col[*padding, rindex]
138
+ col[*padding, rblank] = [nil]
139
+ [k, col]
140
+ end
141
+
142
+ keys = []
143
+ if type == 'right' or type == 'outer'
144
+ keys = key.map do |k|
145
+ col = get_column(k)
146
+ padding = [nil] * (col.rank - 1)
147
+ col = col[*padding, lindex]
148
+ col[*padding, lblank] = other[k][*padding, rmissing]
149
+ [k, col]
150
+ end
151
+ end
152
+
153
+ self.class.new(keys + left + right)
154
+ end
155
+
156
+
157
+ def __static_view_cls__
158
+ self.class::StaticView
159
+ end
160
+
161
+ def __view_cls__
162
+ self.class::View
163
+ end
164
+
165
+ def __group_cls__
166
+ self.class::Group
167
+ end
168
+
169
+ def __table_cls__
170
+ self.class
171
+ end
172
+
87
173
  end