groupBy.cpp 19.7 KB
Newer Older
Aaron's avatar
Aaron committed
1
/*****************************************************************************
2
  groupBy.cpp
Aaron's avatar
Aaron committed
3
4
5
6
7
8
9

  (c) 2009 - Aaron Quinlan
  Hall Laboratory
  Department of Biochemistry and Molecular Genetics
  University of Virginia
  aaronquinlan@gmail.com

Aaron's avatar
Aaron committed
10
  Licenced under the GNU General Public License 2.0 license.
Aaron's avatar
Aaron committed
11
12
******************************************************************************/
#include <vector>
13
#include <map>
Aaron's avatar
Aaron committed
14
15
16
17
18
#include <numeric>
#include <iterator>
#include <iostream>
#include <iomanip>
#include <fstream>
19
#include <sstream>
Aaron's avatar
Aaron committed
20
21
#include <stdlib.h>
#include <math.h>
22
23
#include <limits.h>
#include <string.h>
24
25
#include <exception>
#include <stdexcept> // out_of_range exception
Aaron's avatar
Aaron committed
26
27
28

#include "version.h"
#include "lineFileUtilities.h"
Aaron's avatar
Aaron committed
29
#include "tabFile.h"
Aaron's avatar
Aaron committed
30
31
32
using namespace std;


Aaron's avatar
Aaron committed
33
34
35
const int PRECISION = 21;


Aaron's avatar
Aaron committed
36
37
38
39
// define our program name
#define PROGRAM_NAME "groupBy"
// define our parameter checking macro
#define PARAMETER_CHECK(param, paramLen, actualLen) (strncmp(argv[i], param, min(actualLen, paramLen))== 0) && (actualLen == paramLen)
Aaron's avatar
Aaron committed
40
#define LOOKS_LIKE_A_PARAM(string) (strlen(string)>0 && string[0]=='-')
Aaron's avatar
Aaron committed
41
42


Aaron's avatar
Aaron committed
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
struct ValueGreaterThan
{
    bool operator()( const vector< pair<int, string> >::value_type& lhs,
                     const vector< pair<int, string> >::value_type& rhs ) const
    {
        return lhs.first > rhs.first;
    }
};

struct ValueLessThan
{
    bool operator()( const vector< pair<int, string> >::value_type& lhs,
                     const vector< pair<int, string> >::value_type& rhs ) const
    {
        return lhs.first < rhs.first;
    }
};

Aaron's avatar
Aaron committed
61
62
// function declarations
void ShowHelp(void);
63
64
65
void GroupBy(const string &inFile, const vector<int> &groupColumns, const vector<int> &opColumns, const vector<string> &ops);
void ReportSummary(const vector<string> &group, const vector<vector<string> > &data, const vector<string> &ops);
void addValue (const vector<string> &fromList, vector<string> &toList, int index, int lineNum);
Aaron's avatar
Aaron committed
66
float ToFloat (string element);
67
double ToDouble(const string &element);
68
69
void TabPrintPost (string element);
void TabPrintPre (string element);
Aaron's avatar
Aaron committed
70
void CommaPrint (string element);
71

Aaron's avatar
Aaron committed
72
73
int main(int argc, char* argv[]) {

Aaron's avatar
Aaron committed
74
75
76
    // input files
    string inFile             = "stdin";
    string groupColumnsString = "1,2,3";
77
78
    string opsColumnString;
    string opsString;
79

Aaron's avatar
Aaron committed
80
81
    // our configuration variables
    bool showHelp          = false;
82
83
84
    bool haveOpColumns     = false;
    bool haveOps           = true;

Aaron's avatar
Aaron committed
85
86
87
88
89
90
    // check to see if we should print out some help
    if(argc <= 1) showHelp = true;

    for(int i = 1; i < argc; i++) {
        int parameterLength = (int)strlen(argv[i]);

91
        if((PARAMETER_CHECK("-h", 2, parameterLength)) ||
Aaron's avatar
Aaron committed
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
        (PARAMETER_CHECK("--help", 5, parameterLength))) {
            showHelp = true;
        }
    }

    if(showHelp) ShowHelp();

    // do some parsing (all of these parameters require 2 strings)
    for(int i = 1; i < argc; i++) {

        int parameterLength = (int)strlen(argv[i]);

        if(PARAMETER_CHECK("-i", 2, parameterLength)) {
            if ((i+1) >= argc || LOOKS_LIKE_A_PARAM(argv[i+1])) {
                cerr << endl << "*****ERROR: -i parameter requires a value." << endl << endl;
                ShowHelp();
                break;
109
            }
Aaron's avatar
Aaron committed
110
            else {
111
112
                inFile     = argv[i + 1];
                i++;
Aaron's avatar
Aaron committed
113
114
            }
        }
115
        else if (PARAMETER_CHECK("-grp", 4, parameterLength) || PARAMETER_CHECK("-g", 2, parameterLength)) {
116
117
            if ((i+1) >= argc || LOOKS_LIKE_A_PARAM(argv[i+1])) {
                cerr << endl << "*****ERROR: -grp parameter requires a value." << endl << endl;
Aaron's avatar
Aaron committed
118
                ShowHelp();
119
120
121
122
123
124
                break;
            }
            else {
                groupColumnsString     = argv[i + 1];
                i++;
            }
Aaron's avatar
Aaron committed
125
        }
126
        else if(PARAMETER_CHECK("-opCols", 7, parameterLength) || PARAMETER_CHECK("-c", 2, parameterLength)) {
127
128
            if ((i+1) >= argc || LOOKS_LIKE_A_PARAM(argv[i+1])) {
                cerr << endl << "*****ERROR: -opCols parameter requires a value." << endl << endl;
Aaron's avatar
Aaron committed
129
                ShowHelp();
130
131
132
133
134
135
136
                break;
            }
            else {
                haveOpColumns       = true;
                opsColumnString     = argv[i + 1];
                i++;
            }
Aaron's avatar
Aaron committed
137
        }
138
        else if(PARAMETER_CHECK("-ops", 4, parameterLength) || PARAMETER_CHECK("-o", 2, parameterLength)) {
139
140
            if ((i+1) >= argc || LOOKS_LIKE_A_PARAM(argv[i+1])) {
                cerr << endl << "*****ERROR: -ops parameter requires a value." << endl << endl;
Aaron's avatar
Aaron committed
141
142
                ShowHelp();
                break;
143
144
145
146
147
148
            }
            else {
                haveOps    = true;
                opsString  = argv[i + 1];
                i++;
            }
Aaron's avatar
Aaron committed
149
150
151
152
        }
        else {
            cerr << endl << "*****ERROR: Unrecognized parameter: " << argv[i] << " *****" << endl << endl;
            showHelp = true;
153
        }
Aaron's avatar
Aaron committed
154
155
156
157
158
159
160
161
    }

    if (!haveOpColumns) {
        cerr << endl << "*****" << endl << "*****ERROR: Need -opCols." << endl << "*****" << endl;
        showHelp = true;
    }
    // split the opsString into discrete operations and make sure they are all valid.
    vector<string> ops;
162
163
164
165
    Tokenize(opsString, ops, ",");
    for( size_t i = 0; i < ops.size(); i++ ) {
        if ((ops[i] != "sum")  && (ops[i] != "max")    && (ops[i] != "min") && (ops[i] != "mean") &&
            (ops[i] != "mode") && (ops[i] != "median") && (ops[i] != "antimode") && (ops[i] != "stdev") &&
Aaron's avatar
Aaron committed
166
167
            (ops[i] != "sstdev") && (ops[i] != "count") && (ops[i] != "collapse") && (ops[i] != "freqdesc") &&
            (ops[i] != "freqasc")) {
168
169
            cerr << endl << "*****" << endl << "*****ERROR: Invalid operation selection \"" << ops[i] << endl << "*****" << endl;
            showHelp = true;
170
171
        }
    }
Aaron's avatar
Aaron committed
172
    if (!showHelp) {
173

Aaron's avatar
Aaron committed
174
175
176
177
        // Split the column string sent by the user into discrete column numbers
        // A comma separated string is expected.
        vector<int> groupColumnsInt;
        Tokenize(groupColumnsString, groupColumnsInt, ",");
178

Aaron's avatar
Aaron committed
179
        vector<int> opColumnsInt;
180
        Tokenize(opsColumnString, opColumnsInt, ",");
181
182

        // sanity check the group columns
183
184
185
        for(size_t i = 0; i < groupColumnsInt.size(); ++i) {
            int groupColumnInt = groupColumnsInt[i];
            if (groupColumnInt < 1) {
186
187
                cerr << endl << "*****" << endl << "*****ERROR: group columns must be >=1. " << endl << "*****" << endl;
                ShowHelp();
188
189
            }
        }
190
191

        // sanity check the op columns
192
193
194
        for(size_t i = 0; i < opColumnsInt.size(); ++i) {
            int opColumnInt = opColumnsInt[i];
            if (opColumnInt < 1) {
195
196
                cerr << endl << "*****" << endl << "*****ERROR: op columns must be >=1. " << endl << "*****" << endl;
                ShowHelp();
197
            }
198
        }
199

200
201
        // sanity check that there are equal number of opColumns and ops
        if (ops.size() != opColumnsInt.size()) {
202
203
            cerr << endl << "*****" << endl << "*****ERROR: There must be equal number of ops and opCols. " << endl << "*****" << endl;
            ShowHelp();
204
        }
Aaron's avatar
Aaron committed
205
        GroupBy(inFile, groupColumnsInt, opColumnsInt, ops);
206
    }
Aaron's avatar
Aaron committed
207
208
209
    else {
        ShowHelp();
    }
Aaron's avatar
Aaron committed
210
211
212
213
}

void ShowHelp(void) {

Aaron's avatar
Aaron committed
214
    cerr << endl << "Program: " << PROGRAM_NAME << " (v" << VERSION << ")" << endl;
215

Aaron's avatar
Aaron committed
216
217
218
219
    cerr << "Author:  Aaron Quinlan (aaronquinlan@gmail.com)" << endl;

    cerr << "Summary: Summarizes a dataset column based upon" << endl;
    cerr << "\t common column groupings. Akin to the SQL \"group by\" command." << endl << endl;
220

221
    cerr << "Usage:   " << PROGRAM_NAME << " -i <input> -g <group_column(s)> -c <op_column(s)> -o <ops> " << endl << endl;
Aaron's avatar
Aaron committed
222
223

    cerr << "Options: " << endl;
224
    cerr << "\t-i\t\t"        << "Input file. Use \"stdin\" for pipes." << endl << endl;
225

226
227
    cerr << "\t-g -grp\t\t"      << "Specify the columns (1-based) for the grouping." << endl;
    cerr                         << "\t\t\tThe columns must be comma separated." << endl;
228
    cerr                         << "\t\t\t- Default: 1,2,3" << endl << endl;
Aaron's avatar
Aaron committed
229

230
231
    cerr << "\t-c -opCols\t"   << "Specify the column (1-based) that should be summarized." << endl;
    cerr                         << "\t\t\t- Required." << endl << endl;
Aaron's avatar
Aaron committed
232

233
234
235
236
    cerr << "\t-o -ops\t\t"      << "Specify the operation that should be applied to opCol." << endl;
    cerr                         << "\t\t\tValid operations:" << endl;
    cerr                         << "\t\t\t    sum, count, min, max," << endl;
    cerr                         << "\t\t\t    mean, median, mode, antimode," << endl;
237
    cerr                         << "\t\t\t    stdev, sstdev (sample standard dev.)," << endl;
238
    cerr                         << "\t\t\t    collapse (i.e., print a comma separated list), " << endl;
Aaron's avatar
Aaron committed
239
240
    cerr                         << "\t\t\t    freqdesc (i.e., print desc. list of values:freq)" << endl;
    cerr                         << "\t\t\t    freqasc (i.e., print asc. list of values:freq)" << endl;
241
    cerr                         << "\t\t\t- Default: sum" << endl << endl;
Aaron's avatar
Aaron committed
242

Aaron's avatar
Aaron committed
243
244
245
246
    cerr << "Examples: " << endl;
    cerr << "\t$ cat ex1.out" << endl;
    cerr << "\tchr1 10  20  A   chr1    15  25  B.1 1000" << endl;
    cerr << "\tchr1 10  20  A   chr1    25  35  B.2 10000" << endl << endl;
247
    cerr << "\t$ groupBy -i ex1.out -g 1,2,3,4 -c 9 -o sum" << endl;
Aaron's avatar
Aaron committed
248
249
250
    cerr << "\tchr1 10  20  A   11000" << endl << endl;
    cerr << "\t$ groupBy -i ex1.out -grp 1,2,3,4 -opCols 9,9 -ops sum,max" << endl;
    cerr << "\tchr1 10  20  A   11000   10000" << endl << endl;
251
    cerr << "\t$ groupBy -i ex1.out -g 1,2,3,4 -c 8,9 -o collapse,mean" << endl;
Aaron's avatar
Aaron committed
252
    cerr << "\tchr1 10  20  A   B.1,B.2,    5500" << endl << endl;
253

Aaron's avatar
Aaron committed
254
255
    cerr << "Notes: " << endl;
    cerr << "\t(1)  The input file/stream should be sorted/grouped by the -grp. columns" << endl << endl;
256
    cerr << "\t(2)  If -i is unspecified, input is assumed to come from stdin." << endl << endl;
Aaron's avatar
Aaron committed
257

258

Aaron's avatar
Aaron committed
259
260
    // end the program here
    exit(1);
Aaron's avatar
Aaron committed
261
262
263
264

}


265
266
267
void GroupBy (const string &inFile,
            const vector<int> &groupColumns,
            const vector<int> &opColumns,
Aaron's avatar
Aaron committed
268
            const vector<string> &ops) {
269

Aaron's avatar
Aaron committed
270
271
272
273
    // current line number
    int lineNum = 0;
    // string representing current line
    string inLine;
274

Aaron's avatar
Aaron committed
275
276
    // vector of strings holding the tokenized current line
    vector<string>  inFields;
Aaron's avatar
Aaron committed
277
    inFields.reserve(20);
278

Aaron's avatar
Aaron committed
279
    // keys for the current and previous group
Aaron's avatar
Aaron committed
280
281
    vector<string>  prevGroup(0);
    vector<string>  currGroup(0);
282

283
284
285
286
287
    // vector (one per column) of vector (one per value/column) of the opColumn values for the current group
    vector< vector<string> >  values;
    for( size_t i = 0; i < opColumns.size(); i++ ) {
        values.push_back( vector<string>() );
    }
288

Aaron's avatar
Aaron committed
289
290
    // check the status of the current line
    TabLineStatus tabLineStatus;
291

Aaron's avatar
Aaron committed
292
293
294
    // open a new tab file, loop through it line by line
    // and summarize the data for a given group when the group
    // fields change
295
    TabFile *_tab = new TabFile(inFile);
Aaron's avatar
Aaron committed
296
    _tab->Open();
Aaron's avatar
Aaron committed
297
298
299
300
    while ((tabLineStatus = _tab->GetNextTabLine(inFields, lineNum)) != TAB_INVALID) {
        if (tabLineStatus == TAB_VALID) {
            // build the group vector for the current line
            currGroup.clear();
Aaron's avatar
Aaron committed
301
302
            vector<int>::const_iterator gIt  = groupColumns.begin();
            vector<int>::const_iterator gEnd = groupColumns.end();
303
304
            for (; gIt != gEnd; ++gIt)
                addValue(inFields, currGroup, (*gIt-1), lineNum);
Aaron's avatar
Aaron committed
305

306
            // there has been a group change
Aaron's avatar
Aaron committed
307
308
            if ((currGroup != prevGroup) && (prevGroup.size() > 0)) {
                // Summarize this group
309
310
                ReportSummary(prevGroup, values, ops);
                // reset and add the first value for the next group.
Aaron's avatar
Aaron committed
311
                values.clear();
312
313
                for( size_t i = 0; i < opColumns.size(); i++ ) {
                    values.push_back( vector<string>() );
Aaron's avatar
Aaron committed
314
                    addValue(inFields, values[i], opColumns[i]-1, lineNum);
315
                }
Aaron's avatar
Aaron committed
316
            }
317
            // we're still dealing with the same group
318
319
            else {
                for( size_t i = 0; i < opColumns.size(); i++ )
Aaron's avatar
Aaron committed
320
321
                    addValue(inFields, values[i], opColumns[i]-1, lineNum);
            }
Aaron's avatar
Aaron committed
322
323
            // reset for the next line
            prevGroup = currGroup;
Aaron's avatar
Aaron committed
324
325
        }
        inFields.clear();
Aaron's avatar
Aaron committed
326
327
    }
    // report the last group
328
    ReportSummary(currGroup, values, ops);
Aaron's avatar
Aaron committed
329
    _tab->Close();
Aaron's avatar
Aaron committed
330
331
332
}


333
void ReportSummary(const vector<string> &group, const vector<vector<string> > &data, const vector<string> &ops) {
334

335
336
    vector<string> result;
    for( size_t i = 0; i < data.size(); i++ ) {
337

338
339
340
341
342
        string op = ops[i];
        std::stringstream buffer;
        vector<double> dataF;
        // are we doing a numeric conversion?  if so, convert the strings to doubles.
        if ((op == "sum") || (op == "max") || (op == "min") || (op == "mean") ||
343
            (op == "median") || (op == "stdev") || (op == "sstdev"))
Aaron's avatar
Aaron committed
344
        {
345
346
            transform(data[i].begin(), data[i].end(), back_inserter(dataF), ToDouble);
        }
Aaron's avatar
Aaron committed
347
348

        if (op == "sum") {
349
350
            // sum them up
            double total = accumulate(dataF.begin(), dataF.end(), 0.0);
Aaron's avatar
Aaron committed
351
            buffer << setprecision (PRECISION) << total;
352
            result.push_back(buffer.str());
Aaron's avatar
Aaron committed
353
        }
Aaron's avatar
Aaron committed
354
        else if (op == "collapse") {
355
356
357
358
359
360
361
362
            string collapse;
            for( size_t j = 0; j < data[i].size(); j++ ) {//Ugly, but cannot use back_inserter
                collapse.append(data[i][j]);
                collapse.append(",");
            }
            result.push_back(collapse);
        }
        else if (op == "min") {
Aaron's avatar
Aaron committed
363
            buffer << setprecision (PRECISION) << *min_element( dataF.begin(), dataF.end() );
364
365
366
            result.push_back(buffer.str());
        }
        else if (op == "max") {
Aaron's avatar
Aaron committed
367
            buffer << setprecision (PRECISION) << *max_element( dataF.begin(), dataF.end() );
368
369
370
371
372
            result.push_back(buffer.str());
        }
        else if (op == "mean") {
            double total = accumulate(dataF.begin(), dataF.end(), 0.0);
            double mean = total / dataF.size();
Aaron's avatar
Aaron committed
373
            buffer << setprecision (PRECISION) << mean;
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
            result.push_back(buffer.str());
        }
        else if (op == "median") {
            double median = 0.0;
            sort(dataF.begin(), dataF.end());
            int totalLines = dataF.size();
            if ((totalLines % 2) > 0) {
                long mid;
                mid = totalLines / 2;
                median = dataF[mid];
            }
            else {
                long midLow, midHigh;
                midLow = (totalLines / 2) - 1;
                midHigh = (totalLines / 2);
                median = (dataF[midLow] + dataF[midHigh]) / 2.0;
            }
Aaron's avatar
Aaron committed
391
            buffer << setprecision (PRECISION) << median;
392
393
394
            result.push_back(buffer.str());
        }
        else if (op == "count") {
Aaron's avatar
Aaron committed
395
            buffer << setprecision (PRECISION) << data[i].size();
396
397
            result.push_back(buffer.str());
        }
398
        else if ((op == "mode") || (op == "antimode") ||
Aaron's avatar
Aaron committed
399
                 (op == "freqdesc") || (op == "freqasc")) {
400
401
402
403
404
405
406
            // compute the frequency of each unique value
            map<string, int> freqs;
            vector<string>::const_iterator dIt  = data[i].begin();
            vector<string>::const_iterator dEnd = data[i].end();
            for (; dIt != dEnd; ++dIt) {
                freqs[*dIt]++;
            }
407

408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
            // grab the mode and the anti mode
            string mode, antiMode;
            int    count = 0;
            int minCount = INT_MAX;
            for(map<string,int>::const_iterator iter = freqs.begin(); iter != freqs.end(); ++iter) {
                if (iter->second > count) {
                    mode = iter->first;
                    count = iter->second;
                }
                if (iter->second < minCount) {
                    antiMode = iter->first;
                    minCount = iter->second;
                }
            }
            // report
            if (op == "mode") {
Aaron's avatar
Aaron committed
424
                buffer << setprecision (PRECISION) << mode;
425
426
427
                result.push_back(buffer.str());
            }
            else if (op == "antimode") {
Aaron's avatar
Aaron committed
428
                buffer << setprecision (PRECISION) << antiMode;
429
                result.push_back(buffer.str());
Aaron's avatar
Aaron committed
430
431
            }
            else if (op == "freqdesc" || op == "freqasc") {
432
                // pair for the num times a values was
Aaron's avatar
Aaron committed
433
434
435
                // observed (1) and the value itself (2)
                pair<int, string> freqPair;
                vector< pair<int, string> > freqList;
436

Aaron's avatar
Aaron committed
437
438
439
                // create a list of pairs of all the observed values (second)
                // and their occurences (first)
                map<string,int>::const_iterator mapIter = freqs.begin();
440
                map<string,int>::const_iterator mapEnd  = freqs.end();
Aaron's avatar
Aaron committed
441
442
                for(; mapIter != mapEnd; ++mapIter)
                    freqList.push_back( make_pair(mapIter->second, mapIter->first) );
443

Aaron's avatar
Aaron committed
444
445
446
447
448
449
                // sort the list of pairs in the requested order by the frequency
                // this will make the value that was observed least/most bubble to the top
                if (op == "freqdesc")
                    sort(freqList.begin(), freqList.end(), ValueGreaterThan());
                else if (op == "freqasc")
                    sort(freqList.begin(), freqList.end(), ValueLessThan());
450

Aaron's avatar
Aaron committed
451
452
453
454
455
456
457
                // record all of the values and their frequencies.
                vector< pair<int, string> >::const_iterator iter    = freqList.begin();
                vector< pair<int, string> >::const_iterator iterEnd = freqList.end();
                for (; iter != iterEnd; ++iter)
                    buffer << iter->second << ":" << iter->first << ",";
                result.push_back(buffer.str());
            }
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
        }
        else if (op == "stdev" || op == "sstdev") {
            // get the mean
            double total = accumulate(dataF.begin(), dataF.end(), 0.0);
            double mean = total / dataF.size();
            // get the variance
            double totalVariance = 0.0;
            vector<double>::const_iterator dIt  = dataF.begin();
            vector<double>::const_iterator dEnd = dataF.end();
            for (; dIt != dEnd; ++dIt) {
                totalVariance += pow((*dIt - mean),2);
            }
            double variance = 0.0;
            if (op == "stdev") {
                variance = totalVariance / dataF.size();
            }
            else if (op == "sstdev" && dataF.size() > 1) {
                variance = totalVariance / (dataF.size() - 1);
            }
            double stddev = sqrt(variance);
            // report
Aaron's avatar
Aaron committed
479
            buffer << setprecision (PRECISION) << stddev;
480
            result.push_back(buffer.str());
Aaron's avatar
Aaron committed
481
        }
482
483
484
485
486
487
488
489
490
491
492
493
494
    }
    for_each(group.begin(), group.end(), TabPrintPost);
    cout << *result.begin();
    for_each(++result.begin(), result.end(), TabPrintPre);
    cout << endl; //Gets rid of extraneous tab
}


void addValue (const vector<string> &fromList, vector<string> &toList, int index, int lineNum) {
    try {
        toList.push_back(fromList.at(index));
    }
    catch(std::out_of_range& e) {
495
496
        cerr << endl << "*****" << endl << "*****ERROR: requested column exceeds the number of columns in file at line "
             << lineNum << ". Exiting." << endl << "*****" << endl;
Aaron's avatar
Aaron committed
497
        exit(1);
Aaron's avatar
Aaron committed
498
499
500
501
502
503
504
505
    }
}


float ToFloat (string element) {
    return atof(element.c_str());
}

506
void TabPrintPost (string element) {
Aaron's avatar
Aaron committed
507
508
    cout << element << "\t";
}
Aaron's avatar
Aaron committed
509

510
511
512
513
void TabPrintPre (string element) {
    cout << "\t" << element;
}

Aaron's avatar
Aaron committed
514
515
516
void CommaPrint (string element) {
    cout << element << ",";
}
517
518
519
520
521
522
523
524
525
526

double ToDouble(const string &element) {
    std::istringstream i(element);
    double x;
    if (!(i >> x)) {
        cerr << "Error: Could not properly convert string to numeric (\"" + element + "\")" << endl;
        exit(1);
    }
    return x;
}