Class: NTable::Table

Inherits:
Object
  • Object
show all
Defined in:
lib/ntable/table.rb,
lib/ntable/construction.rb

Overview

An N-dimensional table object, comprising structure and values.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(structure_, data_ = {}) ⇒ Table

This is a low-level table creation mechanism. Generally, you should use ::NTable.create instead.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ntable/table.rb', line 51

def initialize(structure_, data_={})
  @structure = structure_
  @structure.lock!
  size_ = @structure.size
  if (load_ = data_[:load])
    load_size_ = load_.size
    if load_size_ > size_
      @vals = load_[0, size_]
    elsif load_size_ < size_
      @vals = load_ + ::Array.new(size_ - load_size_, data_[:fill])
    else
      @vals = load_.dup
    end
  elsif (acquire_ = data_[:acquire])
    @vals = acquire_
  else
    @vals = ::Array.new(size_, data_[:fill])
  end
  @offset = data_[:offset].to_i
  @parent = data_[:parent]
end

Instance Attribute Details

#structureObject (readonly)

The Structure of this table



148
149
150
# File 'lib/ntable/table.rb', line 148

def structure
  @structure
end

Class Method Details

.from_json_object(json_) ⇒ Object

Deprecated synonym for ::NTable.from_json_object



301
302
303
# File 'lib/ntable/construction.rb', line 301

def from_json_object(json_)
  ::NTable.from_json_object(json_)
end

.from_nested_object(obj_, field_opts_ = [], opts_ = {}) ⇒ Object

Deprecated synonym for ::NTable.from_nested_object



315
316
317
# File 'lib/ntable/construction.rb', line 315

def from_nested_object(obj_, field_opts_=[], opts_={})
  ::NTable.from_nested_object(obj_, field_opts_, opts_)
end

.parse_json(json_) ⇒ Object

Deprecated synonym for ::NTable.parse_json



308
309
310
# File 'lib/ntable/construction.rb', line 308

def parse_json(json_)
  ::NTable.parse_json(json_)
end

Instance Method Details

#==(rhs_) ⇒ Object

Returns true if the two tables are equivalent in data but not necessarily parentage. The structure of a shared slice may be equivalent, in this sense, to the “same” table created from scratch with no parent.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ntable/table.rb', line 116

def ==(rhs_)
  if self.equal?(rhs_)
    true
  elsif rhs_.is_a?(Table)
    if rhs_.parent || self.parent
      if @structure == rhs_.structure
        riter_ = rhs_.each
        liter_ = self.each
        @structure.size.times do
          return false unless liter_.next == riter_.next
        end
        true
      else
        false
      end
    else
      rhs_.structure.eql?(@structure) && rhs_.instance_variable_get(:@vals).eql?(@vals)
    end
  else
    false
  end
end

#_compacted_valsObject

:nodoc:



652
653
654
655
656
657
658
659
# File 'lib/ntable/table.rb', line 652

def _compacted_vals  # :nodoc:
  vec_ = ::Array.new(@structure.dim, 0)
  ::Array.new(@structure.size) do
    val_ = @vals[@offset + @structure._compute_offset_for_vector(vec_)]
    @structure._inc_vector(vec_)
    val_
  end
end

#_offset_for_args(args_) ⇒ Object

:nodoc:



662
663
664
665
666
667
668
# File 'lib/ntable/table.rb', line 662

def _offset_for_args(args_)  # :nodoc:
  if args_.size == 1
    first_ = args_.first
    args_ = first_ if first_.is_a?(::Hash) || first_.is_a?(::Array)
  end
  @structure._offset(args_)
end

#_to_nested_obj(aidx_, vec_, opts_) ⇒ Object

:nodoc:



632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
# File 'lib/ntable/table.rb', line 632

def _to_nested_obj(aidx_, vec_, opts_)  # :nodoc:
  exclude_ = opts_.include?(:exclude_value)
  exclude_value_ = opts_[:exclude_value] if exclude_
  axis_ = @structure.axis(aidx_).axis_object
  result_ = IndexedAxis === axis_ ? [] : {}
  (0...axis_.size).map do |i_|
    vec_[aidx_] = i_
    val_ = if aidx_ + 1 == vec_.size
      @vals[@offset + @structure._compute_offset_for_vector(vec_)]
    else
      _to_nested_obj(aidx_ + 1, vec_, opts_)
    end
    if !exclude_ || !val_.eql?(exclude_value_)
      result_[axis_.label(i_)] = val_
    end
  end
  result_
end

#all_axesObject

Returns an array of AxisInfo objects representing all the axes of the structure of this table.



192
193
194
# File 'lib/ntable/table.rb', line 192

def all_axes
  @structure.all_axes
end

#axis(axis_) ⇒ Object

Returns the AxisInfo object representing the given axis. The axis must be specified by 0-based index or by name string. Returns nil if there is no such axis.



184
185
186
# File 'lib/ntable/table.rb', line 184

def axis(axis_)
  @structure.axis(axis_)
end

#decompose(*axes_) ⇒ Object

Decomposes this table, breaking it into a set of lower-dimensional tables, all arranged in a table. For example, you could decompose a two-dimensional table into a one-dimensional table of one-dimensional tables. You must provide an array of axis specifications (indexes or names) identifying which axes should be part of the lower-dimensional tables.



522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
# File 'lib/ntable/table.rb', line 522

def decompose(*axes_)
  axes_ = axes_.flatten
  axis_indexes_ = []
  axes_.each do |a_|
    if (ainfo_ = @structure.axis(a_))
      axis_indexes_ << ainfo_.axis_index
    else
      raise UnknownAxisError, "Unknown axis: #{a_.inspect}"
    end
  end
  inner_struct_ = @structure.substructure_including(axis_indexes_)
  outer_struct_ = @structure.substructure_omitting(axis_indexes_)
  vec_ = ::Array.new(outer_struct_.dim, 0)
  tables_ = (0...outer_struct_.size).map do |i_|
    t_ = Table.new(inner_struct_, :acquire => @vals,
      :offset => @offset + outer_struct_._compute_offset_for_vector(vec_),
      :parent => self)
    outer_struct_._inc_vector(vec_)
    t_
  end
  Table.new(outer_struct_.unlocked_copy, :acquire => tables_)
end

#decompose_reduce(decompose_axes_, *reduce_args_, &block_) ⇒ Object

Decompose this table using the given axes, and then reduce each inner table, returning a table of the reduction values.



549
550
551
# File 'lib/ntable/table.rb', line 549

def decompose_reduce(decompose_axes_, *reduce_args_, &block_)
  decompose(decompose_axes_).map{ |sub_| sub_.reduce(*reduce_args_, &block_) }
end

#decompose_reduce_with_position(decompose_axes_, *reduce_args_, &block_) ⇒ Object

Decompose this table using the given axes, and then reduce each inner table with position, returning a table of the reduction values.



557
558
559
# File 'lib/ntable/table.rb', line 557

def decompose_reduce_with_position(decompose_axes_, *reduce_args_, &block_)
  decompose(decompose_axes_).map{ |sub_| sub_.reduce_with_position(*reduce_args_, &block_) }
end

#degenerate?Boolean

True if this is a degenerate (scalar) table with a single cell and no dimensions.

Returns:

  • (Boolean)


175
176
177
# File 'lib/ntable/table.rb', line 175

def degenerate?
  @structure.degenerate?
end

#dimObject

The number of dimensions/axes in this table.



160
161
162
# File 'lib/ntable/table.rb', line 160

def dim
  @structure.dim
end

#each(&block_) ⇒ Object

Iterate over all table cells, in order, and call the given block. If no block is given, an ::Enumerator is returned.



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/ntable/table.rb', line 325

def each(&block_)
  if @parent
    if block_given?
      vec_ = ::Array.new(@structure.dim, 0)
      @structure.size.times do
        yield(@vals[@offset + @structure._compute_offset_for_vector(vec_)])
        @structure._inc_vector(vec_)
      end
    else
      enum_for
    end
  else
    @vals.each(&block_)
  end
end

#each_with_positionObject

Iterate over all table cells, and call the given block with the value and the Structure::Position for the cell.



345
346
347
348
349
350
351
352
353
# File 'lib/ntable/table.rb', line 345

def each_with_position
  vec_ = ::Array.new(@structure.dim, 0)
  @structure.size.times do
    yield(@vals[@offset + @structure._compute_offset_for_vector(vec_)],
      Structure::Position.new(@structure, vec_))
    @structure._inc_vector(vec_)
  end
  self
end

#empty?Boolean

True if this table has no cells.

Returns:

  • (Boolean)


167
168
169
# File 'lib/ntable/table.rb', line 167

def empty?
  @structure.empty?
end

#eql?(rhs_) ⇒ Boolean

Returns true if the two tables are equivalent, both in the data and in the parentage. The structure of a shared slice is not equivalent, in this sense, to the “same” table created from scratch, because the former is a “sparse” subset of a parent whereas the latter is not.

Returns:

  • (Boolean)


102
103
104
105
106
107
108
# File 'lib/ntable/table.rb', line 102

def eql?(rhs_)
  self.equal?(rhs_) ||
    rhs_.is_a?(Table) &&
    @structure.eql?(rhs_.structure) && @parent.eql?(rhs_.parent) &&
    rhs_.instance_variable_get(:@offset) == @offset &&
    rhs_.instance_variable_get(:@vals).eql?(@vals)
end

#fill!(value_) ⇒ Object

Fill all table cells with the given value.

You cannot load values into a table with a parent. Instead, you must modify the parent, and those changes will be reflected in the child.

Returns self so calls can be chained.

Raises:



315
316
317
318
319
# File 'lib/ntable/table.rb', line 315

def fill!(value_)
  raise TableLockedError if @parent
  @vals.fill(value_)
  self
end

#get(*args_) ⇒ Object Also known as: []

Returns the value in the cell at the given coordinates, which may be given as labels or as 0-based row indexes. You may specify the cell as an array of coordinates, or as a hash mapping axis name or axis index to coordinate.

For example, for a typical database result set with an axis called “row” of numerically identified rows, and an axis called “col” with string-named columns, these call sequences are equivalent:

get(3, 'name')
get([3, 'name'])
get(:row => 3, :col => 'name')
get(0 => 3, 1 => 'name')

Alternately, you can provide row numbers (0-based) instead. If, for example, “name” is the second column (corresponding to index 1), then the following queries are also equivalent:

get(3, 1)
get(:row => 3, :col => 1)

For axes whose labels are integers (for example, a numerically identified axis such as IndexedAxis), it is ambiguous whether a value is intended as a label or an index. In this case, NTable defalts to assuming the value is a label. If you want to force a value to be treated as a 0-based row index, wrap it in a call to NTable.index(), as follows:

get(NTable.index(3), 1)

Raises NoSuchCellError if the coordinates do not exist.



239
240
241
242
243
244
245
# File 'lib/ntable/table.rb', line 239

def get(*args_)
  offset_ = _offset_for_args(args_)
  unless offset_
    raise NoSuchCellError
  end
  @vals[@offset + offset_]
end

#hashObject

Standard hash value



142
143
144
# File 'lib/ntable/table.rb', line 142

def hash
  @structure.hash + @vals.hash + @offset.hash + @parent.hash
end

#include?(*args_) ⇒ Boolean

Returns a boolean indicating whether the given cell coordinates actually exist. The arguments use the same syntax as for Table#get.

Returns:

  • (Boolean)


252
253
254
# File 'lib/ntable/table.rb', line 252

def include?(*args_)
  _offset_for_args(args_) ? true : false
end

#initialize_copy(other_) ⇒ Object

:nodoc:



74
75
76
77
78
79
80
81
82
83
84
# File 'lib/ntable/table.rb', line 74

def initialize_copy(other_)  # :nodoc:
  if other_.parent
    @structure = other_.structure.unlocked_copy
    @structure.lock!
    @vals = other_._compacted_vals
    @offset = 0
    @parent = nil
  else
    initialize(other_.structure, :load => other_.instance_variable_get(:@vals))
  end
end

#inspectObject Also known as: to_s

Basic output.



89
90
91
92
# File 'lib/ntable/table.rb', line 89

def inspect
  axes_ = @structure.all_axes.map{ |a_| "#{a_.axis_name}:#{a_.axis_object.class.name.sub('NTable::', '')}" }
  "#<#{self.class}:0x#{object_id.to_s(16)} #{axes_.join(', ')}#{@parent ? ' (sub)' : ''} #{@vals.inspect}>"
end

#load!(vals_) ⇒ Object

Load an array of values into the table cells, in order.

You cannot load values into a table with a parent. Instead, you must modify the parent, and those changes will be reflected in the child.

Returns self so calls can be chained.

Raises:



293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/ntable/table.rb', line 293

def load!(vals_)
  raise TableLockedError if @parent
  is_ = vals_.size
  vs_ = @vals.size
  if is_ < vs_
    @vals = vals_.dup + @vals[is_..-1]
  elsif is_ > vs_
    @vals = vals_[0,vs_]
  else
    @vals = vals_.dup
  end
  self
end

#map(&block_) ⇒ Object

Return a new table whose structure is the same as this table, and whose values are given by mapping the current table’s values through the given block.



360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/ntable/table.rb', line 360

def map(&block_)
  if @parent
    vec_ = ::Array.new(@structure.dim, 0)
    nvals_ = (0...@structure.size).map do |i_|
      val_ = yield(@vals[@offset + @structure._compute_offset_for_vector(vec_)])
      @structure._inc_vector(vec_)
      val_
    end
    Table.new(@structure.unlocked_copy, :acquire => nvals_)
  else
    Table.new(@structure, :acquire => @vals.map(&block_))
  end
end

#map!(&block_) ⇒ Object

Modify the current table in place, mapping values through the given block.

You cannot set values in a table with a parent. Instead, you must modify the parent, and those changes will be reflected in the child.

Raises:



397
398
399
400
401
# File 'lib/ntable/table.rb', line 397

def map!(&block_)
  raise TableLockedError if @parent
  @vals.map!(&block_)
  self
end

#map_with_positionObject

Same as Table#map except the block is passed the current table’s value for each cell, and the cell’s Structure::Position.



378
379
380
381
382
383
384
385
386
387
388
# File 'lib/ntable/table.rb', line 378

def map_with_position
  nstructure_ = @structure.parent ? @structure.unlocked_copy : @structure
  vec_ = ::Array.new(@structure.dim, 0)
  nvals_ = (0...@structure.size).map do |i_|
    nval_ = yield(@vals[@offset + @structure._compute_offset_for_vector(vec_)],
      Structure::Position.new(@structure, vec_))
    @structure._inc_vector(vec_)
    nval_
  end
  Table.new(nstructure_, :acquire => nvals_)
end

#map_with_position!Object

Modify the current table in place, mapping values through the given block, which takes both the old value and the Structure::Position.

You cannot set values in a table with a parent. Instead, you must modify the parent, and those changes will be reflected in the child.

Raises:



410
411
412
413
414
415
416
417
418
419
# File 'lib/ntable/table.rb', line 410

def map_with_position!
  raise TableLockedError if @parent
  vec_ = ::Array.new(@structure.dim, 0)
  @vals.map! do |val_|
    nval_ = yield(val_, Structure::Position.new(@structure, vec_))
    @structure._inc_vector(vec_)
    nval_
  end
  self
end

#parentObject

Return the parent of this table. A table with a parent shares the parent’s data, and cannot have its data modified directly. Instead, if the parent table is modified, the changes are reflected in the child. Returns nil if this table has no parent.



202
203
204
# File 'lib/ntable/table.rb', line 202

def parent
  @parent
end

#reduce(*args_) ⇒ Object Also known as: inject

Performs a reduce on the entire table and returns the result. You may use one of the following call sequences:

reduce{ |accumulator, value| block }

Reduces using the given block as the reduction function. The first element in the table is used as the initial accumulator.

reduce(initial){ |accumulator, value| block }

Reduces using the given block as the reduction function, with the given initial value for the accumulator.

[reduce(:method-name)

Reduces using the given binary operator or method name as the
reduction function. If it is a method, the method must take a
single argument for the right-hand-side of the operation. The
first element in the table is used as the initial accumulator.

[reduce(initial, :method-name)

Reduces using the given binary operator or method name as the
reduction function. If it is a method, the method must take a
single argument for the right-hand-side of the operation. The
given initial accumulator value is used.


442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/ntable/table.rb', line 442

def reduce(*args_)
  nothing_ = ::Object.new
  if block_given?
    case args_.size
    when 1
      obj_ = args_.first
    when 0
      obj_ = nothing_
    else
      raise ::ArgumentError, "Wrong number of arguments"
    end
    each do |e_|
      if nothing_ == obj_
        obj_ = e_
      else
        obj_ = yield(obj_, e_)
      end
    end
  else
    sym_ = args_.pop
    case args_.size
    when 1
      obj_ = args_.first
    when 0
      obj_ = nothing_
    else
      raise ::ArgumentError, "Wrong number of arguments"
    end
    each do |e_|
      if nothing_ == obj_
        obj_ = e_
      else
        obj_ = obj_.send(sym_, e_)
      end
    end
  end
  nothing_ == obj_ ? nil : obj_
end

#reduce_with_position(*args_) ⇒ Object Also known as: inject_with_position

Performs a reduce on the entire table and returns the result. You may use one of the following call sequences:

reduce_with_position{ |accumulator, value, position| block }

Reduces using the given block as the reduction function. The first element in the table is used as the initial accumulator.

reduce_with_position(initial){ |accumulator, value, position| block }

Reduces using the given block as the reduction function, with the given initial value for the accumulator.



493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/ntable/table.rb', line 493

def reduce_with_position(*args_)
  nothing_ = ::Object.new
  case args_.size
  when 1
    obj_ = args_.first
  when 0
    obj_ = nothing_
  else
    raise ::ArgumentError, "Wrong number of arguments"
  end
  each_with_position do |val_, pos_|
    if nothing_ == obj_
      obj_ = val_
    else
      obj_ = yield(obj_, val_, pos_)
    end
  end
  nothing_ == obj_ ? nil : obj_
end

#set!(*args_, &block_) ⇒ Object Also known as: []=

Set the value in the cell at the given coordinates. If a block is given, it is passed the current value and expects the new value to be its result. If no block is given, the last argument is taken to be the new value. The remaining arguments identify the cell, using the same syntax as for Table#get.

You cannot set a value in a table with a parent. Instead, you must modify the parent, and those changes will be reflected in the child.

Raises NoSuchCellError if the coordinates do not exist.

Returns self so calls can be chained.

Raises:



270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/ntable/table.rb', line 270

def set!(*args_, &block_)
  raise TableLockedError if @parent
  value_ = block_ ? nil : args_.pop
  offset_ = _offset_for_args(args_)
  unless offset_
    raise NoSuchCellError
  end
  if block_
    value_ = block_.call(@vals[@offset + offset_])
  end
  @vals[@offset + offset_] = value_
  self
end

#shared_slice(hash_) ⇒ Object

Returns a table containing a “slice” of this table. The given hash should be keyed by axis indexes or axis names, and should provide specific values for zero or more dimensions, which provides the constraints for the slice.

Returns a slice table whose parent is this table. Because the slice table has a parent, it is not mutable because it shares data with this table. If this table has values modified, the slice data will reflect those changes.



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
# File 'lib/ntable/table.rb', line 572

def shared_slice(hash_)
  offset_ = @offset
  select_set_ = {}
  hash_.each do |k_, v_|
    if (ainfo_ = @structure.axis(k_))
      aindex_ = ainfo_.axis_index
      unless select_set_.include?(aindex_)
        lindex_ = ainfo_.index(v_)
        if lindex_
          offset_ += ainfo_.step * lindex_
          select_set_[aindex_] = true
        end
      end
    end
  end
  Table.new(@structure.substructure_omitting(select_set_.keys),
    :acquire => @vals, :offset => offset_, :parent => self)
end

#sizeObject

The number of cells in this table.



153
154
155
# File 'lib/ntable/table.rb', line 153

def size
  @structure.size
end

#slice(hash_) ⇒ Object

Returns a table containing a “slice” of this table. The given hash should be keyed by axis indexes or axis names, and should provide specific values for zero or more dimensions, which provides the constraints for the slice.

Returns a new table independent of this table. The new table can have cell values modified independently of this table.



600
601
602
# File 'lib/ntable/table.rb', line 600

def slice(hash_)
  shared_slice(hash_).dup
end

#to_jsonObject

Returns a JSON serialization of this table, as an unparsed string.



615
616
617
# File 'lib/ntable/table.rb', line 615

def to_json
  to_json_object.to_json
end

#to_json_objectObject

Returns a JSON serialization of this table, as an object. If you need to output a JSON string, you must unparse separately.



608
609
610
# File 'lib/ntable/table.rb', line 608

def to_json_object
  {'type' => 'ntable', 'axes' => @structure.to_json_array, 'values' => @parent ? _compacted_vals : @vals}
end

#to_nested_object(opts_ = {}) ⇒ Object

Returns a nested-object (nested arrays and hashes) serialization of this table.



623
624
625
626
627
628
629
# File 'lib/ntable/table.rb', line 623

def to_nested_object(opts_={})
  if @structure.degenerate?
    @vals[@offset]
  else
    _to_nested_obj(0, ::Array.new(@structure.dim, 0), opts_)
  end
end