Class: Tb

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/tb/pnm.rb,
lib/tb/csv.rb,
lib/tb/tsv.rb,
lib/tb/basic.rb

Overview

Tb represents a set of records. A record contains field values accessed by field names.

A table can be visualized as follows.

_recordid f1  f2  f3
0         v01 v02 v03
1         v11 v12 v13
2         v21 v22 v23

This table has 4 fields and 3 records:

  • fields: _recordid, f1, f2 and f3.

  • records: [0, v01, v02, v03], [1, v11, v12, v13] and [2, v21, v22, v23]

The fields are strings. The field names starts with “_” is reserved. “_recordid” is a reserved field always defined to identify a record.

Non-reserved fields can be defined by Tb.new and Tb#define_field. It is an error to access a field which is not defined.

A value in a record is identified by a recordid and field name. A value for non-reserved fields can be any Ruby values. A value for _recordid is an non-negative integer and it is automatically allocated when a new record is inserted. It is an error to access a record by recordid which is not allocated.

Defined Under Namespace

Modules: Search Classes: CSVReader, CatReader, Cmd, FieldSet, PNMReader, Pager, Reader, Record, TSVReader

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Enumerable

#tb_categorize, #tb_category_count, #tb_unique_categorize

Constructor Details

#initialize(*args) ⇒ Tb

:call-seq:

Tb.new
Tb.new(fields, values1, values2, ...)

creates an instance of Tb class.

If the first argument, fields, is given, it should be an array of strings. The strings are used as field names to define fields.

The field names begins with underscore, “_”, are reserved. Currently, “_recordid” field is defined automatically.

If the second argument and subsequent arguments, valuesN, are given, they should be an array. The arrays are used as records to define records. A value in the array is used for a value of corresponding field defined by the first argument.

t = Tb.new %w[fruit color],
              %w[apple red],
              %w[banana yellow],
              %w[orange orange]
pp t
#=> #<Tb
#     {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#     {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}>
#     {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>


84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/tb/basic.rb', line 84

def initialize(*args)
  @next_recordid = 0
  @recordid2index = {}
  @free_index = []
  @tbl = {"_recordid"=>[]}
  @field_list = ["_recordid".freeze]
  if !args.empty?
    args.first.each {|f|
      define_field(f)
    }
    insert_values(*args)
  end
end

Class Method Details

.csv_stream_input(csv, &b) ⇒ Object



59
60
61
62
63
# File 'lib/tb/csv.rb', line 59

def Tb.csv_stream_input(csv, &b)
  csvreader = CSVReader.new(csv)
  csvreader.each(&b)
  nil
end

.csv_stream_output(out) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/tb/csv.rb', line 110

def Tb.csv_stream_output(out)
  require 'csv'
  if defined? CSV::Writer
    # Ruby 1.8
    CSV::Writer.generate(out) {|csvgen|
      yield csvgen
    }
  else
    # Ruby 1.9
    gen = Object.new
    gen.instance_variable_set(:@out, out)
    def gen.<<(ary)
      @out << ary.to_csv
    end
    yield gen
  end
end

.load_csv(filename, *header_fields, &block) ⇒ Object



30
31
32
# File 'lib/tb/csv.rb', line 30

def Tb.load_csv(filename, *header_fields, &block)
  Tb.parse_csv(File.read(filename), *header_fields, &block)
end

.load_pnm(pnm_filename) ⇒ Object

:call-seq:

Tb.load_pnm(pnm_filename) -> tb


31
32
33
34
# File 'lib/tb/pnm.rb', line 31

def Tb.load_pnm(pnm_filename)
  pnm_content = File.open(pnm_filename, "rb") {|f| f.read }
  Tb.parse_pnm(pnm_content)
end

.load_tsv(filename, *header_fields, &block) ⇒ Object



30
31
32
# File 'lib/tb/tsv.rb', line 30

def Tb.load_tsv(filename, *header_fields, &block)
  Tb.parse_tsv(File.read(filename), *header_fields, &block)
end

.parse_csv(csv, *header_fields) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/tb/csv.rb', line 34

def Tb.parse_csv(csv, *header_fields)
  aa = []
  csv_stream_input(csv) {|ary|
    aa << ary
  }
  aa = yield aa if block_given?
  if header_fields.empty?
    reader = Tb::Reader.new(aa)
    arys = []
    reader.each {|ary|
      arys << ary
    }
    header = reader.header
  else
    header = header_fields
    arys = aa
  end
  t = Tb.new(header)
  arys.each {|ary|
    ary << nil while ary.length < header.length
    t.insert_values header, ary
  }
  t
end

.parse_pnm(pnm_content) ⇒ Object

:call-seq:

Tb.parse_pnm(pnm_content) -> tb


39
40
41
42
43
44
45
46
47
# File 'lib/tb/pnm.rb', line 39

def Tb.parse_pnm(pnm_content)
  reader = PNMReader.new(pnm_content)
  header = reader.shift
  t = Tb.new(header)
  reader.each {|ary|
    t.insert_values header, ary
  }
  t
end

.parse_tsv(tsv, *header_fields) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/tb/tsv.rb', line 34

def Tb.parse_tsv(tsv, *header_fields)
  aa = []
  tsv_stream_input(tsv) {|ary|
    aa << ary
  }
  aa = yield aa if block_given?
  if header_fields.empty?
    reader = Tb::Reader.new(aa)
    arys = []
    reader.each {|ary|
      arys << ary
    }
    header = reader.header
  else
    header = header_fields
    arys = aa
  end
  t = Tb.new(header)
  arys.each {|ary|
    ary << nil while ary.length < header.length
    t.insert_values header, ary
  }
  t
end

.pnm_stream_input(pnm_io) ⇒ Object

:call-seq:

Tb.pnm_stream_input(pnm_io) {|ary| ... }


52
53
54
55
56
# File 'lib/tb/pnm.rb', line 52

def Tb.pnm_stream_input(pnm_io)
  pnm_io.binmode
  content = pnm_io.read
  PNMReader.new(content)
end

.tsv_fields_join(values) ⇒ Object



118
119
120
# File 'lib/tb/tsv.rb', line 118

def Tb.tsv_fields_join(values)
  values.map {|v| v.to_s.gsub(/[\t\r\n]/, ' ') }.join("\t")
end

.tsv_stream_input(tsv) ⇒ Object



59
60
61
62
63
64
65
# File 'lib/tb/tsv.rb', line 59

def Tb.tsv_stream_input(tsv)
  tsvreader = TSVReader.new(tsv)
  while ary = tsvreader.shift
    yield ary
  end
  nil
end

.tsv_stream_output(out) {|gen| ... } ⇒ Object

Yields:

  • (gen)


88
89
90
91
92
93
94
95
# File 'lib/tb/tsv.rb', line 88

def Tb.tsv_stream_output(out)
  gen = Object.new
  gen.instance_variable_set(:@out, out)
  def gen.<<(ary)
    @out << Tb.tsv_fields_join(ary) << "\n"
  end
  yield gen
end

Instance Method Details

#allocate_record(recordid = nil) ⇒ Object

:call-seq:

table.allocate_record(recordid=nil)

allocates a record.

If the optional argument, recordid, is specified, the allocated record will have the recordid. If recordid is already used, ArgumentError is raised.



424
425
426
# File 'lib/tb/basic.rb', line 424

def allocate_record(recordid=nil)
  Tb::Record.new(self, allocate_recordid(recordid))
end

#allocate_recordid(recordid = nil) ⇒ Object

:call-seq:

table.allocate_recordid -> fresh_recordid
table.allocate_recordid(recordid) -> recordid

inserts a record and returns its identifier. All fields of the record are initialized to nil.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.allocate_recoridd #=> 3
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
#    {"_recordid"=>3}>

If the optional recordid is specified and the recordid is not used in the table, a record is allocated with the recordid. If the specified recordid is already used, ArgumentError is raised.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.allocate_recordid(100)
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
#    {"_recordid"=>100}>


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/tb/basic.rb', line 395

def allocate_recordid(recordid=nil)
  if recordid.nil?
    recordid = @next_recordid
    @next_recordid += 1
  else
    recordid = check_recordid_type(recordid)
    if @recordid2index.include? recordid
      raise ArgumentError, "recordid already used: #{recordid.inspect}"
    end
    @next_recordid = recordid + 1 if @next_recordid <= recordid
  end
  if @free_index.empty?
    index = @tbl["_recordid"].length
  else
    index = @free_index.pop
  end
  @recordid2index[recordid] = index
  @tbl["_recordid"][index] = recordid
  recordid
end

#concat(*tables) ⇒ Object

:call-seq:

table1.concat(table2, table3, ...) -> table1

concatenates argument tables destructively into table1. The reserved field (_recordid) in the argument tables is ignored.

This method returns table1.

t1 = Tb.new %w[fruit color],
            %w[apple red]
t2 = Tb.new %w[fruit color],
            %w[banana yellow],
            %w[orange orange]
pp t1
#=> #<Tb {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}>
pp t2
#=> #<Tb
     {"_recordid"=>0, "fruit"=>"banana", "color"=>"yellow"}
     {"_recordid"=>1, "fruit"=>"orange", "color"=>"orange"}>
t1.concat(t2)
pp t1
#=> #<Tb
     {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
     {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
     {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>


653
654
655
656
657
658
659
660
661
662
# File 'lib/tb/basic.rb', line 653

def concat(*tables)
  tables.each {|t|
    t.each_record {|record|
      record = record.to_h
      record.delete "_recordid"
      self.insert record
    }
  }
  self
end

#define_field(field) ⇒ Object

:call-seq:

table.define_field(field)
table.define_field(field) {|record| value_for_the_field }

defines a new field.

If no block is given, the initial value for the field is nil.

If a block is given, the block is called for each record. The return value of the block is used for the initial value of the field.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.define_field("namelen") {|record| record["fruit"].length }
pp t
#=>  #<Tb
#     {"_recordid"=>0, "fruit"=>"apple", "color"=>"red", "namelen"=>5}
#     {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow", "namelen"=>6}
#     {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange", "namelen"=>6}>


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/tb/basic.rb', line 196

def define_field(field)
  field = check_field_type(field).dup.freeze
  if field.start_with?("_")
    raise ArgumentError, "field begins with underscore: #{field.inspect}"
  end
  if @tbl.include? field
    raise ArgumentError, "field already defined: #{field.inspect}"
  end
  @tbl[field] = []
  @field_list << field
  if block_given?
    each_record {|record|
      v = yield(record)
      if !v.nil?
        record[field] = v
      end
    }
  end
end

#delete_cell(recordid, field) ⇒ Object

:call-seq:

table.delete_cell(recordid, field) -> oldvalue

sets nil to the cell identified by recordid and field.

This method returns the old value.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange] 
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.delete_cell(1, "color") #=> "yellow"
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.get_cell(1, "color") #=> nil

Raises:

  • (ArgumentError)


503
504
505
506
507
508
509
510
511
512
# File 'lib/tb/basic.rb', line 503

def delete_cell(recordid, field)
  recordid = check_recordid(recordid)
  field = check_field(field)
  raise ArgumentError, "can not delete reserved field: #{field.inspect}" if field.start_with?("_") 
  ary = @tbl[field]
  index = @recordid2index[recordid]
  old = ary[index]
  ary[index] = nil
  old
end

#delete_field(*fields) ⇒ Object

:call-seq:

table.delete_field(field1, ...) -> nil

deletes zero or more fields destructively.

This method returns nil.



985
986
987
988
989
990
991
992
993
# File 'lib/tb/basic.rb', line 985

def delete_field(*fields)
  fields.each {|f|
    f = check_field(f)
    raise ArgumentError, "can not delete reserved field: #{f.inspect}" if f.start_with?("_") 
    @tbl.delete(f)
    @field_list.delete(f)
  }
  nil
end

#delete_recordid(recordid) ⇒ Object

:call-seq:

table.delete_recordid(recordid) -> nil

deletes a record identified by recordid.

This method returns nil.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.delete_recordid(1)
#=> nil
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>


537
538
539
540
541
542
543
544
545
# File 'lib/tb/basic.rb', line 537

def delete_recordid(recordid)
  recordid = check_recordid(recordid)
  index = @recordid2index.delete(recordid)
  @tbl.each {|f, ary|
    ary[index] = nil
  }
  @free_index.push index
  nil
end

#each_fieldObject

:call-seq:

table.each_field {|field| ... }

iterates over the non-reserved field names of the table.

t = Tb.new %w[fruit color],    
           %w[apple red], 
           %w[banana yellow], 
           %w[orange orange] 
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.each_field {|f| p f }
#=> "fruit"
#   "color"


762
763
764
765
766
767
768
# File 'lib/tb/basic.rb', line 762

def each_field
  @field_list.each {|f|
    next if f.start_with?("_")
    yield f
  }
  nil
end

#each_field_with_reservedObject

:call-seq:

table.each_field_with_reserved {|field| ... }

iterates over the reserved and non-reserved field names of the table.

t = Tb.new %w[fruit color],    
           %w[apple red], 
           %w[banana yellow], 
           %w[orange orange] 
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.each_field {|f| p f }
#=> "_recordid"
#   "fruit"
#   "color"


789
790
791
792
# File 'lib/tb/basic.rb', line 789

def each_field_with_reserved
  @field_list.each {|f| yield f }
  nil
end

#each_recordObject Also known as: each

:call-seq:

table.each {|record| ... }
table.each_record {|record| ... }

iterates over all records and yields them as Tb::Record object.

This method returns nil.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.each_record {|record| p record }   
#=> #<Tb::Record: "fruit"=>"apple", "color"=>"red">
#   #<Tb::Record: "fruit"=>"banana", "color"=>"yellow">
#   #<Tb::Record: "fruit"=>"orange", "color"=>"orange">


872
873
874
875
876
877
# File 'lib/tb/basic.rb', line 872

def each_record
  each_recordid {|recordid|
    yield get_record(recordid)
  }
  nil
end

#each_record_values(*fields) ⇒ Object

:call-seq:

table.each_record_values(field1, ...) {|value1, ...| ... }


882
883
884
885
886
887
# File 'lib/tb/basic.rb', line 882

def each_record_values(*fields)
  each_recordid {|recordid|
    vs = get_values(recordid, *fields)
    yield vs
  }
end

#each_recordidObject

:call-seq:

table.each_recordid {|recordid| ... }

iterates over all records and yield the recordids of them.

This method returns nil.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.each_recordid {|recordid| p recordid }
#=> 0
#   1
#   2


815
816
817
818
819
820
821
# File 'lib/tb/basic.rb', line 815

def each_recordid
  @tbl["_recordid"].each {|recordid|
    next if recordid.nil?
    yield recordid
  }
  nil
end

#filterObject

:call-seq:

table.filter {|record| ... }


891
892
893
894
895
896
897
898
899
# File 'lib/tb/basic.rb', line 891

def filter
  t = Tb.new list_fields
  each_record {|record|
    if yield(record)
      t.insert record
    end
  }
  t
end

#fmap!(field) ⇒ Object

:call-seq:

table.fmap!(field) {|record, value| new_value }


972
973
974
975
976
977
# File 'lib/tb/basic.rb', line 972

def fmap!(field)
  each_recordid {|recordid|
    value = yield get_record(recordid), get_cell(recordid, field)
    set_cell(recordid, field, value)
  }
end

#generate_csv(out = '', fields = nil, &block) ⇒ Object

:call-seq:

generate_csv(out='', fields=nil) {|recordids| modified_recordids }
generate_csv(out='', fields=nil)


132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/tb/csv.rb', line 132

def generate_csv(out='', fields=nil, &block)
  if fields.nil?
    fields = list_fields
  end
  require 'csv'
  recordids = list_recordids
  if block_given?
    recordids = yield(recordids)
  end
  Tb.csv_stream_output(out) {|gen|
    gen << fields
    recordids.each {|recordid|
      gen << get_values(recordid, *fields)
    }
  }
  out
end

#generate_pnm(out = '') ⇒ Object

:call-seq:

generate_pnm(out='')


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/tb/pnm.rb', line 186

def generate_pnm(out='')
  undefined_field = ['x', 'y', 'component', 'value'] - self.list_fields
  if !undefined_field.empty?
    raise ArgumentError, "field not defined: #{undefined_field.inspect[1...-1]}"
  end
  pnm_type = nil
  width = height = nil
  comments = []
  max_value = nil
  max_x = max_y = 0
  values = { 0.0 => true, 1.0 => true }
  components = {}
  self.each {|rec|
    case rec['component']
    when 'pnm_type'
      pnm_type = rec['value']
    when 'width'
      width = rec['value'].to_i
    when 'height'
      height = rec['value'].to_i
    when 'max'
      max_value = rec['value'].to_i
    when 'comment'
      comments << rec['value']
    when 'R', 'G', 'B', 'V'
      components[rec['component']] = true
      x = rec['x'].to_i
      y = rec['y'].to_i
      max_x = x if max_x < x
      max_y = y if max_y < y
      values[rec['value'].to_f] = true
    end

  }
  if !(components.keys - %w[V]).empty? &&
     !(components.keys - %w[R G B]).empty?
    raise ArgumentError, "inconsistent color component: #{components.keys.sort.inspect[1...-1]}"
  end
  case pnm_type
  when 'P1', 'P4' then raise ArgumentError, "unexpected compoenent for PBM: #{components.keys.sort.inspect[1...-1]}" if !(components.keys - %w[V]).empty?
  when 'P2', 'P5' then raise ArgumentError, "unexpected compoenent for PGM: #{components.keys.sort.inspect[1...-1]}" if !(components.keys - %w[V]).empty?
  when 'P3', 'P6' then raise ArgumentError, "unexpected compoenent for PPM: #{components.keys.sort.inspect[1...-1]}" if !(components.keys - %w[R G B]).empty?
  end
  comments.each {|c|
    if /[\r\n]/ =~ c
      raise ArgumentError, "comment cannot contain a newline: #{c.inspect}"
    end
  }
  if !width
    width = max_x + 1
  end
  if !height
    height = max_y + 1
  end
  if !max_value
    min_interval = 1.0
    values.keys.sort.each_cons(2) {|v1, v2|
      d = v2-v1
      min_interval = d if d < min_interval
    }
    if min_interval < 0.0039 # 1/255 = 0.00392156862745098...
      max_value = 0xffff
    elsif min_interval < 1.0 || !(components.keys & %w[R G B]).empty?
      max_value = 0xff
    else
      max_value = 1
    end
  end
  if pnm_type
    if !pnm_type.kind_of?(String) || /\AP[123456]\z/ !~ pnm_type
      raise ArgumentError, "unexpected PNM type: #{pnm_type.inspect}"
    end
  else
    if (components.keys - ['V']).empty?
      if max_value == 1
        pnm_type = 'P4' # PBM
      else
        pnm_type = 'P5' # PGM
      end
    else
      pnm_type = 'P6' # PPM
    end
  end
  header = "#{pnm_type}\n"
  comments.each {|c| header << '#' << c << "\n" }
  header << "#{width} #{height}\n"
  header << "#{max_value}\n" if /P[2536]/ =~ pnm_type
  if /P[14]/ =~ pnm_type # PBM
    max_value = 1
  end
  bytes_per_component = bytes_per_line = component_fmt = component_template = nil
  case pnm_type
  when 'P1' then bytes_per_component = 1; raster = '1' * (width * height)
  when 'P4' then bytes_per_line = (width + 7) / 8; raster = ["1"*width].pack("B*") * height
  when 'P2' then bytes_per_component = max_value.to_s.length+1; component_fmt = "%#{bytes_per_component}d"; raster = (component_fmt % 0) * (width * height)
  when 'P5' then bytes_per_component, component_template = max_value < 0x100 ? [1, 'C'] : [2, 'n']; raster = "\0" * (bytes_per_component * width * height)
  when 'P3' then bytes_per_component = max_value.to_s.length+1; component_fmt = "%#{bytes_per_component}d"; raster = (component_fmt % 0) * (3 * width * height)
  when 'P6' then bytes_per_component, component_template = max_value < 0x100 ? [1, 'C'] : [2, 'n']; raster = "\0" * (bytes_per_component * 3 * width * height)
  else
    raise
  end
  raster.force_encoding("ASCII-8BIT") if raster.respond_to? :force_encoding
  self.each {|rec|
    c = rec['component']
    next if /\A[RGBV]\z/ !~ c
    x = rec['x'].to_i
    y = rec['y'].to_i
    next if x < 0 || width <= x
    next if y < 0 || height <= y
    v = rec['value'].to_f
    if v < 0
      v = 0
    elsif 1 < v
      v = 1
    end
    case pnm_type
    when 'P1'
      v = v < 0.5 ? '1' : '0'
      raster[y * width + x] = v
    when 'P4'
      xhi, xlo = x.divmod(8)
      i = y * bytes_per_line + xhi
      byte = raster[i].ord
      if v < 0.5
        byte |= 0x80 >> xlo
      else
        byte &= 0xff7f >> xlo
      end
      raster[i] = [byte].pack("C")
    when 'P2'
      v = (v * max_value).round
      raster[(y * width + x) * bytes_per_component, bytes_per_component] = component_fmt % v
    when 'P5'
      v = (v * max_value).round
      raster[(y * width + x) * bytes_per_component, bytes_per_component] = [v].pack(component_template)
    when 'P3'
      v = (v * max_value).round
      i = (y * width + x) * 3
      if c == 'G' then i += 1
      elsif c == 'B' then i += 2
      end
      raster[i * bytes_per_component, bytes_per_component] = component_fmt % v
    when 'P6'
      v = (v * max_value).round
      i = (y * width + x) * 3
      if c == 'G' then i += 1
      elsif c == 'B' then i += 2
      end
      raster[i * bytes_per_component, bytes_per_component] = [v].pack(component_template)
    else
      raise
    end
  }
  if pnm_type == 'P1'
    raster.gsub!(/[01]{#{width}}/, "\\&\n")
    if 70 < width
      raster.gsub!(/[01]{70}/, "\\&\n")
    end
    raster << "\n" if /\n\z/ !~ raster
  elsif /P[23]/ =~ pnm_type
    components_per_line = /P2/ =~ pnm_type ? width : 3 * width
    raster.gsub!(/  +/, ' ')
    raster.gsub!(/( \d+){#{components_per_line}}/, "\\&\n")
    raster.gsub!(/(\A|\n) +/, '\1')
    raster.gsub!(/.{71,}\n/) {
      $&.gsub(/(.{1,69})[ \n]/, "\\1\n")
    }
    raster << "\n" if /\n\z/ !~ raster
  end
  out << (header+raster)
end

#generate_tsv(out = '', fields = nil, &block) ⇒ Object

:call-seq:

generate_tsv(out='', fields=nil) {|recordids| modified_recordids }
generate_tsv(out='', fields=nil)


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/tb/tsv.rb', line 101

def generate_tsv(out='', fields=nil, &block)
  if fields.nil?
    fields = list_fields
  end
  recordids = list_recordids
  if block_given?
    recordids = yield(recordids)
  end
  Tb.tsv_stream_output(out) {|gen|
    gen << fields
    recordids.each {|recordid|
      gen << get_values(recordid, *fields)
    }
  }
  out
end

#get_cell(recordid, field) ⇒ Object

:call-seq:

table.get_cell(recordid, field) -> value

returns the value of the cell identified by recordid and field.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.get_cell(1, "fruit") #=> "banana"


472
473
474
475
476
477
# File 'lib/tb/basic.rb', line 472

def get_cell(recordid, field)
  recordid = check_recordid(recordid)
  field = check_field(field)
  ary = @tbl[field]
  ary[@recordid2index[recordid]]
end

#get_record(recordid) ⇒ Object

:call-seq:

table.get_record(recordid) -> record

get the record specified by recordid as a hash.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.get_record(1)                    
#=> #<Tb::Record: "_recordid"=>1, "fruit"=>"banana", "color"=>"yellow">


739
740
741
742
# File 'lib/tb/basic.rb', line 739

def get_record(recordid)
  recordid = check_recordid(recordid)
  Tb::Record.new(self, recordid)
end

#get_values(recordid, *fields) ⇒ Object

:call-seq:

table.get_values(recordid, field1, field2, ...) -> [value1, value2, ...]

extracts specified fields of the specified record.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.get_values(1, "fruit", "color")
#=> ["banana", "yellow"]
p t.get_values(0, "fruit")
#=> ["apple"]


714
715
716
717
718
719
720
# File 'lib/tb/basic.rb', line 714

def get_values(recordid, *fields)
  recordid = check_recordid(recordid)
  fields.map {|f|
    f = check_field(f)
    get_cell(recordid, f)
  }
end

#has_field?(field) ⇒ Boolean

:call-seq:

table.has_field?(field) -> true or false

returns true if the field specified by the argument is exist.

t = Tb.new %w[fruit color], 
           %w[apple red], 
           %w[banana yellow], 
           %w[orange orange] 
pp t 
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.has_field?("fruit") #=> true
p t.has_field?("foo") #=> false

Returns:

  • (Boolean)


233
234
235
236
# File 'lib/tb/basic.rb', line 233

def has_field?(field)
  field = check_field_type(field)
  @tbl.has_key?(field)
end

#insert(record) ⇒ Object

:call-seq:

table.insert({field1=>value1, ...})

inserts a record. The record is represented as a hash which keys are field names.

This method returned the recordid of the inserted record.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
recordid = t.insert({"fruit"=>"grape", "color"=>"purple"})
p recordid
#=> 3
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
#    {"_recordid"=>3, "fruit"=>"grape", "color"=>"purple"}>


574
575
576
577
578
# File 'lib/tb/basic.rb', line 574

def insert(record)
  recordid = allocate_recordid
  update_record(recordid, record)
  recordid
end

#insert_values(fields, *values_list) ⇒ Object

call-seq

table.insert_values(fields, values1, values2, ...) -> [recordid1, recordid2, ...]

inserts records. The records are represented by fields and values separately. The first argument specifies the field names as an array. The second argument specifies the first record values as an array. The third argument specifies the second record values and so on. The third and subsequent arguments are optional.

This method return an array of recordids.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.insert_values(["fruit", "color"], ["grape", "purple"], ["cherry", "red"])
#=> [3, 4]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
#    {"_recordid"=>3, "fruit"=>"grape", "color"=>"purple"}
#    {"_recordid"=>4, "fruit"=>"cherry", "color"=>"red"}>


611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/tb/basic.rb', line 611

def insert_values(fields, *values_list)
  recordids = []
  values_list.each {|values|
    if values.length != fields.length
      raise ArgumentError, "#{fields.length} fields expected but #{values.length} values given"
    end
    h = {}
    fields.each_with_index {|f, i|
      v = values[i]
      h[f] = v
    }
    recordids << insert(h)
  }
  recordids
end

#list_fieldsObject

:call-seq:

table.list_fields -> [field1, field2, ...]

returns the list of non-reserved field names as an array of strings.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.list_fields #=> ["fruit", "color"]


254
255
256
# File 'lib/tb/basic.rb', line 254

def list_fields
  @field_list.reject {|f| f.start_with?("_") }
end

#list_fields_allObject

:call-seq:

table.list_fields_all -> [field1, field2, ...]

returns the list of reserved and non-reserved field names as an array of strings.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.list_fields_all #=> ["_recordid", "fruit", "color"]


274
275
276
# File 'lib/tb/basic.rb', line 274

def list_fields_all
  @field_list.dup
end

#list_recordidsObject

:call-seq:

table.list_recordids -> [recordid1, recordid2, ...]

returns the list of recordids as an array of integers.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.list_recordids #=> [0, 1, 2]


325
326
327
# File 'lib/tb/basic.rb', line 325

def list_recordids
  @tbl["_recordid"].compact
end

#natjoin2(table2) ⇒ Object

:call-seq:

table1.natjoin2(table2)


903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
# File 'lib/tb/basic.rb', line 903

def natjoin2(table2)
  table1 = self
  fields1 = table1.list_fields
  fields2 = table2.list_fields
  common_fields = fields1 & fields2
  total_fields = fields1 | fields2
  unique_fields2 = fields2 - common_fields
  h = {}
  table2.each {|rec2|
    k = rec2.values_at(*common_fields)
    (h[k] ||= []) << rec2
  }
  result = Tb.new(fields1 | fields2)
  table1.each {|rec1|
    k = rec1.values_at(*common_fields)
    rec2_list = h[k]
    next if !rec2_list
    values = rec1.values_at(*fields1)
    rec2_list.each {|rec2|
      result.insert_values total_fields, values + rec2.values_at(*unique_fields2)
    }
  }
  result
end

#natjoin2_outer(table2, missing = nil, retain_left = true, retain_right = true) ⇒ Object

:call-seq:

table1.natjoin2_outer(table2, missing=nil, retain_left=true, retain_right=true)


930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
# File 'lib/tb/basic.rb', line 930

def natjoin2_outer(table2, missing=nil, retain_left=true, retain_right=true)
  table1 = self
  fields1 = table1.list_fields
  fields2 = table2.list_fields
  common_fields = fields1 & fields2
  total_fields = fields1 | fields2
  unique_fields2 = fields2 - common_fields
  fields2_extended = total_fields.map {|f| fields2.include?(f) ? f : nil }
  h = {}
  table2.each {|rec2|
    k = rec2.values_at(*common_fields)
    (h[k] ||= []) << rec2
  }
  result = Tb.new(total_fields)
  ids2 = {}
  table1.each {|rec1|
    k = rec1.values_at(*common_fields)
    rec2_list = h[k]
    values = rec1.values_at(*fields1)
    if !rec2_list || rec2_list.empty? 
      if retain_left
 result.insert_values total_fields, values + unique_fields2.map { missing }
	end
    else
      rec2_list.each {|rec2|
        ids2[rec2['_recordid']] = true
        result.insert_values total_fields, values + rec2.values_at(*unique_fields2)
      }
    end
  }
  if retain_right
    table2.each {|rec2|
	if !ids2[rec2['_recordid']]
 result.insert_values total_fields, fields2_extended.map {|f| f ? rec2[f] : missing }
	end
    }
  end
  result
end

#pretty_print(q) ⇒ Object

:nodoc:



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/tb/basic.rb', line 111

def pretty_print(q) # :nodoc:
  q.object_group(self) {
    each_recordid {|recordid|
      q.breakable
      fs = @field_list.reject {|f| get_cell(recordid, f).nil? }
      q.group(1, '{', '}') {
        q.seplist(fs, nil, :each) {|f|
          v = get_cell(recordid, f)
          q.group {
            q.pp f
            q.text '=>'
            q.group(1) {
              q.breakable ''
              q.pp v
            }
          }
        }
      }
    }
  }
end

#rename_field(rename_hash) ⇒ Object

:call-seq:

table.rename_field({old_field1=>new_field1, ...})

creates a new table which field names are renamed.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
pp t.rename_field("fruit"=>"food")
#=> #<Tb
#    {"_recordid"=>0, "food"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "food"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "food"=>"orange", "color"=>"orange"}>


1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
# File 'lib/tb/basic.rb', line 1015

def rename_field(rename_hash)
  rh = {}
  rename_hash.each {|of, nf|
    of = check_field(of)
    nf = check_field_type(nf)
    rh[of] = nf
  }
  result = Tb.new
  field_list = self.list_fields
  field_list.each {|of|
    nf = rh.fetch(of, of)
    result.define_field(nf)
  }
  each_recordid {|recordid|
    values = get_values(recordid, *field_list)
    result.allocate_recordid(recordid)
    field_list.each_with_index {|of, i|
      nf = rh.fetch(of, of)
      result.set_cell(recordid, nf, values[i])
    }
  }
  result
end

#reorder_fields!(fields) ⇒ Object

:call-seq:

table.reorder_fields!(fields)

reorder the fields.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
p t.list_fields
#=> ["fruit", "color"]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.reorder_fields! %w[color fruit]
p t.list_fields
#=> ["color", "fruit"]
pp t
#=> #<Tb
#    {"_recordid"=>0, "color"=>"red", "fruit"=>"apple"}
#    {"_recordid"=>1, "color"=>"yellow", "fruit"=>"banana"}
#    {"_recordid"=>2, "color"=>"orange", "fruit"=>"orange"}>


303
304
305
306
307
# File 'lib/tb/basic.rb', line 303

def reorder_fields!(fields)
  reserved, non_resreved = @field_list.reject {|f| fields.include? f }.partition {|f| f.start_with?("_") }
  fs = reserved + fields + non_resreved
  @field_list = @field_list.sort_by {|f| fs.index(f) }
end

#reorder_records_by(&b) ⇒ Object

:call-seq:

table.reorder_records_by {|rec| ... }

creates a new table object which has same records as table but the order of the records are sorted.

The sort order is defined as similar manner to Enumerable#sort_by.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>

pp t.reorder_records_by {|rec| rec["color"] }
#=> #<Tb
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}>


1063
1064
1065
1066
1067
1068
1069
1070
# File 'lib/tb/basic.rb', line 1063

def reorder_records_by(&b)
  result = Tb.new self.list_fields
  self.sort_by(&b).each {|rec|
    recordid = result.allocate_recordid(rec["_recordid"])
    result.update_record(recordid, rec)
  }
  result
end

#replace(tbl2) ⇒ Object

:call-seq:

table.replace(table2)

replaces the contents of table same as table2.

Raises:

  • (TypeError)


102
103
104
105
106
107
108
109
# File 'lib/tb/basic.rb', line 102

def replace(tbl2)
  raise TypeError, "a Tb expected but #{tbl2.inspect}" unless Tb === tbl2
  @next_recordid = tbl2.instance_variable_get(:@next_recordid)
  @recordid2index = tbl2.instance_variable_get(:@recordid2index).dup
  @free_index = tbl2.instance_variable_get(:@free_index).dup
  @tbl = Hash[tbl2.instance_variable_get(:@tbl).map {|k, v| [k, v.dup] }]
  @field_list = tbl2.instance_variable_get(:@field_list).dup
end

#set_cell(recordid, field, value) ⇒ Object

:call-seq:

table.set_cell(recordid, field, value) -> value

sets the value of the cell identified by recordid and field.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
t.set_cell(1, "color", "green")
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"green"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>

Raises:

  • (ArgumentError)


448
449
450
451
452
453
454
# File 'lib/tb/basic.rb', line 448

def set_cell(recordid, field, value)
  recordid = check_recordid(recordid)
  field = check_field(field)
  raise ArgumentError, "can not set for reserved field: #{field.inspect}" if field.start_with?("_")
  ary = @tbl[field]
  ary[@recordid2index[recordid]] = value
end

#sizeObject

:call-seq:

table.size

returns the number of records.

t = Tb.new %w[fruit],      
           %w[apple],    
           %w[banana],       
           %w[orange]       
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple"}
#    {"_recordid"=>1, "fruit"=>"banana"}
#    {"_recordid"=>2, "fruit"=>"orange"}>
p t.size
#=> 3


346
347
348
# File 'lib/tb/basic.rb', line 346

def size
  @recordid2index.size
end

#to_aObject

:call-seq:

table.to_a -> [record1, ...]

returns an array containing all records as Tb::Record objects.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
pp t.to_a                         
#=> [#<Tb::Record: "fruit"=>"apple", "color"=>"red">,
#    #<Tb::Record: "fruit"=>"banana", "color"=>"yellow">,
#    #<Tb::Record: "fruit"=>"orange", "color"=>"orange">]


842
843
844
845
846
847
848
# File 'lib/tb/basic.rb', line 842

def to_a
  ary = []
  each_recordid {|recordid|
    ary << get_record(recordid)
  }
  ary
end

#update_record(recordid, record) ⇒ Object

:call-seq:

table.update_record(recordid, {field1=>value1, ...}) -> nil

updates the record specified by recordid.

t = Tb.new %w[fruit color],
           %w[apple red],
           %w[banana yellow],
           %w[orange orange]
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"yellow"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>
p t.update_record(1, {"color"=>"green"}) 
#=> nil
pp t
#=> #<Tb
#    {"_recordid"=>0, "fruit"=>"apple", "color"=>"red"}
#    {"_recordid"=>1, "fruit"=>"banana", "color"=>"green"}
#    {"_recordid"=>2, "fruit"=>"orange", "color"=>"orange"}>


686
687
688
689
690
691
692
693
# File 'lib/tb/basic.rb', line 686

def update_record(recordid, record)
  recordid = check_recordid(recordid)
  record.each {|f, v|
    f = check_field(f)
    set_cell(recordid, f, v)
  }
  nil
end