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



139
140
141
# File 'lib/ntable/table.rb', line 139

def structure
  @structure
end

Class Method Details

.from_json_object(json_) ⇒ Object

Deprecated synonym for ::NTable.from_json_object



280
281
282
# File 'lib/ntable/construction.rb', line 280

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



294
295
296
# File 'lib/ntable/construction.rb', line 294

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



287
288
289
# File 'lib/ntable/construction.rb', line 287

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.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/ntable/table.rb', line 107

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:



643
644
645
646
647
648
649
650
# File 'lib/ntable/table.rb', line 643

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:



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

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:



623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/ntable/table.rb', line 623

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.



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

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.



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

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.



513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/ntable/table.rb', line 513

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 => 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.



540
541
542
# File 'lib/ntable/table.rb', line 540

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.



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

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)


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

def degenerate?
  @structure.degenerate?
end

#dimObject

The number of dimensions/axes in this table.



151
152
153
# File 'lib/ntable/table.rb', line 151

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.



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/ntable/table.rb', line 316

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.



336
337
338
339
340
341
342
343
344
# File 'lib/ntable/table.rb', line 336

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)


158
159
160
# File 'lib/ntable/table.rb', line 158

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)


93
94
95
96
97
98
99
# File 'lib/ntable/table.rb', line 93

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:



306
307
308
309
310
# File 'lib/ntable/table.rb', line 306

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.



230
231
232
233
234
235
236
# File 'lib/ntable/table.rb', line 230

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

#hashObject

Standard hash value



133
134
135
# File 'lib/ntable/table.rb', line 133

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)


243
244
245
# File 'lib/ntable/table.rb', line 243

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

#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:



284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/ntable/table.rb', line 284

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.



351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/ntable/table.rb', line 351

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:



388
389
390
391
392
# File 'lib/ntable/table.rb', line 388

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.



369
370
371
372
373
374
375
376
377
378
379
# File 'lib/ntable/table.rb', line 369

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:



401
402
403
404
405
406
407
408
409
410
# File 'lib/ntable/table.rb', line 401

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.



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

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.


433
434
435
436
437
438
439
440
441
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
# File 'lib/ntable/table.rb', line 433

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.



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/ntable/table.rb', line 484

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:



261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/ntable/table.rb', line 261

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.



563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'lib/ntable/table.rb', line 563

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.



144
145
146
# File 'lib/ntable/table.rb', line 144

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.



591
592
593
# File 'lib/ntable/table.rb', line 591

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

#to_jsonObject

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



606
607
608
# File 'lib/ntable/table.rb', line 606

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.



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

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.



614
615
616
617
618
619
620
# File 'lib/ntable/table.rb', line 614

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