Class: NTable::Structure
- Inherits:
-
Object
- Object
- NTable::Structure
- Includes:
- Enumerable
- Defined in:
- lib/ntable/structure.rb
Overview
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.
Defined Under Namespace
Class Method Summary collapse
-
.add(axis_, name_ = nil) ⇒ Object
Create a new structure and automatically add the given axis.
-
.from_json_array(array_) ⇒ Object
Deserialize a structure from the given JSON array.
Instance Method Summary collapse
-
#==(rhs_) ⇒ Object
Returns true if the two structures are equivalent in the axes but not necessarily in the offsets.
-
#_compute_coords_for_vector(vector_) ⇒ Object
:nodoc:.
-
#_compute_offset_for_vector(vector_) ⇒ Object
:nodoc:.
-
#_compute_position_coords(offset_) ⇒ Object
:nodoc:.
-
#_dec_vector(vector_) ⇒ Object
:nodoc:.
-
#_inc_vector(vector_) ⇒ Object
:nodoc:.
-
#_offset(arg_) ⇒ Object
:nodoc:.
-
#_substructure(axes_, bool_) ⇒ Object
:nodoc:.
-
#_vector(arg_) ⇒ Object
:nodoc:.
-
#add(axis_, name_ = nil) ⇒ Object
Append an axis to the configuration of this structure.
-
#all_axes ⇒ Object
Returns an array of AxisInfo objects representing all the axes of this structure.
-
#axis(axis_) ⇒ Object
(also: #[])
Returns the AxisInfo object representing the given axis.
-
#create(data_ = {}) ⇒ Object
Create a new table using this structure as the structure.
-
#degenerate? ⇒ Boolean
Returns true if this is a degenerate/scalar structure.
-
#dim ⇒ Object
Returns the number of axes/dimensions currently in this structure.
-
#each(&block_) ⇒ Object
Iterate over the axes in order, yielding AxisInfo objects.
-
#empty? ⇒ Boolean
Returns true if this structure implies an “empty” table, one with no cells.
-
#eql?(rhs_) ⇒ Boolean
Returns true if the two structures are equivalent, both in the axes and in the parentage.
-
#from_json_array(array_) ⇒ Object
Use the given array to reconstitute a structure previously serialized using Structure#to_json_array.
-
#initialize ⇒ Structure
constructor
Create an empty Structure.
-
#initialize_copy(other_) ⇒ Object
:nodoc:.
-
#inspect ⇒ Object
(also: #to_s)
Basic output.
-
#lock! ⇒ Object
Lock this structure, preventing further modification.
-
#locked? ⇒ Boolean
Returns true if this structure has been locked.
-
#parent ⇒ Object
Returns the parent structure if this is a sub-view into a larger structure, or nil if not.
-
#position(arg_) ⇒ Object
Creates a Position object for the given argument.
-
#remove(axis_) ⇒ Object
Remove the given axis from the configuration.
-
#replace(axis_, naxis_ = nil) ⇒ Object
Replace the given axis already in the configuration, with the given new axis.
-
#size ⇒ Object
Returns the number of cells in a table with this structure.
-
#substructure_including(*axes_) ⇒ Object
Create a new substructure of this structure.
-
#substructure_omitting(*axes_) ⇒ Object
Create a new substructure of this structure.
-
#to_json_array ⇒ Object
Returns an array of objects representing the configuration of this structure.
-
#unlocked_copy ⇒ Object
Create an unlocked copy of this structure that can be further modified.
Constructor Details
#initialize ⇒ Structure
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 Structure#add method before using the structure.
267 268 269 270 271 272 273 |
# File 'lib/ntable/structure.rb', line 267 def initialize @indexes = [] @names = {} @size = 1 @locked = false @parent = nil end |
Class Method Details
.add(axis_, name_ = nil) ⇒ Object
Create a new structure and automatically add the given axis. See Structure#add.
744 745 746 |
# File 'lib/ntable/structure.rb', line 744 def add(axis_, name_=nil) self.new.add(axis_, name_) end |
.from_json_array(array_) ⇒ Object
Deserialize a structure from the given JSON array
751 752 753 |
# File 'lib/ntable/structure.rb', line 751 def from_json_array(array_) self.new.from_json_array(array_) end |
Instance Method Details
#==(rhs_) ⇒ Object
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.
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# 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 |
#_compute_coords_for_vector(vector_) ⇒ Object
:nodoc:
691 692 693 694 695 |
# File 'lib/ntable/structure.rb', line 691 def _compute_coords_for_vector(vector_) # :nodoc: vector_.map.with_index do |v_, i_| @indexes[i_].label(v_) end end |
#_compute_offset_for_vector(vector_) ⇒ Object
:nodoc:
682 683 684 685 686 687 688 |
# File 'lib/ntable/structure.rb', line 682 def _compute_offset_for_vector(vector_) # :nodoc: offset_ = 0 vector_.each_with_index do |v_, i_| offset_ += v_ * @indexes[i_].step end offset_ end |
#_compute_position_coords(offset_) ⇒ Object
:nodoc:
728 729 730 731 732 733 734 735 |
# File 'lib/ntable/structure.rb', line 728 def _compute_position_coords(offset_) # :nodoc: raise StructureStateError, "Structure not locked" unless @locked @indexes.map do |ainfo_| i_ = offset_ / ainfo_.step offset_ -= ainfo_.step * i_ ainfo_.label(i_) end end |
#_dec_vector(vector_) ⇒ Object
:nodoc:
713 714 715 716 717 718 719 720 721 722 723 724 725 |
# File 'lib/ntable/structure.rb', line 713 def _dec_vector(vector_) # :nodoc: (vector_.size - 1).downto(-1) do |i_| return true if i_ < 0 v_ = vector_[i_] - 1 if v_ < 0 vector_[i_] = @indexes[i_].size - 1 else vector_[i_] = v_ break end end false end |
#_inc_vector(vector_) ⇒ Object
:nodoc:
698 699 700 701 702 703 704 705 706 707 708 709 710 |
# File 'lib/ntable/structure.rb', line 698 def _inc_vector(vector_) # :nodoc: (vector_.size - 1).downto(-1) do |i_| return true if i_ < 0 v_ = vector_[i_] + 1 if v_ >= @indexes[i_].size vector_[i_] = 0 else vector_[i_] = v_ break end end false end |
#_offset(arg_) ⇒ Object
:nodoc:
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 |
# File 'lib/ntable/structure.rb', line 621 def _offset(arg_) # :nodoc: raise StructureStateError, "Structure not locked" unless @locked return nil unless @size > 0 case arg_ when ::Hash offset_ = 0 arg_.each do |k_, v_| if (ainfo_ = axis(k_)) delta_ = ainfo_._compute_offset(v_) return nil unless delta_ offset_ += delta_ else return nil end end offset_ when ::Array offset_ = 0 arg_.each_with_index do |v_, i_| if (ainfo_ = @indexes[i_]) delta_ = ainfo_._compute_offset(v_) return nil unless delta_ offset_ += delta_ else return nil end end offset_ else nil end end |
#_substructure(axes_, bool_) ⇒ Object
:nodoc:
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 |
# File 'lib/ntable/structure.rb', line 598 def _substructure(axes_, bool_) # :nodoc: raise StructureStateError, "Structure not locked" unless @locked sub_ = Structure.new indexes_ = [] names_ = {} size_ = 1 @indexes.each do |ainfo_| if axes_.include?(ainfo_.axis_index) == bool_ nainfo_ = AxisInfo.new(ainfo_.axis_object, indexes_.size, ainfo_.axis_name, ainfo_.step) indexes_ << nainfo_ names_[ainfo_.axis_name] = nainfo_ size_ *= ainfo_.size end end sub_.instance_variable_set(:@indexes, indexes_) sub_.instance_variable_set(:@names, names_) sub_.instance_variable_set(:@size, size_) sub_.instance_variable_set(:@locked, true) sub_.instance_variable_set(:@parent, self) sub_ end |
#_vector(arg_) ⇒ Object
:nodoc:
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 |
# File 'lib/ntable/structure.rb', line 655 def _vector(arg_) # :nodoc: raise StructureStateError, "Structure not locked" unless @locked return nil unless @size > 0 vec_ = ::Array.new(@indexes.size, 0) case arg_ when ::Hash arg_.each do |k_, v_| if (ainfo_ = axis(k_)) val_ = ainfo_.index(v_) vec_[ainfo_.axis_index] = val_ if val_ end end vec_ when ::Array arg_.each_with_index do |v_, i_| if (ainfo_ = @indexes[i_]) val_ = ainfo_.index(v_) vec_[i_] = val_ if val_ end end vec_ else nil end end |
#add(axis_, name_ = nil) ⇒ Object
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.
350 351 352 353 354 355 356 357 358 |
# 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 ⇒ Object
Returns an array of AxisInfo objects representing all the axes of this structure.
437 438 439 |
# File 'lib/ntable/structure.rb', line 437 def all_axes @indexes.dup end |
#axis(axis_) ⇒ Object Also known as: []
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.
446 447 448 449 450 451 452 453 |
# File 'lib/ntable/structure.rb', line 446 def axis(axis_) case axis_ when ::Integer @indexes[axis_] else @names[axis_.to_s] end end |
#create(data_ = {}) ⇒ Object
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.
593 594 595 |
# File 'lib/ntable/structure.rb', line 593 def create(data_={}) Table.new(self, data_) end |
#degenerate? ⇒ Boolean
Returns true if this is a degenerate/scalar structure. That is, if the dimension is 0.
429 430 431 |
# File 'lib/ntable/structure.rb', line 429 def degenerate? @indexes.size == 0 end |
#dim ⇒ Object
Returns the number of axes/dimensions currently in this structure.
421 422 423 |
# File 'lib/ntable/structure.rb', line 421 def dim @indexes.size end |
#each(&block_) ⇒ Object
Iterate over the axes in order, yielding AxisInfo objects.
459 460 461 |
# File 'lib/ntable/structure.rb', line 459 def each(&block_) @indexes.each(&block_) end |
#empty? ⇒ Boolean
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.
503 504 505 |
# File 'lib/ntable/structure.rb', line 503 def empty? @size == 0 end |
#eql?(rhs_) ⇒ Boolean
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.
314 315 316 317 318 319 |
# 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_) ⇒ Object
Use the given array to reconstitute a structure previously serialized using Structure#to_json_array.
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 |
# 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_ =~ /^([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 |
#initialize_copy(other_) ⇒ Object
:nodoc:
276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/ntable/structure.rb', line 276 def initialize_copy(other_) # :nodoc: initialize other_.instance_variable_get(:@indexes).each do |ai_| ai_ = ai_.dup @indexes << ai_ if (name_ = ai_.axis_name) @names[name_] = ai_ end end @size = other_.size end |
#inspect ⇒ Object Also known as: to_s
Basic output.
301 302 303 304 |
# 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! ⇒ Object
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.
470 471 472 473 474 475 476 477 478 479 480 481 482 |
# 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? ⇒ Boolean
Returns true if this structure has been locked.
487 488 489 |
# File 'lib/ntable/structure.rb', line 487 def locked? @locked end |
#parent ⇒ Object
Returns the parent structure if this is a sub-view into a larger structure, or nil if not.
414 415 416 |
# File 'lib/ntable/structure.rb', line 414 def parent @parent end |
#position(arg_) ⇒ Object
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.
512 513 514 515 |
# File 'lib/ntable/structure.rb', line 512 def position(arg_) vector_ = _vector(arg_) vector_ ? Position.new(self, vector_) : nil end |
#remove(axis_) ⇒ Object
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.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# 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) ⇒ Object
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.
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
# 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 ⇒ Object
Returns the number of cells in a table with this structure.
494 495 496 |
# File 'lib/ntable/structure.rb', line 494 def size @size end |
#substructure_including(*axes_) ⇒ Object
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.
522 523 524 |
# File 'lib/ntable/structure.rb', line 522 def substructure_including(*axes_) _substructure(axes_.flatten, true) end |
#substructure_omitting(*axes_) ⇒ Object
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.
531 532 533 |
# File 'lib/ntable/structure.rb', line 531 def substructure_omitting(*axes_) _substructure(axes_.flatten, false) end |
#to_json_array ⇒ Object
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.
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 |
# 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_ =~ /^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 |