fat_core 1.2.5 → 1.2.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e598f2e37937104a57320a950be61c8555e86cf
4
- data.tar.gz: bbac42039a8ec51f82b302c7c20198757c8556e0
3
+ metadata.gz: 5c93e591807760bad4ae026e144c216d03fbe3d7
4
+ data.tar.gz: 0a7bcf980e22635bdb4880e5ba140544ce03530d
5
5
  SHA512:
6
- metadata.gz: 3089937ca40448ae1b1350b4492aa47e21ee3bff6b0c043326b33a544fba3d17167bbd89c7e953b9a7aab48eee240df6809b55d84b3e7d1224fee2f5ba382684
7
- data.tar.gz: 68786a6dfe2114b1ff21e650d23ec3f476b79012d0cae7f9173f99eba65431850b4cb44799984d661cb9215e55cb2a2e8c2fb8db1875cc81e3244c8fbdb9c55a
6
+ metadata.gz: 91bdb2d5b8cf2dc577967462433c73fa18f6de41c72ed27564e3cf5dacecc60825ecac131aea4f1a6182e24fbecd18b3b0d752a27bee0de258446baa705dd421
7
+ data.tar.gz: aa85f79b609f356e333adb6816a9764b2bcf1cad2f7bd5e7ba1a00ef83c81310302d450b81d403890c9d5c808b208435d3fa10db97aa778a5938e3d5b0eba817
@@ -6,18 +6,19 @@ module FatCore
6
6
  class Column
7
7
  attr_reader :header, :type, :items
8
8
 
9
- TYPES = %w(NilClass TrueClass FalseClass Date DateTime Numeric String)
9
+ TYPES = %w(NilClass Boolean DateTime Numeric String)
10
10
 
11
- def initialize(header:, type: 'NilClass', items: [])
11
+ def initialize(header:, items: [])
12
12
  @header = header.as_sym
13
- @type = type
13
+ @type = 'NilClass'
14
14
  raise "Unknown column type '#{type}" unless TYPES.include?(@type.to_s)
15
- @items = items
15
+ @items = []
16
+ items.each { |i| self << i }
16
17
  end
17
18
 
18
- def <<(itm)
19
- items << convert_to_type(itm)
20
- end
19
+ ##########################################################################
20
+ # Attributes
21
+ ##########################################################################
21
22
 
22
23
  def [](k)
23
24
  items[k]
@@ -31,16 +32,17 @@ module FatCore
31
32
  items.size
32
33
  end
33
34
 
35
+ def empty?
36
+ items.empty?
37
+ end
38
+
34
39
  def last_i
35
40
  size - 1
36
41
  end
37
42
 
38
- # Return a new Column appending the items of other to our items, checking
39
- # for type compatibility.
40
- def +(other)
41
- raise 'Cannot combine columns with different types' unless type == other.type
42
- Column.new(header: header, type: type, items: items + other.items)
43
- end
43
+ ##########################################################################
44
+ # Aggregates
45
+ ##########################################################################
44
46
 
45
47
  def first
46
48
  items.compact.first
@@ -56,21 +58,78 @@ module FatCore
56
58
  end
57
59
 
58
60
  def sum
61
+ only_with('sum', 'Numeric', 'String')
59
62
  items.compact.sum
60
63
  end
61
64
 
62
65
  def min
66
+ only_with('min', 'NilClass', 'Numeric', 'String', 'DateTime')
63
67
  items.compact.min
64
68
  end
65
69
 
66
70
  def max
71
+ only_with('max', 'NilClass', 'Numeric', 'String', 'DateTime')
67
72
  items.compact.max
68
73
  end
69
74
 
70
75
  def avg
71
- sum / items.compact.size.to_d
76
+ only_with('avg', 'DateTime', 'Numeric')
77
+ if type == 'DateTime'
78
+ avg_jd = items.compact.map(&:jd).sum / items.compact.size.to_d
79
+ DateTime.jd(avg_jd)
80
+ else
81
+ sum / items.compact.size.to_d
82
+ end
72
83
  end
73
84
 
85
+ def any?
86
+ only_with('any?', 'Boolean')
87
+ items.compact.any?
88
+ end
89
+
90
+ def all?
91
+ only_with('all?', 'Boolean')
92
+ items.compact.all?
93
+ end
94
+
95
+ def none?
96
+ only_with('any?', 'Boolean')
97
+ items.compact.none?
98
+ end
99
+
100
+ def one?
101
+ only_with('any?', 'Boolean')
102
+ items.compact.one?
103
+ end
104
+
105
+ private
106
+
107
+ def only_with(agg, *types)
108
+ unless types.include?(type)
109
+ raise "Aggregate '#{agg}' cannot be applied to a #{type} column"
110
+ end
111
+ end
112
+
113
+ public
114
+
115
+ ##########################################################################
116
+ # Construction
117
+ ##########################################################################
118
+
119
+ # Append item to end of the column
120
+ def <<(itm)
121
+ items << convert_to_type(itm)
122
+ end
123
+
124
+ # Return a new Column appending the items of other to our items, checking
125
+ # for type compatibility.
126
+ def +(other)
127
+ raise 'Cannot combine columns with different types' unless type == other.type
128
+ Column.new(header: header, items: items + other.items)
129
+ end
130
+
131
+ private
132
+
74
133
  # Convert val to the type of key, a ruby class constant, such as Date,
75
134
  # Numeric, etc. If type is NilClass, the type is open, and a non-blank val
76
135
  # will attempt conversion to one of the allowed types, typing it as a String
@@ -87,12 +146,11 @@ module FatCore
87
146
  if val != false && val.blank?
88
147
  # Leave the type of the column open. Unfortunately, false counts as
89
148
  # blank and we don't want it to. It should be classified as a boolean.
90
- val = nil
149
+ new_val = nil
91
150
  else
92
151
  # Only non-blank values are allowed to set the type of the column
93
- val_class = val.class
94
152
  bool_val = convert_to_boolean(val)
95
- val =
153
+ new_val =
96
154
  if bool_val.nil?
97
155
  convert_to_date_time(val) ||
98
156
  convert_to_numeric(val) ||
@@ -101,45 +159,61 @@ module FatCore
101
159
  bool_val
102
160
  end
103
161
  @type =
104
- if val == true || val == false
162
+ if new_val == true || new_val == false
105
163
  'Boolean'
106
- elsif val.is_a?(Numeric)
164
+ elsif new_val.is_a?(Date) || new_val.is_a?(DateTime)
165
+ 'DateTime'
166
+ elsif new_val.is_a?(Numeric)
107
167
  'Numeric'
168
+ elsif new_val.is_a?(String)
169
+ 'String'
108
170
  else
109
- val.class.name
171
+ raise "Cannot add #{val} of type #{new_val.class.name} to a column"
110
172
  end
111
173
  end
112
- val
113
- when 'Boolean', 'TrueClass', 'FalseClass'
114
- val_class = val.class
115
- val = convert_to_boolean(val)
174
+ new_val
175
+ when 'Boolean'
116
176
  if val.nil?
117
- raise "Inconsistent value in a Boolean column #{header} has class #{val_class}"
177
+ nil
178
+ else
179
+ new_val = convert_to_boolean(val)
180
+ if new_val.nil?
181
+ raise "Attempt to add '#{val}' to a column already typed as #{type}"
182
+ end
183
+ new_val
118
184
  end
119
- val
120
- when 'DateTime', 'Date'
121
- val_class = val.class
122
- val = convert_to_date_time(val)
123
- unless val
124
- raise "Inconsistent value in a DateTime column #{key} has class #{val_class}"
185
+ when 'DateTime'
186
+ if val.nil?
187
+ nil
188
+ else
189
+ new_val = convert_to_date_time(val)
190
+ if new_val.nil?
191
+ raise "Attempt to add '#{val}' to a column already typed as #{type}"
192
+ end
193
+ new_val
125
194
  end
126
- val
127
195
  when 'Numeric'
128
- val_class = val.class
129
- val = convert_to_numeric(val)
130
- unless val
131
- raise "Inconsistent value in a Numeric column #{key} has class #{val_class}"
196
+ if val.nil?
197
+ nil
198
+ else
199
+ new_val = convert_to_numeric(val)
200
+ if new_val.nil?
201
+ raise "Attempt to add '#{val}' to a column already typed as #{type}"
202
+ end
203
+ new_val
132
204
  end
133
- val
134
205
  when 'String'
135
- val_class = val.class
136
- val = convert_to_string(val)
137
- unless val
138
- raise "Inconsistent value in a String column #{key} has class #{val_class}"
206
+ if val.nil?
207
+ nil
208
+ else
209
+ new_val = convert_to_string(val)
210
+ if new_val.nil?
211
+ raise "Attempt to add '#{val}' to a column already typed as #{type}"
212
+ end
213
+ new_val
139
214
  end
140
- val
141
215
  else
142
- raise "Unknown object of class #{type} in Table"
216
+ raise "Mysteriously, column has unknown type '#{type}'"
143
217
  end
144
218
  end
145
219
 
@@ -174,7 +174,6 @@ module FatCore
174
174
  raise "Header #{h} does not exist" unless headers.include?(h)
175
175
  new_heads << h
176
176
  new_cols[h] = Column.new(header: h,
177
- type: column(h).type,
178
177
  items: column(h).items)
179
178
  when Hash
180
179
  exp.each_pair do |key, xp|
@@ -184,7 +183,6 @@ module FatCore
184
183
  raise "Header #{key} does not exist" unless column?(key)
185
184
  new_heads << h
186
185
  new_cols[h] = Column.new(header: h,
187
- type: column(key).type,
188
186
  items: column(key).items)
189
187
  when String
190
188
  # Evaluate xp in the context of a binding including a local
@@ -314,8 +312,7 @@ module FatCore
314
312
  items = rows.map { |r| r[h] }
315
313
  new_h = "#{agg_func}_#{h}"
316
314
  new_row[new_h] = Column.new(header: h,
317
- items: items,
318
- type: column(h).type).send(agg_func)
315
+ items: items).send(agg_func)
319
316
  end
320
317
  new_row
321
318
  end
@@ -1,7 +1,7 @@
1
1
  module FatCore
2
2
  MAJOR = 1
3
3
  MINOR = 2
4
- PATCH = 5
4
+ PATCH = 6
5
5
 
6
6
  VERSION = [MAJOR, MINOR, PATCH].compact.join('.')
7
7
  end
@@ -0,0 +1,208 @@
1
+ require 'spec_helper'
2
+
3
+ module FatCore
4
+ describe Column do
5
+ describe 'construction' do
6
+ it 'should be able to append items to the column' do
7
+ c = Column.new(header: 'junk')
8
+ expect(c.type).to eq('NilClass')
9
+ c << '2.71828'
10
+ expect(c.items).to eq([2.71828])
11
+ expect(c.type).to eq('Numeric')
12
+ end
13
+
14
+ it 'should leave the type of an all-nil column open' do
15
+ c = Column.new(header: :bool, items: [nil, nil, nil, nil])
16
+ expect(c.type).to eq('NilClass')
17
+ expect(c[0]).to eq(nil)
18
+ expect(c[1]).to eq(nil)
19
+ expect(c[2]).to eq(nil)
20
+ expect(c[3]).to eq(nil)
21
+ # But then, assign a type when a recognizable type comes along.
22
+ c << '625.18'
23
+ expect(c.type).to eq('Numeric')
24
+ expect { c << '2018-05-06' }.to raise_error /already typed as Numeric/
25
+ end
26
+
27
+ it 'should recognize boolean columns' do
28
+ c = Column.new(header: :bool, items: [nil, 'F', 'F', 'T'])
29
+ expect(c.type).to eq('Boolean')
30
+ c = Column.new(header: :bool,
31
+ items: [nil, 'N', 'no', 'yes', 'false', 'TRUE', nil])
32
+ expect(c.type).to eq('Boolean')
33
+ expect(c[0]).to eq(nil)
34
+ expect(c[1]).to eq(false)
35
+ expect(c[2]).to eq(false)
36
+ expect(c[3]).to eq(true)
37
+ expect(c[4]).to eq(false)
38
+ expect(c[5]).to eq(true)
39
+ expect(c[6]).to eq(nil)
40
+ end
41
+
42
+ it 'should recognize DateTime columns but allow Date and nil' do
43
+ c = Column.new(header: :when,
44
+ items: [nil, '2015-01-21', '[2015-01-12]',
45
+ '<2011-01-06>', nil, '<2017-01-25 Wed 10:00>'])
46
+ expect(c.type).to eq('DateTime')
47
+ expect(c[0]).to eq(nil)
48
+ expect(c[1]).to eq(Date.parse('2015-01-21'))
49
+ expect(c[2]).to eq(Date.parse('2015-01-12'))
50
+ expect(c[3]).to eq(Date.parse('2011-01-06'))
51
+ expect(c[4]).to eq(nil)
52
+ expect(c[5]).to eq(DateTime.parse('2017-01-25 Wed 10:00'))
53
+ expect(c[1].class).to eq(Date)
54
+ expect(c[5].class).to eq(DateTime)
55
+ end
56
+
57
+ it 'should recognize Numeric columns but allow nils and Integers' do
58
+ c = Column.new(header: :when,
59
+ items: [nil, '20151', '3.14159',
60
+ BigDecimal('2.718281828'), nil,
61
+ '45024098204982340982049802348'])
62
+ expect(c.type).to eq('Numeric')
63
+ expect(c[0]).to eq(nil)
64
+ expect(c[1]).to eq(20151)
65
+ expect(c[2]).to eq(3.14159)
66
+ expect(c[3]).to eq(2.718281828)
67
+ expect(c[4]).to eq(nil)
68
+ expect(c[5]).to eq(45024098204982340982049802348)
69
+ expect(c[0].class).to eq(NilClass)
70
+ expect(c[1].class).to eq(Fixnum)
71
+ expect(c[2].class).to eq(BigDecimal)
72
+ expect(c[3].class).to eq(BigDecimal)
73
+ expect(c[4].class).to eq(NilClass)
74
+ expect(c[5].class).to eq(Bignum)
75
+ end
76
+
77
+ it 'should recognize String columns but allow nils and Integers' do
78
+ c = Column.new(header: :when,
79
+ items: [nil, 'Four', 'score', 'and', nil, '7 years'])
80
+ expect(c.type).to eq('String')
81
+ expect(c[0]).to eq(nil)
82
+ expect(c[1]).to eq('Four')
83
+ expect(c[2]).to eq('score')
84
+ expect(c[3]).to eq('and')
85
+ expect(c[4]).to eq(nil)
86
+ expect(c[5]).to eq('7 years')
87
+ expect(c[0].class).to eq(NilClass)
88
+ expect(c[1].class).to eq(String)
89
+ expect(c[2].class).to eq(String)
90
+ expect(c[3].class).to eq(String)
91
+ expect(c[4].class).to eq(NilClass)
92
+ expect(c[5].class).to eq(String)
93
+ end
94
+ end
95
+
96
+ describe 'attribute getting and setting' do
97
+ it 'should be able to retrieve items by index number' do
98
+ c = Column.new(header: :when,
99
+ items: [nil, '20151', '3.14159',
100
+ BigDecimal('2.718281828'), nil,
101
+ '45024098204982340982049802348'])
102
+ expect(c[0]).to eq(nil)
103
+ expect(c[1]).to eq(20151)
104
+ expect(c[2]).to eq(3.14159)
105
+ expect(c[3]).to eq(2.718281828)
106
+ expect(c[4]).to eq(nil)
107
+ expect(c[5]).to eq(45024098204982340982049802348)
108
+ end
109
+
110
+ it 'should respond to to_a, size, empty?, and last_i' do
111
+ arr = [nil, '20151', '3.14159',
112
+ BigDecimal('2.718281828'), nil,
113
+ '45024098204982340982049802348']
114
+ c = Column.new(header: :when, items: arr)
115
+ expect(c.to_a).to eq([nil, 20151, 3.14159, 2.718281828,
116
+ nil, 45024098204982340982049802348])
117
+ expect(c.to_a.class).to eq(Array)
118
+ expect(c.size).to eq(6)
119
+ expect(c.empty?).to eq(false)
120
+ expect(c.last_i).to eq(5)
121
+ end
122
+ end
123
+
124
+ describe 'aggregates' do
125
+ before :each do
126
+ @nil_col =
127
+ Column.new(header: :none, items: [nil, nil, nil, nil])
128
+ @bool_col =
129
+ Column.new(header: :bool,
130
+ items: [nil, 'N', 'no', 'yes', 'false', 'TRUE', nil])
131
+ @date_col =
132
+ Column.new(header: :when,
133
+ items: [nil, '2015-01-21', '[2015-01-12]',
134
+ '<2011-01-06>', nil, '<2017-01-25 Wed 10:00>'])
135
+ @num_col =
136
+ Column.new(header: :nums,
137
+ items: [nil, '20151', '3.14159',
138
+ BigDecimal('2.718281828'), nil, '45024'])
139
+ @str_col =
140
+ Column.new(header: :strs,
141
+ items: [nil, 'Four', 'score', 'and', nil, '7 years'])
142
+ end
143
+
144
+ it 'should be able apply to first to appropriate columns' do
145
+ expect(@nil_col.first).to eq(nil)
146
+ expect(@bool_col.first).to eq(false)
147
+ expect(@date_col.first).to eq(Date.parse('2015-01-21'))
148
+ expect(@num_col.first).to eq(20151)
149
+ expect(@str_col.first).to eq('Four')
150
+ end
151
+
152
+ it 'should be able to apply last to appropriate columns' do
153
+ expect(@nil_col.last).to eq(nil)
154
+ expect(@bool_col.last).to eq(true)
155
+ expect(@date_col.last).to eq(DateTime.parse('2017-01-25 10am'))
156
+ expect(@num_col.last).to eq(45024)
157
+ expect(@str_col.last).to eq('7 years')
158
+ end
159
+
160
+ it 'should be able to apply rng_s to appropriate columns' do
161
+ expect(@nil_col.rng_s).to eq('..')
162
+ expect(@bool_col.rng_s).to eq('false..true')
163
+ expect(@date_col.rng_s).to eq('2015-01-21..2017-01-25T10:00:00+00:00')
164
+ expect(@num_col.rng_s).to eq('20151..45024')
165
+ expect(@str_col.rng_s).to eq('Four..7 years')
166
+ end
167
+
168
+ it 'should be able to sum to appropriate columns' do
169
+ expect { @nil_col.sum }.to raise_error(/cannot be applied/)
170
+ expect { @bool_col.sum }.to raise_error(/cannot be applied/)
171
+ expect { @date_col.sum }.to raise_error(/cannot be applied/)
172
+ expect(@num_col.sum).to eq(65180.859871828)
173
+ expect(@str_col.sum).to eq('Fourscoreand7 years')
174
+ end
175
+
176
+ it 'should be able to min to appropriate columns' do
177
+ expect(@nil_col.min).to eq(nil)
178
+ expect { @bool_col.min }.to raise_error(/cannot be applied/)
179
+ expect(@date_col.min).to eq(Date.parse('2011-01-06'))
180
+ expect(@num_col.min).to eq(BigDecimal('2.718281828'))
181
+ expect(@str_col.min).to eq('7 years')
182
+ end
183
+
184
+ it 'should be able to max to appropriate columns' do
185
+ expect(@nil_col.max).to eq(nil)
186
+ expect { @bool_col.max }.to raise_error(/cannot be applied/)
187
+ expect(@date_col.max).to eq(DateTime.parse('2017-01-25 10am'))
188
+ expect(@num_col.max).to eq(45024)
189
+ expect(@str_col.max).to eq('score')
190
+ end
191
+
192
+ it 'should be able to apply avg to appropriate columns' do
193
+ expect { @nil_col.avg }.to raise_error(/cannot be applied/)
194
+ expect { @bool_col.avg }.to raise_error(/cannot be applied/)
195
+ expect(@date_col.avg).to eq(DateTime.parse('2014-07-17 12pm'))
196
+ expect(@num_col.avg).to eq(16295.214967957)
197
+ expect{ @str_col.avg }.to raise_error(/cannot be applied/)
198
+ end
199
+
200
+ it 'should be able to apply boolean aggregates to boolean columns' do
201
+ expect(@bool_col.any?).to eq(true)
202
+ expect(@bool_col.all?).to eq(false)
203
+ expect(@bool_col.none?).to eq(false)
204
+ expect(@bool_col.one?).to eq(false)
205
+ end
206
+ end
207
+ end
208
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
@@ -222,6 +222,7 @@ files:
222
222
  - spec/example_files/goldberg.org
223
223
  - spec/example_files/wpcs.csv
224
224
  - spec/lib/array_spec.rb
225
+ - spec/lib/column_spec.rb
225
226
  - spec/lib/date_spec.rb
226
227
  - spec/lib/enumerable_spec.rb
227
228
  - spec/lib/evaluator_spec.rb
@@ -264,6 +265,7 @@ test_files:
264
265
  - spec/example_files/goldberg.org
265
266
  - spec/example_files/wpcs.csv
266
267
  - spec/lib/array_spec.rb
268
+ - spec/lib/column_spec.rb
267
269
  - spec/lib/date_spec.rb
268
270
  - spec/lib/enumerable_spec.rb
269
271
  - spec/lib/evaluator_spec.rb