1

I'm trying to create a code that will parse through a csv database with stock information. Currently, I have the code generated so that it will search with a keyword and print the whole row, but I'm trying to get it so that it will print the whole row with the header row in a neatly formatted way.

I'm trying to get it so that if I searched Google, it'd return

Symbol GOOG
NAME Google Inc
High Today $568.77

How the csv looks like:

Symbol,Name,Price,High Today,Low Today,52 Week Low
GOOG,Google Inc.,$568.77 ,$570.25 ,$560.35
AAPL,Apple Inc.,$93.28 ,$63.89 ,$99.44.

Code:

string NameSearch::getInput()
{
    cout << "Enter the name of the company you would like to search for: ";
    getline(cin, input);
    return input;

}
void NameSearch::NameAlgorithm()
{
    string line;
    ifstream fs("Stock Database.csv");

    while (!fs.eof())
    {
        getline(fs, line);
        string companyname = "";    
        string a;
        int column = 1;             
        int commacount = 0;         
        int ChrCount = 0;               

        while (line != "\0")        
        {
            a = line[ChrCount];       
            ChrCount++;

            if (a == ",")
            {
                commacount++; 
            }

            else if (commacount == column) 
            {
                companyname.append(a);
            }

            else if (commacount > column) 
            {
                break;
            }

            if (companyname == input)
            {
                cout << endl << line;
            }
        }
    }
}
David G
  • 94,763
  • 41
  • 167
  • 253
user3867859
  • 63
  • 1
  • 7

2 Answers2

7

First a comma should be parsed as whitespace. You can do this by changing the internal std::ctype<charT> facet in the stream's locale:

struct csv_classification : std::ctype<char> {
    csv_classification() : ctype(make_table()) { }
private:
    static mask* make_table() {
        const mask* classic = classic_table();
        static std::vector<mask> v(classic, classic + table_size);
        v[','] |= space;
        v[' '] &= ~space;
        return &v[0];
    }
};

Then set the locale using:

ifs.imbue(std::locale(ifs.getloc(), new csv_classification));

Next make a manipulator that checks to see if you're at the end of the line. If you are it sets the std::ios_base::failbit flag in the stream state. Also use internal storage to tell if the record belongs as a key or value in the map. Borrowing a bit from Dietmar...

static int row_end = std::ios_base::xalloc();

std::istream& record(std::istream& is) {
    while (std::isspace(is.peek())) {
        int c(is.peek());
        is.ignore();

        if (c == '\n') {
            is.iword(row_end) = !is.iword(row_end);
            is.setstate(std::ios_base::failbit);
        }
    }
    return is;
}

Then you can do:

std::vector<std::string> keys, values;

for (std::string item;;) {
    if (ifs >> record >> item)
        keys.push_back(item);
    else if (ifs.eof())
        break;
    else if (ifs.iword(row_end)) {
        ifs.clear();
        while (ifs >> record >> item)
            values.push_back(item);
    }
    else
        break;
}

Now we need to apply both the keys and values and print them out. We can create a new algorithm for that:

template<class Iter1, class Iter2, class Function>
void for_each_binary_range(Iter1 first1, Iter1 last1,
                           Iter2 first2, Iter2 last2, Function f)
{
    assert(std::distance(first1, last1) <= std::distance(first2, last2));

    while (first1 != last1) {
        f(*first1++, *first2++);
    }
}

Finally we do:

for_each_binary_range(std::begin(keys),   std::end(keys),
                      std::begin(values), std::end(values),
[&] (std::string const& key, std::string const& value)
{
    std::cout << key << ": " << value << std::endl;
}

Live Demo

David G
  • 94,763
  • 41
  • 167
  • 253
  • This is an interesting answer but I don't get it why U return address of an scope variable from the function, U are returning address of internal buffer of the local vector variable(`v`) from function `make_table` – BigBoss Aug 10 '14 at 04:59
  • @BigBoss The vector and its contents have static storage duration so they exist for the duration of the program. – David G Aug 10 '14 at 05:19
  • Sorry what I see is your function is `static` and your vector is local, what make your vector static? – BigBoss Aug 10 '14 at 05:22
  • Maybe you want: `static std::vector v(classic, classic + table_size)` – BigBoss Aug 10 '14 at 05:23
  • Sorry but this edit also have one problem, how you initialize an static vector from an scoped variable and why rest of initialization will be called multiple times for this. I think we should have something like this: `static std::vector v(create_my_table()); return &v[0];` and `create_my_table` return a vector not a pointer or like of that. – BigBoss Aug 10 '14 at 05:26
  • @BigBoss The initialization of the vector is fine. `classic` is a pointer to data provided by the derived class `std::ctype`. The actual data is not local to the scope of the function. Besides, the range constructor of the vector *copies* the data that the range provides, so it is safe. – David G Aug 10 '14 at 05:35
  • I know it is safe in this case, your first code most possibly worked with all major compilers but since u r coding with standard, U must consider that you want to initialize vector just once, but this code change those specific masks each time that it is called and beside that it is not wise to initialize an static variable(its initialization only called once) with a auto storage variable(that will be initialized every time that function called). but beside that I have +1 for U. I liked your answer :) – BigBoss Aug 10 '14 at 06:28
  • I wish I knew what you guys were talking about. Unfortunately, this is my first c++ class. – user3867859 Aug 10 '14 at 07:01
  • @user3867859 For a simpler implementation. Go through the characters one by one line in your original code. Accumulate characters until you reach a comma. When you do, add the string to an array and clear the string. When you come to a newline use a different array to store the values. At the end, print them in the correct format. – David G Aug 10 '14 at 07:43
  • comparison with '\n' is good fo *nix systems. For the sake of universality what about std::endl? – Tanuki Aug 11 '14 at 09:52
  • @Tanuki `std::endl` is just a function and all it does it write `'\n'` to the stream and flush it. It's not a "universal new line outputter". – David G Aug 11 '14 at 13:51
  • @0x499602D2 nice answer - certainly shorter and easier than all those boost::spirit::qi parsers. The only problem I have trying to adapt this is handling blank entries in the csv value lines, i.e. instead of a csv line as follows (AAPL,Apple Inc.,$93.28 ,$63.89 ,$99.44) use the following AAPL,Apple Inc.,$93.28 ,,$99.44) or leave the last entry blank per (AAPL,Apple Inc.,$93.28 ,$63.89 ,,) – johnco3 Dec 01 '15 at 19:04
0

Here it is your solution (the most close to what you requested):

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

using namespace std;

typedef std::vector<std::string> record;

std::istream&
operator>>(
  std::istream& is,
  record&       r)
{
  r.clear();

  string line;
  getline(is, line);

  istringstream iss(line);
  string field;

  while (getline(iss, field, ',' ))
    r.push_back(field);

  return is;
}

int
main()
{

  ifstream file("Stock Database.csv");
  record headers, r;

  if (file.good())
    file >> headers;

  while (file >> r)
  {
    for (int i = 0; i < r.size(); i++)
      cout << headers[i] <<  ":\t" << r[i] << endl;

    cout << "------------------------------" << endl;
  }

  return 0;
}

// EOF

The content of the "Stock Database.csv" file is:

Symbol,Name,Price,High Today,Low Today,52 Week Low
GOOG,Google Inc.,$568.77 ,$570.25 ,$560.35
AAPL,Apple Inc.,$93.28 ,$63.89 ,$99.44

Just do with the record whatever you want. The first read from the file suppose to bring you headers. Every next read fill each record with csv values.

Tanuki
  • 449
  • 2
  • 8
  • All this does is print the stream. He wants it to be formatted like he showed in his question. – David G Aug 10 '14 at 03:19
  • 1 sec. I'll change the sample. – Tanuki Aug 10 '14 at 03:20
  • @Tanuki Sorry if this is obvious, I'm a beginner in c++, but does the above code take as input what's is searched or is in manually inputting the whole line? Because the csv above is just a small snippet of a larger database with more columns and rows. – user3867859 Aug 10 '14 at 03:56
  • @Tanuki Would I just replace this line here istringstream input("GOOG,Google Inc.,$568.77 ,$570.25 ,$560.35"); to input(line) – user3867859 Aug 10 '14 at 03:57
  • @Tanuki Thank you - I'll take a deeper look. – user3867859 Aug 10 '14 at 04:02
  • I used C++11. If it does not compile - pass -std=c++11 flag to your compiler. – Tanuki Aug 10 '14 at 04:03
  • @user3867859, last version exactly prints what you requested. – Tanuki Aug 10 '14 at 04:17
  • @Tanuki Thank you so much! I'm still trying to follow your code, but I did run it through. How would I go about searching through the .csv to print out a specific row (for example in my above code, I search for Google so that in only displays information on that row). Eventually I'm trying to create multiple search algorithms and would only like to display that information, but formatted the way you have done above. – user3867859 Aug 10 '14 at 04:40
  • It is not a problem if you are sure that the structure of the CSV file will not change. If so - create enum for CSV fields and print as you want to. – Tanuki Aug 10 '14 at 04:44