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.
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
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
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
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
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
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
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
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
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
Returns the number of axes/dimensions currently in this structure.
# File lib/ntable/structure.rb, line 421 def dim @indexes.size end
Iterate over the axes in order, yielding AxisInfo objects.
# File lib/ntable/structure.rb, line 459 def each(&block_) @indexes.each(&block_) end
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
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
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
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
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
Returns true if this structure has been locked.
# File lib/ntable/structure.rb, line 487 def locked? @locked end
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
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 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 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
Returns the number of cells in a table with this structure.
# File lib/ntable/structure.rb, line 494 def size @size end
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
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
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
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