class NTable::Structure

A Structure describes how a table is laid out: how many dimensions it has, how large the table is in each of those dimensions, what the axes are called, and how the coordinates are labeled/named. It is essentially an ordered list of named axes, along with some meta-information. A Structure is capable of performing computations such as determining how to look up data at a particular coordinate.

Generally, you create a new empty structure, and then use the add method to define the axes. Provide the axis by creating an axis object (for example, an instance of IndexedAxis or LabeledAxis.) You can also optionally provide a name for the axis.

Once a Structure is used by a table, it is locked and cannot be modified further. However, a Structure can be shared by multiple tables.

Many table operations (such as slice) automatically compute the structure of the result.

Public Class Methods

add(axis_, name_=nil) click to toggle source

Create a new structure and automatically add the given axis. See #add.

# File lib/ntable/structure.rb, line 744
def add(axis_, name_=nil)
  self.new.add(axis_, name_)
end
from_json_array(array_) click to toggle source

Deserialize a structure from the given JSON array

# File lib/ntable/structure.rb, line 751
def from_json_array(array_)
  self.new.from_json_array(array_)
end
new() click to toggle source

Create an empty Structure. An empty structure corresponds to a table with no axes and a single value (i.e. a scalar). Generally, you should add axes using the #add method before using the structure.

# File lib/ntable/structure.rb, line 267
def initialize
  @indexes = []
  @names = {}
  @size = 1
  @locked = false
  @parent = nil
end

Public Instance Methods

==(rhs_) click to toggle source

Returns true if the two structures are equivalent in the axes but not necessarily in the offsets. The structure of a shared slice is equivalent, in this sense, to the “same” structure created from scratch, even though one is a subview and the other is not.

# File lib/ntable/structure.rb, line 327
def ==(rhs_)
  if rhs_.equal?(self)
    true
  elsif rhs_.is_a?(Structure)
    rhs_indexes_ = rhs_.instance_variable_get(:@indexes)
    if rhs_indexes_.size == @indexes.size
      rhs_indexes_.each_with_index do |rhs_ai_, i_|
        lhs_ai_ = @indexes[i_]
        return false unless lhs_ai_.axis_object == rhs_ai_.axis_object && lhs_ai_.axis_name == rhs_ai_.axis_name
      end
      return true
    end
    false
  else
    false
  end
end
[](axis_) click to toggle source
Alias for: axis
add(axis_, name_=nil) click to toggle source

Append an axis to the configuration of this structure. You must provide the axis, as an object that duck-types EmptyAxis. You may also provide an optional name string.

# File lib/ntable/structure.rb, line 350
def add(axis_, name_=nil)
  raise StructureStateError, "Structure locked" if @locked
  name_ = name_ ? name_.to_s : nil
  ainfo_ = AxisInfo.new(axis_, @indexes.size, name_)
  @indexes << ainfo_
  @names[name_] = ainfo_ if name_
  @size *= axis_.size
  self
end
all_axes() click to toggle source

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

# File lib/ntable/structure.rb, line 437
def all_axes
  @indexes.dup
end
axis(axis_) click to toggle source

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.

# File lib/ntable/structure.rb, line 446
def axis(axis_)
  case axis_
  when ::Integer
    @indexes[axis_]
  else
    @names[axis_.to_s]
  end
end
Also aliased as: []
create(data_={}) click to toggle source

Create a new table using this structure as the structure. Note that this also has the side effect of locking this structure.

You can initialize the data using the following options:

:fill

Fill all cells with the given value.

:load

Load the cell data with the values from the given array, in order.

# File lib/ntable/structure.rb, line 593
def create(data_={})
  Table.new(self, data_)
end
degenerate?() click to toggle source

Returns true if this is a degenerate/scalar structure. That is, if the dimension is 0.

# File lib/ntable/structure.rb, line 429
def degenerate?
  @indexes.size == 0
end
dim() click to toggle source

Returns the number of axes/dimensions currently in this structure.

# File lib/ntable/structure.rb, line 421
def dim
  @indexes.size
end
each(&block_) click to toggle source

Iterate over the axes in order, yielding AxisInfo objects.

# File lib/ntable/structure.rb, line 459
def each(&block_)
  @indexes.each(&block_)
end
empty?() click to toggle source

Returns true if this structure implies an “empty” table, one with no cells. This happens only if at least one of the axes has a zero size.

# File lib/ntable/structure.rb, line 503
def empty?
  @size == 0
end
eql?(rhs_) click to toggle source

Returns true if the two structures are equivalent, both in the axes and in the parentage. The structure of a shared slice is not equivalent, in this sense, to the “same” structure created from scratch, because the former is a subview of a larger structure whereas the latter is not.

# File lib/ntable/structure.rb, line 314
def eql?(rhs_)
  rhs_.equal?(self) ||
    rhs_.is_a?(Structure) &&
    @parent.eql?(rhs_.instance_variable_get(:@parent)) &&
    @indexes.eql?(rhs_.instance_variable_get(:@indexes))
end
from_json_array(array_) click to toggle source

Use the given array to reconstitute a structure previously serialized using #to_json_array.

# File lib/ntable/structure.rb, line 560
def from_json_array(array_)
  if @indexes.size > 0
    raise StructureStateError, "There are already axes in this structure"
  end
  array_.each do |obj_|
    name_ = obj_['name']
    type_ = obj_['type'] || 'Empty'
    if type_ =~ %r^([a-z])(.*)$/
      mod_ = ::NTable.const_get("#{$1.upcase}#{$2}Axis")
    else
      mod_ = ::Kernel
      type_.split('::').each do |t_|
        mod_ = mod_.const_get(t_)
      end
    end
    axis_ = mod_.allocate
    axis_.from_json_object(obj_)
    add(axis_, name_)
  end
  self
end
inspect() click to toggle source

Basic output.

# File lib/ntable/structure.rb, line 301
def inspect
  axes_ = @indexes.map{ |a_| "#{a_.axis_name}:#{a_.axis_object.class.name.sub('NTable::', '')}" }
  "#<#{self.class}:0x#{object_id.to_s(16)} #{axes_.join(', ')}#{@parent ? ' (sub)' : ''}>"
end
Also aliased as: to_s
lock!() click to toggle source

Lock this structure, preventing further modification. Generally, this is done automatically when a structure is used by a table; you normally do not need to call it yourself.

# File lib/ntable/structure.rb, line 470
def lock!
  unless @locked
    @locked = true
    if @size > 0
      s_ = @size
      @indexes.each do |ainfo_|
        s_ /= ainfo_.size
        ainfo_._set_step(s_)
      end
    end
  end
  self
end
locked?() click to toggle source

Returns true if this structure has been locked.

# File lib/ntable/structure.rb, line 487
def locked?
  @locked
end
parent() click to toggle source

Returns the parent structure if this is a sub-view into a larger structure, or nil if not.

# File lib/ntable/structure.rb, line 414
def parent
  @parent
end
position(arg_) click to toggle source

Creates a Position object for the given argument. The argument may be a hash of row labels by axis name, or it may be an array of row labels for the axes in order.

# File lib/ntable/structure.rb, line 512
def position(arg_)
  vector_ = _vector(arg_)
  vector_ ? Position.new(self, vector_) : nil
end
remove(axis_) click to toggle source

Remove the given axis from the configuration. You may specify the axis by 0-based index, or by name string. Raises UnknownAxisError if there is no such axis.

# File lib/ntable/structure.rb, line 365
def remove(axis_)
  raise StructureStateError, "Structure locked" if @locked
  ainfo_ = axis(axis_)
  unless ainfo_
    raise UnknownAxisError, "Unknown axis: #{axis_.inspect}"
  end
  index_ = ainfo_.axis_index
  @names.delete(ainfo_.axis_name)
  @indexes.delete_at(index_)
  @indexes[index_..-1].each{ |ai_| ai_._dec_index }
  size_ = ainfo_.size
  if size_ == 0
    @size = @indexes.inject(1){ |s_, ai_| s_ * ai_.size }
  else
    @size /= size_
  end
  self
end
replace(axis_, naxis_=nil) { |ainfo_| ... } click to toggle source

Replace the given axis already in the configuration, with the given new axis. The old axis must be specified by 0-based index or by name string. The new axis must be provided as an axis object that duck-types EmptyAxis.

Raises UnknownAxisError if the given old axis specification does not match an actual axis.

# File lib/ntable/structure.rb, line 393
def replace(axis_, naxis_=nil)
  raise StructureStateError, "Structure locked" if @locked
  ainfo_ = axis(axis_)
  unless ainfo_
    raise UnknownAxisError, "Unknown axis: #{axis_.inspect}"
  end
  osize_ = ainfo_.size
  naxis_ ||= yield(ainfo_)
  ainfo_._set_axis(naxis_)
  if osize_ == 0
    @size = @indexes.inject(1){ |size_, ai_| size_ * ai_.size }
  else
    @size = @size / osize_ * naxis_.size
  end
  self
end
size() click to toggle source

Returns the number of cells in a table with this structure.

# File lib/ntable/structure.rb, line 494
def size
  @size
end
substructure_including(*axes_) click to toggle source

Create a new substructure of this structure. The new structure has this structure as its parent, but includes only the given axes, which can be provided as an array of axis names or indexes.

# File lib/ntable/structure.rb, line 522
def substructure_including(*axes_)
  _substructure(axes_.flatten, true)
end
substructure_omitting(*axes_) click to toggle source

Create a new substructure of this structure. The new structure has this structure as its parent, but includes all axes EXCEPT the given axes, provided as an array of axis names or indexes.

# File lib/ntable/structure.rb, line 531
def substructure_omitting(*axes_)
  _substructure(axes_.flatten, false)
end
to_json_array() click to toggle source

Returns an array of objects representing the configuration of this structure. Such an array can be serialized as JSON, and used to replicate this structure using from_json_array.

# File lib/ntable/structure.rb, line 540
def to_json_array
  @indexes.map do |ai_|
    name_ = ai_.axis_name
    axis_ = ai_.axis_object
    type_ = axis_.class.name
    if type_ =~ %r^NTable::(\w+)Axis$/
      type_ = $1
      type_ = type_[0..0].downcase + type_[1..-1]
    end
    obj_ = {'type' => type_}
    obj_['name'] = name_ if name_
    axis_.to_json_object(obj_)
    obj_
  end
end
to_s() click to toggle source
Alias for: inspect
unlocked_copy() click to toggle source

Create an unlocked copy of this structure that can be further modified.

# File lib/ntable/structure.rb, line 292
def unlocked_copy
  copy_ = Structure.new
  @indexes.each{ |ai_| copy_.add(ai_.axis_object, ai_.axis_name) }
  copy_
end