fat_table 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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