3
\$\begingroup\$

I've been working through the fifth edition of Lajoie's C++ Primer and decided to write a CSV reader/writer as my first project. Its main features are:

  • Able to explicitly call for:
    • Individual/multiple rows by index and range
    • Individual/multiple columns by name and index
  • Able to write new rows using a map<string,string> formatted <column, value>.

The code is available on Github and pasted below. Call me out for poor practices, bad design choices, whatever itches you. It's my first dive into C++ and I want to know how to improve.

main.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include "CSVFile.hpp"

int main(int argc, const char * argv[]) {
    string file_name = "Sample.csv";
    CSVFile csv_obj(file_name);

    typedef vector<vector<string>> CsvVector;

    vector<string> headers = csv_obj.get_header(); // Get available headers in passed CSV

    CsvVector column_one = csv_obj.get_column("Column 1");
    vector<string> row_1 = csv_obj.get_row(1); // Get rows by index
    CsvVector row_1_to_3 = csv_obj.get_row(0, 3); // Get rows by range

    map<string,string> row_map; // Map formatted Column: Column value
    row_map["Column 1"] = "ColumnOneVal";
    row_map["Column 2"] = "ColumnTwoVal";
    row_map["Column 3"] = "ColumnThreeVal";
    cout << endl;
    csv_obj.write_row(row_map); // Use row_map to write a new row

    return 0;
}

CSVFile.hpp

#ifndef CSVFile_hpp
#define CSVFile_hpp

#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <map>

using namespace std;

;class CSVFile {

typedef vector<vector<string>> CsvVector;
friend ostream& print(ostream&, const CSVFile&);

public:
    CSVFile() = default;
    CSVFile(const string&);

    inline vector<string>::size_type get_size();
    inline void headers();
    inline vector<string> get_header();

    CsvVector get_column(const int&);
    CsvVector get_column(const vector<int>&);
    CsvVector get_column(const string&);
    CsvVector get_column(const vector<string>&);

    vector<string> get_row(const int&);
    CsvVector get_row(const int&, const int&); // Rows in range

    // Planned: Explicitly declare line to write to.
    // Pass vector of strings to write lines for
    void write_row(const map<string,string>&);
    inline CsvVector rows();

private:
    vector<string> header;
    CsvVector contents;
    ifstream stream_read;
    ofstream stream_write;
    inline string remove_extension(const string&);
    vector<string> column_info(const string&);
    string merge_row_vector(const vector<string>&);
    int index_from_string(const string&);
    map<int,string> index_from_string(const map<string,string>& row_map);
};

ostream& print(ostream&, const CSVFile&);

void CSVFile::headers() {
    for (auto&& i : header)
        cout << i << ", ";
}

vector<string>::size_type CSVFile::get_size() {
    return contents.size();
}

string CSVFile::remove_extension(const string& s) {
    string ret = s;
    ret.erase(ret.find_last_of("."), string::npos);
    return ret;
}

vector<string> CSVFile::get_header() {
    return header;
}


CSVFile::CsvVector CSVFile::rows() {
    return contents;
}
#endif /* CSVFile_hpp */

CSVFile.cpp

#include "CSVFile.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <array>

// file doc: http://www.cplusplus.com/doc/tutorial/files/
// stream_readtream constructor: http://www.cplusplus.com/forum/general/17771/#msg89650
CSVFile::CSVFile(const string& file_name) {
    stream_read.open(file_name, ios::binary);
    string out_file = remove_extension(file_name) + "_OUT.csv";
    stream_write.open(out_file, ios::binary);
    stream_write << stream_read.rdbuf(); // Copy read file into output file
    bool line_is_header = true; // First line assumed to be the header
    stream_read.seekg(0); // "Rewind" infile stream
    while (stream_read) {
        // Grab whole row
        string s;
        if (!getline(stream_read, s))
            break;

        istringstream ss (s);
        vector<string> record;

        while(ss) {
            // Split string by comma delimiter
            string s;
            if (!getline(ss, s, ','))
                break;
            record.push_back(s);
        }

        if (line_is_header)
            header = record;
        else
            contents.push_back(record);
        line_is_header = false; // First iteration clears through the header
    }
}

void CSVFile::write_row(const map<string,string>& row_map) {
    vector<string> row_vec(header.size(), "NULL");
    map<int,string> index_map = index_from_string(row_map);
    for(auto it = index_map.begin(); it != index_map.end(); ++it) {
        row_vec[it->first] = it->second;
    }
    // for(auto&& i : row_vec) { cout << i << endl; }

    string v_string = merge_row_vector(row_vec);
    stream_write << endl << v_string;
}

map<int,string> CSVFile::index_from_string(const map<string,string>& row_map) {
    map<int,string> index_map;
    for(auto it = row_map.begin(); it != row_map.end(); ++it) {
        index_map[index_from_string(it->first)] = it->second;
    }
    return index_map;
}

int CSVFile::index_from_string(const string& s) {
    // Given column string, returns index of column
    int pos = find(header.begin(), header.end(), s) - header.begin();
    if (pos == header.size()) {
        throw out_of_range("\nColumn: " + s + " not found in header");
    }
    return pos;
}

// http://stackoverflow.com/questions/1430757/c-vector-to-string
string CSVFile::merge_row_vector(const vector<string>& row_vec) {
    stringstream sstream;
    for(vector<string>::size_type ctr = 0; ctr < row_vec.size(); ++ctr) {
        string current = row_vec[ctr];
        // cout << "called : " << current << endl;
        if (ctr == 0)
            sstream << current << ",";
        else if (current == "NULL")
            sstream << ",";
        else if (ctr == row_vec.size() - 1)
            sstream << current;
        else
            sstream << current << ",";
    }
    return sstream.str();
}

vector<string> CSVFile::column_info(const string& s) {
    // Returns all data in the passed column(s)
    // Need to overload this function later
    vector<string> data;
    int index = index_from_string(s);

    for (auto&& i : contents) {
        data.push_back(i[index]);
    }

    return data;
}

CSVFile::CsvVector CSVFile::get_column(const int& index) {
    CSVFile::CsvVector column_data;
    vector<string> buffer;

    for(unsigned i = 0; i < contents.size(); ++i) {
        buffer.push_back(contents[i][index]);
    }
    column_data.push_back(buffer);
    return column_data;
}

CSVFile::CsvVector CSVFile::get_column(const vector<int>& indices) {
    CSVFile::CsvVector column_data;
    for(auto&& index : indices)
    {
        vector<string> buffer;
        for(unsigned i = 0; i < contents.size(); ++i)
        {
            buffer.push_back(contents[i][index]);
        }
        column_data.push_back(buffer);
    }
    return column_data;
}

CSVFile::CsvVector CSVFile::get_column(const string& s) {
    CSVFile::CsvVector column_data;
    column_data.push_back(column_info(s));
    return column_data;
}

CSVFile::CsvVector CSVFile::get_column(const vector<string>& col_vec) {
    CSVFile::CsvVector column_data;
    for (auto&& column : col_vec) {
        column_data.push_back(column_info(column));
    }
    return column_data;
}

vector<string> CSVFile::get_row(const int& i) {
    return contents[i];
}

CSVFile::CsvVector CSVFile::get_row(const int& start, const int& end) {
    CsvVector row_range;
    for(unsigned i = start; i != end; ++i) {
        row_range.push_back(contents[i]);
    }
    return row_range;
}

ostream& print(ostream& os, const CSVFile& csv) {
    string csv_string;
    for (auto&& i : csv.contents) {
        stringstream s;
        for (auto&& j : i) {
            s << j << ' ';
        }
        s << "\n";
        csv_string += s.str();
    }
    os << csv_string;
    return os;
}
\$\endgroup\$
1
\$\begingroup\$

Just briefly:

  • using namespace std is considered as bad practice and in header file it's even worst. Now, if you remove it, it'll broke "unrelated" code in main.cpp.
  • No support for another separators?
  • No support for something,"something,else",....?
  • Why are some methods implemented inside of header file? It's not a template class, so it can be all moved into .cpp file.
  • No Makefile in github repository? I have to compile it by hand? (ok, not so hard but ..)
  • Example main.cpp - there is nothing about what it's supposed to do.
  • Output csv is not in good format. It inserted new empty line and then example data without line separator.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.