tablestakes 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
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