fat_table 0.4.2 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -62,9 +62,14 @@ module FatTable
62
62
  # :category: Constructors
63
63
 
64
64
  # Return an empty FatTable::Table object.
65
- def initialize
65
+ def initialize(*heads)
66
66
  @columns = []
67
67
  @boundaries = []
68
+ unless heads.empty?
69
+ heads.each do |h|
70
+ @columns << Column.new(header: h)
71
+ end
72
+ end
68
73
  end
69
74
 
70
75
  # :category: Constructors
@@ -205,6 +210,7 @@ module FatTable
205
210
  end
206
211
  result << hsh.to_h
207
212
  end
213
+ result.normalize_boundaries
208
214
  result
209
215
  end
210
216
 
@@ -253,6 +259,7 @@ module FatTable
253
259
  hash_row = Hash[headers.zip(row)]
254
260
  result << hash_row
255
261
  end
262
+ result.normalize_boundaries
256
263
  result
257
264
  end
258
265
 
@@ -262,6 +269,7 @@ module FatTable
262
269
  skip_blanks: true).each do |row|
263
270
  result << row.to_h
264
271
  end
272
+ result.normalize_boundaries
265
273
  result
266
274
  end
267
275
 
@@ -334,8 +342,11 @@ module FatTable
334
342
  # Set the column type for Column with the given +key+ as a String type,
335
343
  # but only if empty. Otherwise, we would have to worry about converting
336
344
  # existing items in the column to String. Perhaps that's a TODO.
337
- def set_column_to_string_type(key)
338
- column(key).force_to_string_type
345
+ def force_string!(*keys)
346
+ keys.each do |h|
347
+ column(h).force_string!
348
+ end
349
+ self
339
350
  end
340
351
 
341
352
  # :category: Attributes
@@ -523,6 +534,46 @@ module FatTable
523
534
  groups
524
535
  end
525
536
 
537
+ # Return the number of groups in the table.
538
+ def number_of_groups
539
+ boundaries.size
540
+ end
541
+
542
+ # Return the range of row indexes for boundary number +k+
543
+ def group_row_range(k)
544
+ last_k = boundaries.size - 1
545
+ if k < 0 || k > last_k
546
+ raise ArgumentError, "boundary number '#{k}' out of range in boundary_row_range"
547
+ end
548
+
549
+ if boundaries.empty?
550
+ (0..size-1)
551
+ elsif boundaries.size == 1
552
+ (0..boundaries.first)
553
+ else
554
+ # Keep index at or above zero
555
+ if k.zero?
556
+ (0..boundaries[k])
557
+ else
558
+ (boundaries[k-1]+1..boundaries[k])
559
+ end
560
+ end
561
+ end
562
+
563
+ # Return an Array of Column objects for header +col+ representing a
564
+ # sub-column for each group in the table under that header.
565
+ def group_cols(col)
566
+ normalize_boundaries
567
+ cols = []
568
+ (0..boundaries.size - 1).each do |k|
569
+ range = group_row_range(k)
570
+ tab_col = column(col)
571
+ gitems = tab_col.items[range]
572
+ cols << Column.new(header: col, items: gitems, type: tab_col.type)
573
+ end
574
+ cols
575
+ end
576
+
526
577
  # :category: Operators
527
578
 
528
579
  # Return this table mutated with all groups removed. Useful after something
@@ -545,8 +596,6 @@ module FatTable
545
596
  end
546
597
  end
547
598
 
548
- protected
549
-
550
599
  # :stopdoc:
551
600
 
552
601
  # Make sure size - 1 is last boundary and that they are unique and sorted.
@@ -558,6 +607,8 @@ module FatTable
558
607
  boundaries
559
608
  end
560
609
 
610
+ protected
611
+
561
612
  # Concatenate the array of argument bounds to this table's boundaries, but
562
613
  # increase each of the indexes in bounds by shift. This is used in the
563
614
  # #union_all method.
@@ -604,16 +655,32 @@ module FatTable
604
655
  # After sorting, the output Table will have group boundaries added after
605
656
  # each row where the sort key changes.
606
657
  def order_by(*sort_heads)
607
- sort_heads = [sort_heads].flatten
608
- rev_heads = sort_heads.select { |h| h.to_s.ends_with?('!') }
609
- sort_heads = sort_heads.map { |h| h.to_s.sub(/\!\z/, '').to_sym }
610
- rev_heads = rev_heads.map { |h| h.to_s.sub(/\!\z/, '').to_sym }
658
+ # Sort the rows in order and add to new_rows.
659
+ key_hash = partition_sort_keys(sort_heads)
611
660
  new_rows = rows.sort do |r1, r2|
612
- key1 = sort_heads.map { |h| rev_heads.include?(h) ? r2[h] : r1[h] }
613
- key2 = sort_heads.map { |h| rev_heads.include?(h) ? r1[h] : r2[h] }
614
- key1 <=> key2
661
+ # Set the sort keys based on direction
662
+ key1 = []
663
+ key2 = []
664
+ key_hash.each_pair do |h, dir|
665
+ if dir == :forward
666
+ key1 << r1[h]
667
+ key2 << r2[h]
668
+ else
669
+ key1 << r2[h]
670
+ key2 << r1[h]
671
+ end
672
+ end
673
+ # Make any booleans comparable with <=>
674
+ key1 = key1.map_booleans
675
+ key2 = key2.map_booleans
676
+
677
+ # If there are any nils, <=> will return nil, and we have to use the
678
+ # special comparison method, compare_with_nils, instead.
679
+ result = (key1 <=> key2)
680
+ result.nil? ? compare_with_nils(key1, key2) : result
615
681
  end
616
- # Add the new rows to the table, but mark a group boundary at the points
682
+
683
+ # Add the new_rows to the table, but mark a group boundary at the points
617
684
  # where the sort key changes value. NB: I use self.class.new here
618
685
  # rather than Table.new because if this class is inherited, I want the
619
686
  # new_tab to be an instance of the subclass. With Table.new, this
@@ -623,7 +690,8 @@ module FatTable
623
690
  last_key = nil
624
691
  new_rows.each_with_index do |nrow, k|
625
692
  new_tab << nrow
626
- key = nrow.fetch_values(*sort_heads)
693
+ # key = nrow.fetch_values(*sort_heads)
694
+ key = nrow.fetch_values(*key_hash.keys)
627
695
  new_tab.mark_boundary(k - 1) if last_key && key != last_key
628
696
  last_key = key
629
697
  end
@@ -631,6 +699,33 @@ module FatTable
631
699
  new_tab
632
700
  end
633
701
 
702
+ # :category: Operators
703
+
704
+ # Return a new Table sorting the rows of this Table on an any expression
705
+ # +expr+ that is valid with the +select+ method, except that they
706
+ # expression may end with an exclamation mark +!+ to indicate a reverse
707
+ # sort. The new table will have an additional column called +sort_key+
708
+ # populated with the result of evaluating the given expression and will be
709
+ # sorted (or reverse sorted) on that column.
710
+ #
711
+ # tab.order_with('date.year') => table sorted by date's year
712
+ # tab.order_with('date.year!') => table reverse sorted by date's year
713
+ #
714
+ # After sorting, the output Table will have group boundaries added after
715
+ # each row where the sort key changes.
716
+ def order_with(expr)
717
+ unless expr.is_a?(String)
718
+ raise "must call FatTable::Table\#order_with with a single string expression"
719
+ end
720
+ rev = false
721
+ if expr.match?(/\s*!\s*\z/)
722
+ rev = true
723
+ expr = expr.sub(/\s*!\s*\z/, '')
724
+ end
725
+ sort_sym = rev ? :sort_key! : :sort_key
726
+ dup.select(*headers, sort_key: expr).order_by(sort_sym)
727
+ end
728
+
634
729
  # :category: Operators
635
730
  #
636
731
  # Return a Table having the selected column expressions. Each expression can
@@ -744,7 +839,15 @@ module FatTable
744
839
  ev.eval_before_hook(locals: old_row)
745
840
  # Compute the new row.
746
841
  new_row = {}
747
- cols.each do |k|
842
+ # Allow the :omni col to stand for all columns if it is alone and
843
+ # first.
844
+ cols_to_include =
845
+ if cols.size == 1 && cols.first.as_sym == :omni
846
+ headers
847
+ else
848
+ cols
849
+ end
850
+ cols_to_include.each do |k|
748
851
  h = k.as_sym
749
852
  msg = "Column '#{h}' in select does not exist"
750
853
  raise UserError, msg unless column?(h)
@@ -910,36 +1013,6 @@ module FatTable
910
1013
  set_operation(other, :difference, distinct: false)
911
1014
  end
912
1015
 
913
- private
914
-
915
- # Apply the set operation given by ~oper~ between this table and the other
916
- # table given in the first argument. If distinct is true, eliminate
917
- # duplicates from the result.
918
- def set_operation(other, oper = :+, distinct: true, add_boundaries: true, inherit_boundaries: false)
919
- unless columns.size == other.columns.size
920
- msg = "can't apply set ops to tables with a different number of columns"
921
- raise UserError, msg
922
- end
923
- unless columns.map(&:type) == other.columns.map(&:type)
924
- msg = "can't apply a set ops to tables with different column types."
925
- raise UserError, msg
926
- end
927
- other_rows = other.rows.map { |r| r.replace_keys(headers) }
928
- result = empty_dup
929
- new_rows = rows.send(oper, other_rows)
930
- new_rows.each_with_index do |row, k|
931
- result << row
932
- result.mark_boundary if k == size - 1 && add_boundaries
933
- end
934
- if inherit_boundaries
935
- result.boundaries = normalize_boundaries
936
- other.normalize_boundaries
937
- result.append_boundaries(other.boundaries, shift: size)
938
- end
939
- result.normalize_boundaries
940
- distinct ? result.distinct : result
941
- end
942
-
943
1016
  public
944
1017
 
945
1018
  # An Array of symbols for the valid join types.
@@ -1507,5 +1580,75 @@ module FatTable
1507
1580
  yield fmt if block_given?
1508
1581
  fmt.output
1509
1582
  end
1583
+
1584
+ private
1585
+
1586
+ # Apply the set operation given by ~oper~ between this table and the other
1587
+ # table given in the first argument. If distinct is true, eliminate
1588
+ # duplicates from the result.
1589
+ def set_operation(other, oper = :+, distinct: true, add_boundaries: true, inherit_boundaries: false)
1590
+ unless columns.size == other.columns.size
1591
+ msg = "can't apply set ops to tables with a different number of columns"
1592
+ raise UserError, msg
1593
+ end
1594
+ unless columns.map(&:type) == other.columns.map(&:type)
1595
+ msg = "can't apply a set ops to tables with different column types."
1596
+ raise UserError, msg
1597
+ end
1598
+ other_rows = other.rows.map { |r| r.replace_keys(headers) }
1599
+ result = empty_dup
1600
+ new_rows = rows.send(oper, other_rows)
1601
+ new_rows.each_with_index do |row, k|
1602
+ result << row
1603
+ result.mark_boundary if k == size - 1 && add_boundaries
1604
+ end
1605
+ if inherit_boundaries
1606
+ result.boundaries = normalize_boundaries
1607
+ other.normalize_boundaries
1608
+ result.append_boundaries(other.boundaries, shift: size)
1609
+ end
1610
+ result.normalize_boundaries
1611
+ distinct ? result.distinct : result
1612
+ end
1613
+
1614
+ # Return a hash with the key being the header to sort on and the value
1615
+ # being either :forward or :reverse to indicate the sort order on that
1616
+ # key.
1617
+ def partition_sort_keys(keys)
1618
+ result = {}
1619
+ [keys].flatten.each do |h|
1620
+ if h.to_s.match?(/\s*!\s*\z/)
1621
+ result[h.to_s.sub(/\s*!\s*\z/, '').to_sym] = :reverse
1622
+ else
1623
+ result[h] = :forward
1624
+ end
1625
+ end
1626
+ result
1627
+ end
1628
+
1629
+ # The <=> operator cannot handle nils without some help. Treat a nil as
1630
+ # smaller than any other value, but equal to other nils. The two keys are assumed to be arrays of values to be
1631
+ # compared with <=>.
1632
+ def compare_with_nils(key1, key2)
1633
+ result = nil
1634
+ key1.zip(key2) do |k1, k2|
1635
+ if k1.nil? && k2.nil?
1636
+ result = 0
1637
+ next
1638
+ elsif k1.nil?
1639
+ result = -1
1640
+ break
1641
+ elsif k2.nil?
1642
+ result = 1
1643
+ break
1644
+ elsif (k1 <=> k2) == 0
1645
+ next
1646
+ else
1647
+ result = (k1 <=> k2)
1648
+ break
1649
+ end
1650
+ end
1651
+ result
1652
+ end
1510
1653
  end
1511
1654
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FatTable
4
4
  # The current version of FatTable
5
- VERSION = '0.4.2'
5
+ VERSION = '0.5.1'
6
6
  end
data/lib/fat_table.rb CHANGED
@@ -19,9 +19,12 @@ module FatTable
19
19
 
20
20
  require 'fat_table/version'
21
21
  require 'fat_table/patches'
22
+ require 'ext/array'
22
23
  require 'fat_table/evaluator'
24
+ require 'fat_table/convert'
23
25
  require 'fat_table/column'
24
26
  require 'fat_table/table'
27
+ require 'fat_table/footer'
25
28
  require 'fat_table/formatters'
26
29
  require 'fat_table/db_handle'
27
30
  require 'fat_table/errors'
@@ -58,8 +61,8 @@ module FatTable
58
61
 
59
62
  # Return an empty FatTable::Table object. You can use FatTable::Table#add_row
60
63
  # or FatTable::Table#add_column to populate the table with data.
61
- def self.new
62
- Table.new
64
+ def self.new(*args)
65
+ Table.new(*args)
63
66
  end
64
67
 
65
68
  # Construct a FatTable::Table from the contents of a CSV file given by the
data/md/README.md CHANGED
@@ -1109,8 +1109,7 @@ will raise an exception.
1109
1109
 
1110
1110
  - **`first`:** the first non-nil item in the column,
1111
1111
  - **`last`:** the last non-nil item in the column,
1112
- - **`rng`:** form a string of the form `"#{first}..#{last}"` to show the range of
1113
- values in the column,
1112
+ - **`range`:** form a Range ~~{min}..{max}~ to show the range of values in the column,
1114
1113
  - **`sum`:** for `Numeric` and `String` columns, apply &rsquo;+&rsquo; to all the non-nil
1115
1114
  values,
1116
1115
  - **`count`:** the number of non-nil values in the column,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-06 00:00:00.000000000 Z
11
+ date: 2022-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rspec
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -283,12 +297,24 @@ files:
283
297
  - TODO.org
284
298
  - bin/ft_console
285
299
  - bin/setup
300
+ - examples/create_trans.sql
301
+ - examples/quick.pdf
302
+ - examples/quick.png
303
+ - examples/quick.ppm
304
+ - examples/quick.tex
305
+ - examples/quick_small.png
306
+ - examples/quicktable.tex
307
+ - examples/trades.db
308
+ - examples/trans.csv
286
309
  - fat_table.gemspec
310
+ - lib/ext/array.rb
287
311
  - lib/fat_table.rb
288
312
  - lib/fat_table/column.rb
313
+ - lib/fat_table/convert.rb
289
314
  - lib/fat_table/db_handle.rb
290
315
  - lib/fat_table/errors.rb
291
316
  - lib/fat_table/evaluator.rb
317
+ - lib/fat_table/footer.rb
292
318
  - lib/fat_table/formatters.rb
293
319
  - lib/fat_table/formatters/aoa_formatter.rb
294
320
  - lib/fat_table/formatters/aoh_formatter.rb