tablestakes 0.8.3

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +218 -0
  3. data/capitals.sorted +50 -0
  4. data/capitals.txt +51 -0
  5. data/cities.txt +290 -0
  6. data/doc/Table.html +1174 -0
  7. data/doc/created.rid +2 -0
  8. data/doc/images/add.png +0 -0
  9. data/doc/images/arrow_up.png +0 -0
  10. data/doc/images/brick.png +0 -0
  11. data/doc/images/brick_link.png +0 -0
  12. data/doc/images/bug.png +0 -0
  13. data/doc/images/bullet_black.png +0 -0
  14. data/doc/images/bullet_toggle_minus.png +0 -0
  15. data/doc/images/bullet_toggle_plus.png +0 -0
  16. data/doc/images/date.png +0 -0
  17. data/doc/images/delete.png +0 -0
  18. data/doc/images/find.png +0 -0
  19. data/doc/images/loadingAnimation.gif +0 -0
  20. data/doc/images/macFFBgHack.png +0 -0
  21. data/doc/images/package.png +0 -0
  22. data/doc/images/page_green.png +0 -0
  23. data/doc/images/page_white_text.png +0 -0
  24. data/doc/images/page_white_width.png +0 -0
  25. data/doc/images/plugin.png +0 -0
  26. data/doc/images/ruby.png +0 -0
  27. data/doc/images/tag_blue.png +0 -0
  28. data/doc/images/tag_green.png +0 -0
  29. data/doc/images/transparent.png +0 -0
  30. data/doc/images/wrench.png +0 -0
  31. data/doc/images/wrench_orange.png +0 -0
  32. data/doc/images/zoom.png +0 -0
  33. data/doc/index.html +71 -0
  34. data/doc/js/darkfish.js +155 -0
  35. data/doc/js/jquery.js +18 -0
  36. data/doc/js/navigation.js +142 -0
  37. data/doc/js/search.js +94 -0
  38. data/doc/js/search_index.js +1 -0
  39. data/doc/js/searcher.js +228 -0
  40. data/doc/rdoc.css +595 -0
  41. data/doc/table_of_contents.html +88 -0
  42. data/lib/tablestakes.rb +407 -0
  43. data/spec/factories.rb +16 -0
  44. data/spec/spec_helper.rb +11 -0
  45. data/spec/table_spec.rb +179 -0
  46. data/test.tab +4 -0
  47. metadata +110 -0
@@ -0,0 +1,88 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html>
4
+ <head>
5
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
6
+
7
+ <title>Table of Contents - RDoc Documentation</title>
8
+
9
+ <link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet">
10
+
11
+ <script type="text/javascript">
12
+ var rdoc_rel_prefix = "./";
13
+ </script>
14
+
15
+ <script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script>
16
+ <script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script>
17
+ <script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script>
18
+ <script type="text/javascript" charset="utf-8" src="./js/search.js"></script>
19
+ <script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script>
20
+ <script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script>
21
+
22
+
23
+ <body class="indexpage">
24
+ <h1>Table of Contents - RDoc Documentation</h1>
25
+
26
+
27
+ <h2 id="classes">Classes/Modules</h2>
28
+ <ul>
29
+ <li class="class">
30
+ <a href="Table.html">Table</a>
31
+ </li>
32
+
33
+ </ul>
34
+
35
+ <h2 id="methods">Methods</h2>
36
+ <ul>
37
+
38
+ <li class="method"><a href="Table.html#method-c-new">::new &mdash; Table</a>
39
+
40
+ <li class="method"><a href="Table.html#method-i-bottom">#bottom &mdash; Table</a>
41
+
42
+ <li class="method"><a href="Table.html#method-i-column">#column &mdash; Table</a>
43
+
44
+ <li class="method"><a href="Table.html#method-i-count">#count &mdash; Table</a>
45
+
46
+ <li class="method"><a href="Table.html#method-i-get_columns">#get_columns &mdash; Table</a>
47
+
48
+ <li class="method"><a href="Table.html#method-i-get_rows">#get_rows &mdash; Table</a>
49
+
50
+ <li class="method"><a href="Table.html#method-i-intersect">#intersect &mdash; Table</a>
51
+
52
+ <li class="method"><a href="Table.html#method-i-join">#join &mdash; Table</a>
53
+
54
+ <li class="method"><a href="Table.html#method-i-length">#length &mdash; Table</a>
55
+
56
+ <li class="method"><a href="Table.html#method-i-row">#row &mdash; Table</a>
57
+
58
+ <li class="method"><a href="Table.html#method-i-select">#select &mdash; Table</a>
59
+
60
+ <li class="method"><a href="Table.html#method-i-size">#size &mdash; Table</a>
61
+
62
+ <li class="method"><a href="Table.html#method-i-sub">#sub &mdash; Table</a>
63
+
64
+ <li class="method"><a href="Table.html#method-i-sub-21">#sub! &mdash; Table</a>
65
+
66
+ <li class="method"><a href="Table.html#method-i-tally">#tally &mdash; Table</a>
67
+
68
+ <li class="method"><a href="Table.html#method-i-to_a">#to_a &mdash; Table</a>
69
+
70
+ <li class="method"><a href="Table.html#method-i-to_s">#to_s &mdash; Table</a>
71
+
72
+ <li class="method"><a href="Table.html#method-i-top">#top &mdash; Table</a>
73
+
74
+ <li class="method"><a href="Table.html#method-i-union">#union &mdash; Table</a>
75
+
76
+ <li class="method"><a href="Table.html#method-i-where">#where &mdash; Table</a>
77
+
78
+ <li class="method"><a href="Table.html#method-i-write_file">#write_file &mdash; Table</a>
79
+
80
+ </ul>
81
+
82
+
83
+ <footer id="validator-badges">
84
+ <p><a href="http://validator.w3.org/check/referer">[Validate]</a>
85
+ <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 4.0.0.
86
+ <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3.
87
+ </footer>
88
+
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # Tablestakes is an implementation of a generic table class
4
+ # which takes input from a tab-delimited file and creates a
5
+ # generic table data structure that can be manipulated with
6
+ # methods similar to the way a database table may be manipulated.
7
+ #
8
+ # Author:: J.B. Folkerts (mailto:jbf@pentambic.com)
9
+ # Copyright:: Copyright (c) 2014 J.B. Folkerts
10
+ # License:: Distributes under the same terms as Ruby
11
+
12
+ # This class is a Ruby representation of a table. All data is captured as
13
+ # type +String+ by default. Columns are referred to by their +String+ headers
14
+ # which are assumed to be identified in the first row of the input file.
15
+ # Output is written by default to tab-delimited files with the first row
16
+ # serving as the header names.
17
+
18
+ class Table
19
+ # The headers attribute contains the table headers used to reference
20
+ # columns in the +Table+. All headers are represented as +String+ types.
21
+ attr_reader :headers
22
+ @headers =[]
23
+ @table = {}
24
+ @indices = {}
25
+ # Structure of @table hash
26
+ # { :col1 => [1, 2, 3], :col2 => [1, 2, 3] }
27
+
28
+
29
+ # Instantiate a +Table+ object using a tab-delimited file
30
+ #
31
+ # +input+:: OPTIONAL +Array+ of rows or +String+ to identify the name of the tab-delimited file to read
32
+ def initialize(input=nil)
33
+ @headers = []
34
+ @table = {}
35
+ @indices = {}
36
+
37
+ if input.respond_to?(:fetch)
38
+ if input[0].respond_to?(:fetch)
39
+ #create +Table+ from rows
40
+ add_rows(input)
41
+ end
42
+ elsif input.respond_to?(:upcase)
43
+ # a string, then read_file
44
+ read_file(input)
45
+ elsif input.respond_to?(:headers)
46
+ init(input)
47
+ end
48
+ # else create empty +Table+
49
+ end
50
+
51
+ # Return a copy of a column from the table, identified by column name.
52
+ # Returns +nil+ if column name not found.
53
+ #
54
+ # +colname+:: +String+ to identify the name of the column
55
+ def column(colname)
56
+ # check arguments
57
+ return nil unless @table.has_key?(colname)
58
+
59
+ Array(@table[colname])
60
+ end
61
+
62
+ # Return a copy of a row from the table as an +Array+, given an index
63
+ # (i.e. row number). Returns empty Array if the index is out of bounds.
64
+ #
65
+ # +index+:: +FixNum+ indicating index of the row.
66
+ def row(index)
67
+ Array(get_row(index))
68
+ end
69
+
70
+ # Converts a +Table+ object to a tab-delimited string.
71
+ #
72
+ # none
73
+ def to_s
74
+ result = @headers.join("\t") << "\n"
75
+
76
+ @table[@headers.first].length.times do |row|
77
+ @headers.each do |col|
78
+ result << @table[col][row].to_s
79
+ unless col == @headers.last
80
+ result << "\t"
81
+ else
82
+ result << "\n"
83
+ end
84
+ end
85
+ end
86
+ result
87
+ end
88
+
89
+ # Converts a +Table+ object to an array of arrays (each row)
90
+ #
91
+ # none
92
+ def to_a
93
+ result = [ Array(@headers) ]
94
+
95
+ @table[@headers.first].length.times do |row|
96
+ items = []
97
+ @headers.each do |col|
98
+ items << @table[col][row]
99
+ end
100
+ result << items
101
+ end
102
+ result
103
+ end
104
+
105
+ # Counts the number of instances of a particular string, given a column name,
106
+ # and returns an integer >= 0. Returns +nil+ if the column is not found. If
107
+ # no parameters are given, returns the number of rows in the table.
108
+ #
109
+ # +colname+:: OPTIONAL +String+ to identify the column to count
110
+ # +value+:: OPTIONAL +String+ value to count
111
+ def count(colname=nil, value=nil)
112
+ if colname.nil? || value.nil?
113
+ if @table.size > 0
114
+ @table.each_key {|e| return @table.fetch(e).length }
115
+ else
116
+ return nil
117
+ end
118
+ end
119
+
120
+ if @table[colname]
121
+ result = 0
122
+ @table[colname].each do |val|
123
+ val == value.to_s ? result += 1 : nil
124
+ end
125
+ result
126
+ else
127
+ nil
128
+ end
129
+ end
130
+
131
+ alias :size :count
132
+ alias :length :count
133
+
134
+ # Counts the number of instances of a particular string, given a column name,
135
+ # and returns an integer >= 0. Returns +nil+ if the column is not found. If
136
+ # no parameters are given, returns the number of rows in the table.
137
+ #
138
+ # +colname+:: +String+ to identify the column to count
139
+ # +num+:: OPTIONAL +String+ number of values to return
140
+ def top(colname, num=1)
141
+ freq = tally(colname).to_a[1..-1].sort_by {|k,v| v }.reverse
142
+ return Table.new(freq[0..num-1].unshift(["State","Count"]))
143
+ end
144
+
145
+
146
+ # Counts the number of instances of a particular string, given a column name,
147
+ # and returns an integer >= 0. Returns +nil+ if the column is not found. If
148
+ # no parameters are given, returns the number of rows in the table.
149
+ #
150
+ # +colname+:: +String+ to identify the column to count
151
+ # +num+:: OPTIONAL +String+ number of values to return
152
+ def bottom(colname, num=1)
153
+ freq = tally(colname).to_a[1..-1].sort_by {|k,v| v }
154
+ return Table.new(freq[0..num-1].unshift(["State","Count"]))
155
+ end
156
+
157
+
158
+
159
+ # Count instances in a particular field/column and return a +Table+ of the results.
160
+ # Returns +nil+ if the column is not found.
161
+ #
162
+ # +colname+:: +String+ to identify the column to tally
163
+ def tally(colname)
164
+ # check arguments
165
+ return nil unless @table.has_key?(colname)
166
+
167
+ result = {}
168
+ @table[colname].each do |val|
169
+ result.has_key?(val) ? result[val] += 1 : result[val] = 1
170
+ end
171
+ return Table.new([[colname,"Count"]] + result.to_a)
172
+ end
173
+
174
+ # Select columns from the table, given one or more column names. Returns an instance
175
+ # of +Table+ with the results. Returns nil if any column is not valid.
176
+ #
177
+ # +columns+:: Variable +String+ arguments to identify the columns to select
178
+ def select(*columns)
179
+ # check arguments
180
+ columns.each do |c|
181
+ return nil unless @table.has_key?(c)
182
+ end
183
+
184
+ result = []
185
+ result_headers = []
186
+ columns.each { |col| @headers.include?(col) ? result_headers << col : nil }
187
+ result << result_headers
188
+ @table[@headers.first].length.times do |row|
189
+ this_row = []
190
+ result_headers.each do |col|
191
+ this_row << @table[col][row]
192
+ end
193
+ result << this_row
194
+ end
195
+ unless result_headers.empty?
196
+ return Table.new(result)
197
+ else
198
+ return nil
199
+ end
200
+ end
201
+
202
+ alias :get_columns :select
203
+
204
+ # Given a particular condition for a given column field/column, return a subtable
205
+ # that matches the condition. If no condition is given, a new +Table+ is returned with
206
+ # all records.
207
+ # Returns +nil+ if the condition is not met or the column is not found.
208
+ #
209
+ # +colname+:: +String+ to identify the column to tally
210
+ # +condition+:: OPTIONAL +String+ containing a ruby condition to evaluate
211
+ def where(colname, condition=nil)
212
+ # check arguments
213
+ return nil unless @table.has_key?(colname)
214
+
215
+ result = []
216
+ result << @headers
217
+ @table[colname].each_index do |index|
218
+ if condition
219
+ eval("'#{@table[colname][index]}' #{condition}") ? result << get_row(index) : nil
220
+ else
221
+ result << get_row(index)
222
+ end
223
+ end
224
+ result.length > 1 ? Table.new(result) : nil
225
+ end
226
+
227
+ alias :get_rows :where
228
+
229
+ # Given a second table to join against, and a field/column, return a +Table+ which
230
+ # contains a join of the two tables. Join only lists the common column once, under
231
+ # the column name of the first table (if different from the name of thee second).
232
+ # All columns from both tables are returned. Returns +nil+ if the column is not found.
233
+ #
234
+ # +table2+:: +Table+ to identify the secondary table in the join
235
+ # +colname+:: +String+ to identify the column to join on
236
+ # +col2name+:: OPTIONAL +String+ to identify the column in the second table to join on
237
+ def join(table2, colname, col2name=nil)
238
+ # check arguments
239
+ raise ArgumentError, "Invalid table!" unless table2.is_a?(Table)
240
+ return nil unless @table.has_key?(colname)
241
+ if col2name.nil? # Assume colname applies for both tables
242
+ col2name = colname
243
+ end
244
+ t2_col_index = table2.headers.index(col2name)
245
+ return nil unless t2_col_index # is not nil
246
+
247
+
248
+ # ensure no duplication of header values
249
+ table2.headers.each do |h|
250
+ if @headers.include?(h)
251
+ update_header(h, '_' << h )
252
+ if h == colname
253
+ colname = '_' << colname
254
+ end
255
+ end
256
+ end
257
+
258
+ result = [ Array(@headers) + Array(table2.headers) ]
259
+ @table[colname].each_index do |index|
260
+ t2_index = table2.column(col2name).find_index(@table[colname][index])
261
+ unless t2_index.nil?
262
+ result << self.row(index) + table2.row(t2_index)
263
+ end
264
+ end
265
+ if result.length == 1 #no rows selected
266
+ return nil
267
+ else
268
+ return Table.new(result)
269
+ end
270
+ end
271
+
272
+
273
+ # Given a field/column, and a regular expression to match against, and a replacement string,
274
+ # update the table such that it substitutes the column data with the replacement string.
275
+ # Returns +nil+ if the column is not found.
276
+ #
277
+ # +colname+:: +String+ to identify the column to join on
278
+ # +re+:: +Regexp+ to match the value in the selected column
279
+ # +replace+:: +String+ to specify the replacement text for the given +Regexp+
280
+ def sub(colname, re, replace)
281
+ # check arguments
282
+ raise ArgumentError, "No regular expression to match against" unless re
283
+ raise ArgumentError, "No replacement string specified" unless replace
284
+ return nil unless @table.has_key?(colname)
285
+
286
+ @table[colname].each do |item|
287
+ item.sub!(re, replace)
288
+ end
289
+ return self
290
+ end
291
+
292
+ # Return the union of columns from different tables, eliminating duplicates.
293
+ # Return nil if a column is not found.
294
+ #
295
+ # +table2+:: +Table+ to identify the secondary table in the union
296
+ # +colname+:: +String+ to identify the column to union
297
+ # +col2name+:: OPTIONAL +String+ to identify the column in the second table to union
298
+ def union(table2, colname, col2name=nil)
299
+ # check arguments
300
+ raise ArgumentError, "Invalid table!" unless table2.is_a?(Table)
301
+ return nil unless @table.has_key?(colname)
302
+ if col2name.nil? # Assume colname applies for both tables
303
+ col2name = colname
304
+ end
305
+ return nil unless table2.headers.include?(col2name)
306
+
307
+ return self.column(colname) | table2.column(col2name)
308
+ end
309
+
310
+ # Return the intersection of columns from different tables, eliminating duplicates.
311
+ # Return nil if a column is not found.
312
+ #
313
+ # +table2+:: +Table+ to identify the secondary table in the intersection
314
+ # +colname+:: +String+ to identify the column to intersection
315
+ # +col2name+:: OPTIONAL +String+ to identify the column in the second table to intersection
316
+ def intersect(table2, colname, col2name=nil)
317
+ # check arguments
318
+ raise ArgumentError, "Invalid table!" unless table2.is_a?(Table)
319
+ return nil unless @table.has_key?(colname)
320
+ if col2name.nil? # Assume colname applies for both tables
321
+ col2name = colname
322
+ end
323
+ return nil unless table2.headers.include?(col2name)
324
+
325
+ return self.column(colname) & table2.column(col2name)
326
+ end
327
+
328
+ alias :sub! :sub
329
+
330
+ # Write a representation of the +Table+ object to a file (tab delimited).
331
+ #
332
+ # +filename+:: +String+ to identify the name of the file to write
333
+ def write_file(filename)
334
+ file = File.open(filename, "w")
335
+ file.print self.to_s
336
+ end
337
+
338
+ private
339
+
340
+ def read_file(filename)
341
+ file = File.open(filename, "r")
342
+ @headers = file.gets.chomp.split("\t")
343
+ @headers.each {|col| @table.store(col, []) }
344
+ file.each_line do |line|
345
+ fields = line.chomp.split("\t")
346
+ if fields.length != @headers.length
347
+ $stderr.write "INVALID NUMBER OF FIELDS: #{fields.join(';')}\n"
348
+ else
349
+ @headers.each { |col| @table[col] << fields.shift }
350
+ end
351
+ nil
352
+ end
353
+ end
354
+
355
+ def add_rows(array_of_rows)
356
+ array_of_rows.each do |r|
357
+ row = r.clone
358
+ if @headers.empty?
359
+ @headers = row
360
+ else
361
+ unless row.length == @headers.length
362
+ raise ArgumentError, "Wrong number of fields in Table input"
363
+ end
364
+ @headers.each do |col|
365
+ @table[col] = [] unless @table[col]
366
+ @table[col] << row.shift
367
+ end
368
+ end
369
+ end
370
+ end
371
+
372
+ def get_row(index)
373
+ result = []
374
+ @headers.each do |col|
375
+ result << @table[col][index].to_s
376
+ end
377
+ return result
378
+ end
379
+
380
+ def get_col(colname)
381
+ result = []
382
+ @table[colname].each {|e| result << e }
383
+ end
384
+
385
+ def copy
386
+ result = []
387
+ result << @headers
388
+ @table[@headers.first].each_index do |index|
389
+ result << get_row(index)
390
+ end
391
+ result.length > 1 ? Table.new(result) : Table.new()
392
+ end
393
+
394
+ def update_header(item, new_item)
395
+ i = @headers.index(item)
396
+ @headers[i] = new_item unless i.nil?
397
+ @table.fetch(item,nil).nil? ? nil : @table[new_item] = @table[item]
398
+ end
399
+
400
+ def init(table)
401
+ @headers = table.headers.map {|x| x }
402
+ @headers.each do |key|
403
+ @table[key] = table.table[key].map {|x| x }
404
+ end
405
+ @indices = {}
406
+ end
407
+ end