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



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

def structure
  @structure
end

Class Method Details

.from_json_object(json_) ⇒ Object

Deprecated synonym for ::NTable.from_json_object



253
254
255
# File 'lib/ntable/construction.rb', line 253

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



267
268
269
# File 'lib/ntable/construction.rb', line 267

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



260
261
262
# File 'lib/ntable/construction.rb', line 260

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:



619
620
621
622
623
624
625
626
# File 'lib/ntable/table.rb', line 619

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



629
630
631
632
633
634
635
# File 'lib/ntable/table.rb', line 629

def _offset_for_args(args_)
  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:



599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/ntable/table.rb', line 599

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.



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

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.



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

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.



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

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.



516
517
518
# File 'lib/ntable/table.rb', line 516

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.



524
525
526
# File 'lib/ntable/table.rb', line 524

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)


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

def degenerate?
  @structure.degenerate?
end

#dimObject

The number of dimensions/axes in this table.



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

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.



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

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.



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

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)


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

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:



282
283
284
285
286
# File 'lib/ntable/table.rb', line 282

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 must be given as labels. You may specify the cell as an array of coordinates, or as a hash mapping axis name 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')

Raises NoSuchCellError if the coordinates do not exist.



206
207
208
209
210
211
212
# File 'lib/ntable/table.rb', line 206

def get(*args_)
  offset_ = _offset_for_args(args_)
  unless offset_
    raise NoSuchCellError
  end
  @vals[@offset + offset_]
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)


219
220
221
# File 'lib/ntable/table.rb', line 219

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:



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

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.



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

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:



364
365
366
367
368
# File 'lib/ntable/table.rb', line 364

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.



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

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:



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

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.



186
187
188
# File 'lib/ntable/table.rb', line 186

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.


409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/ntable/table.rb', line 409

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{ |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(initial){ |accumulator, value, position| block }

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



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/ntable/table.rb', line 460

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:



237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/ntable/table.rb', line 237

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.



539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/ntable/table.rb', line 539

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.



137
138
139
# File 'lib/ntable/table.rb', line 137

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.



567
568
569
# File 'lib/ntable/table.rb', line 567

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

#to_jsonObject

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



582
583
584
# File 'lib/ntable/table.rb', line 582

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.



575
576
577
# File 'lib/ntable/table.rb', line 575

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.



590
591
592
593
594
595
596
# File 'lib/ntable/table.rb', line 590

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