passing object to function, columns class

I am working on a small columns class, since I use allot of tabular data. I am trying to set up code to allow me to efficiently read in tabular data, manipulate it, and write to output files. I more or less know what I need to do, but there are many options to sort through.

I have the beginnings of a columns class, without access functions and such. This also includes a vector of class that will hold one object for each col.

class column_data {

public:

   // initialize class members
   column_data()
      : scaleMin(0.0),
        scaleMax(0.0),
        content(""),
        type(""),
        scale_type("") ,
        header("")  { }

   float scaleMin, scaleMax;
   std::string content, type, scale_type, header;
   std::vector<std::string> inputDataString;
   std::vector<char> inputDataChar;
   std::vector<int> inputDataInt;
   std::vector<float> inputDataFloat;

};

// create vector of column_data objects
std::vector<column_data> columns;

There is some meta data here (like the content field), that will probably end up in a different class. I have vectors for the different data types that could be in the data. Only one of these would get populated, depending on the value of "type".

I am reading all of the data and storing each row in a vector of string,

   // create an input stream and open the input file
   ifstream splitsInput;
   splitsInput.open( splitsInFile.c_str() );

   // read in each line
   while(getline(splitsInput,newSplitsLine)) {
      // remove '\r' EOL chars
      newSplitsLine.erase (remove(newSplitsLine.begin(),newSplitsLine.end(),'\r') , newSplitsLine.end());
      // store row data in vector of string
      storeSplitsRows.push_back(newSplitsLine);
   }

Then I can create an object for each col by parsing the first row,

   colsRowStream << storeSplitsRows[0];
   int i = 0;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      // create new column data object
      column_data newCol;
      // add to vector
      columns.push_back(newCol);
      i++;
   }
   // clear the buffer
   colsRowStream.clear();

Now I have an object for each col and can begin processing the other rows.

I would like to make processing a function where I would add a row to the string stream, select a target variable in the class for the parsed data to go, and pass the stringstream and the target to a loading function.

// accepts a string stream and parses the data in to columns
int row_toCols(stringstream colsRowStream){
   int i = 0;
   std::string rowCell

   // parse row into cells by tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      columns.content = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}

// load first row into string stream
colsRowStream << storeSplitsRows[0];
row_toCols( stringstream colsRowStream);

The code above results in the data in the first row being parsed by tab and deposited in the content string var of each object. What I need to do is to be able to pass the target in the call so I can re-use this function to populate the rest of the data structure. I guess I would set the function to look for a pointer to an object, and then pass the object using the & operator, but there are always allot of ways of doing this kind of thing, so I'm not sure. I also need the code to handle different data types other than string. I could have different loading functions for different data types if that is necessary.

If I am going about this whole thing in the wrong way, now would be a good time to say so. :slight_smile: The loading function will probably be a class method at some point.

LMHmedchem

Keeping them as vectors but not using 2/3 of the vector positions is very wasteful. Why have all those different types anyway, instead of one class that contains a string and has operators to return float, int, char? It'd let you keep everything in one vector.

Why do you store each and every row in a vector when you don't need them later? Why not process the row then discard it?

I don't understand this at all, but I think I sort of get your pseudocode -- you want to load data from a string.

Well, you already have a function that loads strings, though it opens files and reads lines to get the strings. You could make a more general-purpose string loading member function that's used by your file loader and external things alike.

If you really wanted to process stringstreams, you could do that with your existing file-loading code, since stringstreams act just like file streams. You just need to make everything more general: A function that loads from a stream, which your file-opening member could use too, instead of just one member which loads from a filename and nothing else.

I have changed this into 2 classes. In some files I have, the first several rows are like extended header rows that give information about the data type and exactly what is stored in the column. I am now creating a metadata object for each col to hold the info from these first rows, and then a second object to hold the actual data. I put the different vector types into the class because the columns could contain any of those data types. For each column, only one vector will have data added to it. I presumed that the declaration of the vectors doesn't cost any significant resource since they have no size at that point.

This is probably temporary, in that I tend to start with a method that I know will work. In some cases, I need to look at allot of the data before I decide what to do with it, so I may not be able to process it one row at a time.

I need to load tabular data from a text file. Reading the data into a string for each row, and then parsing the row by the delimiter is the only way I know how to do that at the moment.

Here is an updated version of the code.
Header file,

// class columns in columns .h

class col_metadata {
public:
   // initialize class members
   col_metadata()
      : scaleMin(0.0),
        scaleMax(0.0),
        content(""),
        type(""),
        scale_type("") { }

   float scaleMin, scaleMax;
   std::string content, type, scale_type, header;
};

class column_data {
public:
   // initialize class members
   column_data()
      : header("") { }

   std::string header;
   std::vector<std::string> inputDataString;
   std::vector<char> inputDataChar;
   std::vector<int> inputDataInt;
   std::vector<float> inputDataFloat;
};

std::vector<col_metadata> metadata;  // create vector of col_metadata objects
std::vector<column_data> columns;  // create vector of column_data objects

Main function,

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

#include "columns.h"

using namespace std;

// creates a data object and meta data object for each column
int createColObjs( stringstream &colsRowStream ){
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      // create new column data object
      col_metadata newColMeta;  metadata.push_back(newColMeta);
      // create new column data object
      column_data newCol;  columns.push_back(newCol);
   }
   // clear the buffer
   colsRowStream.clear();
}


int main(int argc, char **argv) {

// declarations for args
   // file names
   int cmdArguments;
   std::string colsInFile, splitsInFile, outputFile;

//---------------------------------------------------------------------
//  Parse command line arguments
while ((cmdArguments = getopt (argc, argv, "c:i:o:d:h")) != -1)
   switch (cmdArguments)
   {
      case 'c':
         colsInFile = optarg;
      break;
      case 'i':
         splitsInFile = optarg;
      break;
      case 'o':
         outputFile = optarg;
      break;
      case 'd':
         //set delimiter;
      break;
      case 'h':
         std::cout << std::endl; // print help blurb
         exit(0);
      break;
   }

//---------------------------------------------------------------------
// check args

   if(splitsInFile.length() == 0) {
      std::cout << "  -> no input file was specified" << std::endl;
      return(-1);
   }
//---------------------------------------------------------------------
// declarations
   int i; // loop iterator
   std::string newSplitsLine, rowCell;
   std::vector<std::string> storeSplitsRows;    // storage structure for input rows
   stringstream colsRowStream;

//---------------------------------------------------------------------

   // create an input stream and open the parameter file
   ifstream splitsInput;
   splitsInput.open( splitsInFile.c_str() );

   // read innput file and store,
   while(getline(splitsInput,newSplitsLine)) {
      // remove '\r' EOL chars
      newSplitsLine.erase (remove(newSplitsLine.begin(),newSplitsLine.end(),'\r') , newSplitsLine.end());
      // store row data in vector of string
      storeSplitsRows.push_back(newSplitsLine);
   }

   //use first row to create objects for each col
   colsRowStream << storeSplitsRows[0];
   createColObjs ( colsRowStream );


   // load information from metadata row 0 to metadata.content
   colsRowStream << storeSplitsRows[0];
   i = 0;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      metadata.content = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();

   // load information from metadata row 1 to metadata.type
   colsRowStream << storeSplitsRows[1];
   i = 0;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      // add header row content to content field
      metadata.type = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();

   // load information from metadata row 1 to metadata.scale_type
   colsRowStream << storeSplitsRows[2];
   i = 0;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      // add header row content to content field
      metadata.scale_type = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();

   std::cout << std::endl;
   for (i=0; i<metadata.size(); i++) {
      std::cout << "metadata[" << i << "].content     " << metadata.content << std::endl;
      std::cout << "metadata[" << i << "].type        " << metadata.type << std::endl;
      std::cout << "metadata[" << i << "].scale_type  " << metadata .scale_type << std::endl;
      std::cout << std::endl;
   }

} // main EOF brace

This compiles and runs, giving the expected output. I have attached the src. This part just loads the data from the first few rows into the respective col objects. Later code would load the actual data, rows 7-11 in the attached data file, into a vector of the appropriate type. There is an issue that I am loading from the input file and storing as a string, and then parsing the string, possibly to int or real. I know you can convert from string to real, etc, but that seams like a silly solution. The issue is that the rows of data contain all kinds of types, so I'm not sure what else to do.

The above code is written out in blocks, where it is obvious that there should be a function to load data,

// accepts a string stream and parses the data in to columns
int metadata_row_toCol( stringstream &colsRowStream ){
   int i = 0;
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      metadata.content = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}

colsRowStream << storeSplitsRows[0];
metadata_row_toCol( colsRowStream ); // loads the first row to metadata.content

This function works but I need to be able to pass the object.var that is being populated in the second function, since it can't be hardcoded to metadata[i].content like above.

I hope that makes it a bit more clear as to what I'm trying to do, although I'm sure it makes it no more certain I'm going about it the right way.

LMHmedchem

Sure, but why pass it as a stringstream? Why not just pass it as a string?

Or if you really want to pass it a stringstream, why not make one function to handle file streams and stringstreams? The whole point of a stream is you don't have to care what kind it is...

Oh, I see. You want a stringstream so that you can stream>>vartype with it.

It's a choice between

if(vartype==1) stream>>vartype1;
else if(vartype==2) stream>>vartype2;
...

and

if(vartype==1) sscanf(string, "%f", &vartype1);
else if(vartype==2) sscanf(string, "%d", &vartype2);

Be honest with yourself: There's no way around sheer brute force either way, they're equally ugly. >> looks prettier but is harder to check for errors and more difficult to process formatted input with.

You can indeed pass a string stream pointer that way, by the way, but you have to use its members with -> instead of .

Another concept that may help you is the union, with which you can build one class to hold many types.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

class multitype
{
public:
        multitype(const char *i):e(strdup(i)),type(T_CSTRING)   { }
        multitype(float i):c(i),type(T_FLOAT)   { }
        multitype(char i):b(i),type(T_CHAR)             { }
        multitype(int i):a(i),type(T_INT)               { }

        inline operator int() const     { return(a); }
        inline operator float() const   { return(c); }
        inline operator char() const    { return(b); }
        inline operator const char *() const    { return(e); }

        ~multitype()
        {       if(type==T_CSTRING) free(e);            }

protected:
        enum { T_INT, T_CHAR, T_FLOAT, T_DOUBLE, T_CSTRING } type;

        // All members of a union occupy the SAME memory.
        // You can't use more than one at once.
        union
        {
                int a;
                char b;
                float c;
                char *e;
                // You cannot put classes in a union though, since it won't know
                // which constructor to use!
        };
};

int main(int argc, char *argv[])
{
        multitype str("asdf"), f(3.14159f), i(32);

        printf("string %s float %f int %d\n", (const char *)str,
                (float)f, (int)i);
        return(0);
}

I have made some progress, I will deal with the issue of using string or stringstream in a little bit. Can a string be parsed by a delimiter using the same method I was using for stringstream, or do I have to do something different?

I have the function creating the objects,

// creates a data object and meta data object for each column
int createColObjs( stringstream &colsRowStream ){
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      // create new column data object
      col_metadata newColMeta;  metadata.push_back(newColMeta);
      // create new column data object
      column_data newCol;  columns.push_back(newCol);
   }
   // clear the buffer
   colsRowStream.clear();
}

This is called with,
colsRowStream << storeSplitsRows[0];
createColObjs ( colsRowStream );

which dumps the first row into the stream and passes it to the create object function. I get a col metadata object and a col data object for each col in the input file. The objects are stored in vectors, metadata and columns respectively.

I am a bit stuck on the loading function, which parses the stream and loads it to the appropriate var in the col object.

// accepts a stringstream and parses the data in to columns
int metadata_row_toCol( stringstream &colsRowStream, 
                        std::vector<col_metadata>& metadata ){
   int i = 0;
   std::string rowCell;
   // parse row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      metadata.content = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}

This above works, but is hard coded to put the data into metadata[i].content. I can't seem to see how I indicate that the first rows goes into content, but the next row goes into a different member. It seems like I should be passing a pointer to metadata[i].content, and not to the metadata vector of objects.If I did that, I could pass a pointer to metadata[i].type for the next row, etc. All the function would need to know is that it is expecting a pointer to a class variable. At any rate, I need to be able to tell the loading function where to put the data that is parsed out of the stream, but I can't seem to work out the syntax.

LMHmedchem

You have to do something different, perhaps something like this:

# from http://stackoverflow.com/questions/289347/using-strtok-with-a-string-argument-instead-of-char
void split(const string& str, const string& delim, vector<string>& parts) {
  size_t start, end = 0;
  while (end < str.size()) {
    start = end;
    while (start < str.size() && (delim.find(str[start]) != string::npos)) {
      start++;  // skip initial whitespace
    }
    end = start;
    while (end < str.size() && (delim.find(str[end]) == string::npos)) {
      end++; // skip to end of word
    }
    if (end-start != 0) {  // just ignore zero-length strings.
      parts.push_back(string(str, start, end-start));
    }
  }
}

I admit this looks clunky compared to stringstream... In C I'd just use strtok or strtok_r, which has side-effects that one must stay aware of but makes a lot of problems stupidly simple.

On first look I'm left wondering, you DID resize the vector to accommodate all the elements it might need, yes? They don't grow automatically.

The difficulty arises because you're processing it a line at a time even though the data doesn't make sense as individual lines. Make a function that takes a file stream(or better yet, a general purpose istream), it will be able to read and process additional lines at need by whatever method you please.

Well I have something that works. What I ended up doing is loading all of the data to the objects as string. I plan to do the conversion from string to whatever as a second step. That will simplify the loading functions, since they will only have to deal with one type. I think I can do conversion as a class method that will read the string vector in the object and do the conversion to either the int vector or the float vector. In some cases it stays as string.

Here is the code,
Header file,

class col_metadata {
public:
   // initialize class members
   col_metadata()
      : scaleMin(0.0), scaleMax(0.0),
        content(""), type(""), scale_type(""),
        strScaleMin(""), strScaleMax(""),
        header("") { }

   float scaleMin, scaleMax;
   std::string content, type, scale_type,
               strScaleMin, strScaleMax,
               header;
};

class column_data {
public:
   // initialize class members
   column_data()
      : header("") { }

   std::string header;
   std::vector<std::string> inDtaStr;
   std::vector<char> inputDataChar;
   std::vector<int> inputDataInt;
   std::vector<float> inputDataFloat;
};

main file,

#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include "columns.h"

using namespace std;

// global declarations
   // declare vector of col_metadata objects
   std::vector<col_metadata> metadata;
   // declare vector of column_data objects
   std::vector<column_data> columns;

// functions
// creates a data object and meta data object for each column
int createColObjs( stringstream &colsRowStream ){
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      // create new column data object
      col_metadata newColMeta;  metadata.push_back(newColMeta);
      // create new column data object
      column_data newCol;  columns.push_back(newCol);
   }
   // clear the buffer
   colsRowStream.clear();
}

// accepts a stringstream and parses the data in to columns
int metadata_row_toCol( stringstream &colsRowStream, 
                        std::vector<col_metadata>& metadata,
                        std::string col_metadata::* var ){
   int i = 0;
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      metadata.*var = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}

// accepts a stringstream and parses the data in to columns
int header_row_toCol( stringstream &colsRowStream, 
                    std::vector<column_data>& columns,
                    std::string column_data::* var ){
   int i = 0;
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      columns.*var = rowCell;
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}

// accepts a stringstream and parses the data in to columns
int data_row_toCol( stringstream &colsRowStream, 
                    std::vector<column_data>& columns ){
   int i = 0;
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      columns.inDtaStr.push_back(rowCell);
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}


int main(int argc, char **argv) {

// declarations for args
   // file names
   int cmdArguments;
   std::string colsInFile, splitsInFile, outputFile;

//  Parse command line arguments
while ((cmdArguments = getopt (argc, argv, "c:i:o:d:h")) != -1)
   switch (cmdArguments)
   {
      case 'c':
         colsInFile = optarg;
      break;
      case 'i':
         splitsInFile = optarg;
      break;
      case 'o':
         outputFile = optarg;
      break;
      case 'd':
         //set delimiter;
      break;
      case 'h':
         std::cout << std::endl; // print help blurb
         exit(0);
      break;
   }

// check args
   if(splitsInFile.length() == 0) {
      std::cout << "  -> no input file was specified" << std::endl;
      return(-1);
   }

// declarations
   int i; // loop iterator
   std::string newSplitsLine, rowCell, loadvar;
   std::vector<std::string> storeSplitsRows;    // storage structure for input rows
   stringstream colsRowStream;

// read input file
   // create an input stream and open the splitsInFile file
   ifstream splitsInput;
   splitsInput.open( splitsInFile.c_str() );

   // read innput file and store
   while(getline(splitsInput,newSplitsLine)) {
      // remove '\r' EOL chars
      newSplitsLine.erase (remove(newSplitsLine.begin(),newSplitsLine.end(),'\r') , newSplitsLine.end());
      // store row data in vector of string
      storeSplitsRows.push_back(newSplitsLine);
   }

// create objects to store data and meta data
   //use first row to create objects for each col
   colsRowStream << storeSplitsRows[0];
   createColObjs ( colsRowStream );

// load metadata into metadata objects
   // declare pointer to class data member, this is used to tell the loading
   // function where the data goes in the object
   std::string col_metadata::*var;

   // load information from metadata row 0 to metadata.content
   colsRowStream << storeSplitsRows[0];
   //set pointer to metadata.content
   var = &col_metadata::content;
   // call func to load row 0 data to content
   metadata_row_toCol( colsRowStream, metadata, var );

   // load information from metadata row 1
   colsRowStream << storeSplitsRows[1];
   var = &col_metadata::type; // load type row
   metadata_row_toCol( colsRowStream, metadata, var );

   // load information from metadata row 2
   colsRowStream << storeSplitsRows[2];
   var = &col_metadata::scale_type; // load scale_type row
   metadata_row_toCol( colsRowStream, metadata, var );

   // load information from metadata row 3
   colsRowStream << storeSplitsRows[3];
   var = &col_metadata::strScaleMin; // load strScaleMin row
   metadata_row_toCol( colsRowStream, metadata, var );

   // load information from metadata row 4
   colsRowStream << storeSplitsRows[4];
   var = &col_metadata::strScaleMax; // load strScaleMax row
   metadata_row_toCol( colsRowStream, metadata, var );

   // load information from metadata row 5
   colsRowStream << storeSplitsRows[5];
   var = &col_metadata::header;  // load header row
   metadata_row_toCol( colsRowStream, metadata, var );

   // print loaded data for checking
   std::cout << std::endl;
   for (i=0; i<metadata.size(); i++) {
      std::cout << "metadata[" << i << "].content      " << metadata.content << std::endl;
      std::cout << "metadata[" << i << "].type         " << metadata.type << std::endl;
      std::cout << "metadata[" << i << "].scale_type   " << metadata.scale_type << std::endl;
      std::cout << "metadata[" << i << "].strScaleMin  " << metadata.strScaleMin << std::endl;
      std::cout << "metadata[" << i << "].strScaleMax  " << metadata.strScaleMax << std::endl;
      std::cout << "metadata[" << i << "].header       " << metadata.header << std::endl;
      std::cout << std::endl;
   }

// load data to data objects
   // load header row
   int metadata_rows, j;
   std::string column_data::*varCol;
   std::vector<std::string> column_data::*stringVar;

   // number of rows of metadata before header
   metadata_rows = 5;

   // load header row data
   colsRowStream << storeSplitsRows[metadata_rows];
   varCol = &column_data::header;  // load header row
   header_row_toCol( colsRowStream, columns, varCol );

   for(i=metadata_rows+1; i<storeSplitsRows.size(); i++ ) {
      // load first row to stringstream
      colsRowStream << storeSplitsRows;
      data_row_toCol( colsRowStream, columns );
   }

   // print loaded data for checking
   std::cout << std::endl;
   for (i=0; i<columns.size(); i++) {
      std::cout << "columns[" << i << "].header       " << columns.header << std::endl;
      for (j=0; j<columns[0].inDtaStr.size(); j++) {
         std::cout << "columns[" << i << "].inDtaStr[" << j << "]  " << columns.inDtaStr[j] << std::endl;
      }
      std::cout << std::endl;
   }

}

What I did to load the metadata was to declare a pointer to class data member,

    std::string col_metadata::*var;

Then in the function definition, I pass a reference to the vector of objects, and also the pointer to data member,

 int metadata_row_toCol( stringstream &colsRowStream, 
                         std::vector<col_metadata>& metadata,
                         std::string col_metadata::*  var );

As far as I can tell, the function just knows there will be a pointer, but doesn't care which data member it points to, or which object. In the loading fucntion, it's just,

      metadata.*var = rowCell;

Then I select a row to process,

    colsRowStream << storeSplitsRows[0];

point *var to what I want (the content string here, which is in row 0)

   var = &col_metadata::content;

and call the loading function with the row, the vector of objects, and the pointer.

metadata_row_toCol( colsRowStream, metadata, var );

This lets me load each row to a different data member by changing the pointer. As far as I can tell, the only limitation is that the pointer is declared as a string, so I couldn't point to an int data member, etc. I could declare different kinds of pointer if I needed to.

For the data rows, I am loading everything as string, so the destination in the objects is always the same. I am using push_back to dynamically size the vectors.

I think that logically these functions should be class methods, but I'm not sure how to go about adding them. I seem to remember issues when referencing thing from inside a class. I also need to add methods for doing the translation from string to int and string to float.

Do you see anything grievously wrong with how I'm going about this? I am hoping to develop some nice reusable code that I can implement in other apps that use similar data. I think I can dispense with storing the entire file on input, since I am storing as string anyway, or I can just clear the storage vector when I am done with it? Is there some point at which I need to become concerned with resource allocation? I have tried the above on an input file of 250 cols and ~16,000 rows. The app uses about 135MB of RAM and takes 1min 8 seconds to complete. This includes writing the data back to an output file in an inefficient method.

LMHmedchem

Is this a typo? metadata.*var = rowCell;

I'm assuming you meant *var = rowCell;

You've ignored everything I've pointed out to date, but since you ask I'll repeat and add a few more things...

  1. All your loader code is hardcoded. It doesn't happen in a constructor, or even in member functions. You'll have a hard time making this generic later.
  2. Instead of having an array of columns, you have a column of arrays. This data model is going to become very weird when you try to encapsulate this in its own object.
  3. You're using 4 different vectors instead of one vector of something flexible. When you start putting data into those vectors, 3/4 of the memory is going to be blank and wasted.

As for how much memory waste 135 megs is, I can't tell without knowing the size of the input data. 16,000 lines and 250 columns is rather meaningless when you don't know how big the columns tend to be. We still know almost nothing about the data itself at this point except that it's tab-delimited, which is why I've had so little to no actual code to offer you about how to process it, just concepts.

But I suspect the efficiency isn't all that good at this point. Too many objects in containers of containers in objects. Big to store, and cumbersome to use.

This is not actually a typo,
metadata[i].*var = rowCell;
assigns the value of the rowCell string to a data member of a col_metadata object that is stored in the vector metadata. The var pointer points to the specific data member. There is one metadata object for each column, so it needs to loop though and load each parsed "cell" to the right object.

The var pointer is declared as a string,
std::string col_metadata::*var;
since all the data members of col_metadata (that get loaded here) are string. I think if you wanted to point to an int data member, the pointer would need to be declared as int, etc.

In the first call to metadata_row_toCol (), row 0 of the input is processed, and var is set to point to the col_metadata data member "content".

   colsRowStream << storeSplitsRows[0];
   var = &col_metadata::content;
   metadata_row_toCol( colsRowStream, metadata, var ); 

In the function, it should behaive like,

    metadata[0].content = rowCell;
    metadata[1].content = rowCell;
    metadata[2].content = rowCell;

as it loops through the objects in the metadata vector.

In the next call, row 1 of the input is processed, and var is set to point to the col_metadata data member "type".

   colsRowStream << storeSplitsRows[1];
   var = &col_metadata::type; // load type row
   metadata_row_toCol( colsRowStream, metadata, var );

As far as I understand it, this call should work like,

   metadata[0].type= rowCell;
   metadata[1].type= rowCell;
   metadata[2].type= rowCell;

This is how I used the same loading function to load each row to a different class data member and each "cell" in the row to a different object.

If it seems as if I have ignored you comments, it is only my trying to get something that actually works. I promise I will implement as many of the suggestions as I can, but I sometimes have a very hard time doing things like that until I have some working code. I don't know if that is an odd way of doing things or not, but please do feel as if your efforts are unappreciated or ignored. I find it much easier to modify working code than to perceive how code might work before I get started. I probably posted to early on and should have waited until I had things better sorted out, but I was concerned that I would head off to Neptune when I really wanted to go to Mars.

This is the next thing I am working on. I think all of the loader functions should be member functions. The load metadata function is generic in that all you have to do is to change where the pointer is going. One reason I went with loading everything as a string is that there can be a hardcoded function to load the column data in a vector. Do you think I should convert the data from string before loading it?

Setting aside the metadata for a moment, it looks like I have a vector of objects, columns. Each object holds a vector with the data from the column, plus a string with the column header. I need to do things, like take the standard deviation of the column data. I think it shold be simple to just pass the columns[i].inputDataFloat vector to a function to calculate the SD and just loop through columns to do all of them. Outputting in rows will be annoying, but if I stored the input in rows, it would be annoying to do the SD, and other column based data manipulation. I'm not sure what the tastiest poison is in this case.

The very simple answer here is that I don't know how to do anything else, but I would be very happy to learn. I will look at the union code you mentioned again. I didn't think that the empty unused vectors would use up any significant resource. Is there a way other than union to make a generic vector that would take many different data types?

The actual input file is ~11MB, so there really is significant inflation in the memory used. Some of this will be cleared up when I remove the storage of all the rows and process them on input.

I thought I included a sample input file with the last src I attached. I have attached here my current src, along with two sample input files.

That is probably a generous understatement.

What I would end up doing with this data is to load it, calculate mean and SD on the data columns of float and int data (ignoring string), and use the mean/sd to normalize and scale the data. Each col is normalized based on its own mean/sd, so there is a need to keep the data from each col in its own data structure that can be passed around. Later, I would output specific rows and cols to new files based on parameters read from other input files (which I haven't got to yet). It seems like the current method (cleaned up quite a bit) would allow me to easily make those transformations. I have written similar code before, but I need to make this more re-usable so I don't keep trying to re-invent the wheel all the time.

LMHmedchem

But col_metadata has no member named 'var', and if it did, you wouldn't put the * operator in that particular place to use it.

It's declared as a pointer to a string with this col_metadata:: thrown into the middle. I think col_metadata:: and metadata. are entirely superfluous here. The code has no need to know that a pointer to a string came from a col_metadata object, a string is still a string no matter what the source. I'm surprised it compiled at all.

I don't think it matters at this point, that at least will be changed fairly easily later.

I suppose it might come down to a matter of taste but small objects with simple behavior are easier for me to work with than one humungous object with operators for everything. If you build a small fundamental something that works, making an array of them is as simple as vector<type>. If your type is the vector, it becomes your job to worry about all that yourself...

Unfortunately they are; imagine that you have 10 columns, the first 8 are int, the 9th is char, and the last is float. All three vectors will need 10 positions allocated to them. If one row is always the same type I suppose that's not quite so bad but the creation and destruction of all these unused structures is still a significant use of CPU time.

Well, the union is the least amount of work -- it acts like you have n different variables but stores them all in the same place. You could allocate memory, I suppose, and keep a void pointer to it, typecasting it into different types at need, but then your destructor would need n different cases to free it when it's done ala if(type == TYPE_FLOAT) delete (float *)ptr; else if(type == TYPE_INT) delete (int *)ptr; ...

I suppose you could have a base class holding a void pointer, and descend a bunch of different types from it. Each different subclass would have its own separate virtual destructor and know how to free the pointer without having to check what type it is; the compiler would remember what type it was and choose the function accordingly. But then you'd have to make your vectors all vectors of pointers so you can allocate the objects with new every time, and free them all the hard way when you're done with them. And you'd still have to check what type it was before you used it; C++ has no way of implicitly telling you that.

When dealing with simple atomic types, I think the union method is the easiest by far.

Hmmmm. Have you considered writing something in the awk language? The new&improved 'nawk' version especially, which supports super-modern things like functions :wink: awk's designed for chewing up and doing math on huge flatfiles. It's one of those things you avoid for years, finally force yourself to learn, then find yourself using every other day...

I found the documentation for pointers to class data members on an ibm site, but I had never heard of them before. I also found a thread at stackoverflow where someone had asked why you would ever use one.
C++: Pointer to class data member - Stack Overflow

I think the examples there are pretty good.

One of the things mentioned in the thread is that the compiler doesn't care which object data member is pointed to (as long as it is the same type as the pointer) and it also doesn't care which object you are referring to. It looked like if I declared it as pointer to string, I could use it to point to any string data member of any object of the class. Every col_metadata object has a member "content" of type string. Since those objects are stored in the vector metadata, I don't see how I can assign the value for content without referencing metadata[i]. If I just assigned
*ver = rowCell;

which object would get the value? I am sending the entire vector of objects to the loading function, not just a single object, although I could send one object at a time, which I think I would have to do if I moved the loading function to be a class member..

I think that,
std::string col_metadata::* var

in the function definition says that the function is expecting a pointer to a string that is a data member of col_metadata. The pointer is also declared and assigned as the same,
std::string col_metadata::*var = &col_metadata::content;

so it isn't just pointer to a string, but a string that is a data member of col_metadata. I freely admit that I am well out on a limb here as far as understanding this. Now I get what you are saying, a pointer to string is a pointer to string. It does seem as if the compiler shouldn't care that *var points to a string in a col_metadata object, or that it could even do anything with the knowledge anyway. It is possible that if I just declared it as a pointer to string, but assigned the value as an object data member,
std::string *var = &col_metadata::content;

it would work just as well (after changing the function definition). I will check that. I was just following the syntax given on the ibm page and stackoverflow. Perhaps it is just to self document what the pointer is used for? It does compile and execute, so at worst it would seem to be superfluous, or I just got lucky.

I see the current data structure as the following. For the input file,

index    Name          f1    RI_6    SAMsN       SpyridnN    Ssp3C
1        creatinine    R     180     41.6916     0           22.9958
2        putrescine    R     243     0           0           45.5712
3        cotinine      S     254     12.2749     14.5231     45.5611
4        histamine     R     259     0           14.302      22.5163
5        urethane      R     201     15.0141     0           23.0003
 

Setting aside the metadata stuff (which are rows the occur before the header row), there would be one object of column_data created for each of the 7 cols and these would be stored in the vector columns. columns[0] would hold the object for the first col, the value of the "header" string member would be "index" and the vector of string would have the values in the col.

columns[0].header    =  index
columns[0].inDtaStr  = (1, 2, 3, 4, 5)

and for the rest,

columns[1].header    = Name
columns[1].inDtaStr  = (creatinine, putrescine, cotinine, histamine, urethane)
columns[2].header    = f1
columns[2].inDtaStr  = (R, R, S, R, R)
columns[3].header    = RI_6
columns[3].inDtaStr  = (180, 243, 254, 259, 201)
columns[4].header    = SAMsN
columns[4].inDtaStr  = (41.6916, 0, 12.2749, 0, 15.0141)
columns[5].header    = SpyridnN
columns[5].inDtaStr  = (0, 0, 14.5231, 14.302, 0)
columns[6].header    = Ssp3C
columns[6].inDtaStr  = (22.9958, 45.5712, 45.5611, 22.5163, 23.0003)

Is this how you see the data being stored as you look at the code? This seems like a reasonable storage structure with the ability to access column data by both input col number (position in columns) and input row number (element number in inDtaStr vector). I could probably map the header name to the position in columns if I needed to lookup by the header.

I guess the first question is if this is a reasonable way to store and access that data, presuming that I convert the real and int from string? I am looking at using a template to store the data in the objects, instead of a vector of specific type. That would make things more flexible, but would mean having to convert the type before storage if I wanted to keep to just one vector in each object.

The largest objects here are for one column of data. The only way to make them smaller would be to have an object for each individual cell. If you mean keeping the class limited to specific functions and datatypes, that makes sense and I have already split the original class in two.

It seemed that each object would be loaded with its own data, and all the vector data for a column will be of the same type, so only one vector will get loaded. Hopefully using a class template will mean that I will only need one storage vector per object, if I convert from string to int of float before loading, or while loading the vector. At worst, there would be two vectors, one of which will be cleared right after conversion.

What do you think about using a template instead of a union, of should I post a more specific example?

I do use awk for a few things, along with perl and sed, etc. My brother-in-law is good in awk and I have a few scripts that use it in a limited fashion that he helped me with. I tend to think of it more in the interpreted world, as it can take forever with some of the things I use it for. Most of that is text processing regex stuff. Its great at huge files, like the other uniux text utils, since it tends to process single lines, or smaller parts of the input. I couldn't get by without sed for some things, although it makes cpp syntax look like a Jack and Jill book. I swear it's actually in sanskrit and belongs on clay tablets in a hole in the ground somewhere far away.

LMHmedchem

This is some very bizarre kind of indirect pointer offset I've never heard of before, then.

I would've had an array of strings and passed in an integer or enum to say which index to set. That way you'd even be able to check if you were given an invalid index -- values <0 or >n would be invalid and that's that.

I figured if I make these member functions, I would just have a "set_header" function, etc. I think if I make them member functions, I need to specify an object the the call. That would mean I would have to make a separate call for each column, which would be ok I guess.

I need to work a bit on the code to convert from string. I think it makes the most sense to do that in the loading function, since the discrete strings are in scope there,

// accepts a stringstream and parses the data in to columns
int data_row_toCol( stringstream &colsRowStream, 
                    std::vector<column_data>& columns ){
   int i = 0;
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      columns.inDtaStr.push_back(rowCell);
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}

Each "rowCell" is what needs to be converted, so I could add a call to something like the code you posted earlier,

// accepts a stringstream and parses the data in to columns
int data_row_toCol( stringstream &colsRowStream, 
                    std::vector<column_data>& columns ){
   int i = 0;
   std::string rowCell;
   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      convert_string(rowCell);
      columns.inDtaStr.push_back(rowCell);
      i++;
   }
   // clear the buffer
   colsRowStream.clear();
}

or something like that.

I need to take a break from this to finish my taxes, fun, fun, fun. :frowning:

LMHmedchem

Depends on your model. As you have it now maybe, but the code we're talking about and the code you're writing haven't been even close to each other for a while now.

You can only make a regex so fast.

I just tested an awk version of standard deviation versus C and C++ versions on a 100,000 row, 100-column, 55-megabyte flat file. awk took 50 seconds, used 75 megs of RAM. C++ (both implemented like yours -- getline + string tokening + vector vectors) took 10 seconds and used 60 megs of RAM. Plain C took 5 seconds and used 35 megs of RAM. It seems that, compared to C++, for every instruction of work awk does, it spends 4 others doing things like type conversion and syntax checking. It also seems that the overhead of using classes and templates can be significant, so if you're building for performance, maybe inheritance and virtual members isn't the way to achieve what you want.

As opposed to your program, which processes huge text files, line by line, breaking them apart into smaller text tokens on whitespace and processes them in a stateful manner. Nothing similar at all.

How much more work will it take to check for data errors, syntax errors, prevent divide-by-zero conditions, make sure you don't use the wrong data types together, parse your complex multi-line input, etc, etc, etc every time it needs to do math on two numbers? Perhaps just a handful more instructions per operation? Eventually you're going to realize your program is -- horror of horrors -- another interpreter :stuck_out_tongue: If you have to use one, you might as well use a good one...

Well I'm back at this after finishing my taxes.

Per the first suggestion posted, I am now going to try to eliminate the part of the code that stores the entire input file in a vector of string.

This is the code that opens the input file and parses it into rows.

   // create an input stream and open the splitsInFile file
   ifstream splitsInput;
   splitsInput.open( splitsInFile.c_str() );

   // read innput file and store
   while(getline(splitsInput,newSplitsLine)) {
      // remove '\r' EOL chars
      newSplitsLine.erase (remove(newSplitsLine.begin(),newSplitsLine.end(),'\r') , newSplitsLine.end());
      // store row data in vector of string
      storeSplitsRows.push_back(newSplitsLine);
   }

The rest of the code directs a row of code into a stringstream, and then it is parsed into "cells" using a tab delimiter.

   stringstream colsRowStream;
   colsRowStream << storeSplitsRows[0];

   // parse header row into cells, tab delimiter
   while(getline(colsRowStream,rowCell,'\t')) {
      //stuff = rowCell;
   }

I could direct the string newSplitsLine into the string stream in the while loop, and continue with the rest as is, or I cold change the code to parse the string instead of the stringstream. I suppose I could also parse the ifstream from the input file directly into cells, but that would be more involved.

Is this what was meant by, "A function that loads from a stream, which your file-opening member could use too, instead of just one member which loads from a filename and nothing else." in the first reply post? If I just parse splitsInput, the stream from the input file, I would have to parse by tab for the cells, and then the end of line character to differentiate between rows. Can that be done on a stream with getline, or do I have to divert each row into some temp structure and then parse the temp structure into cells?

I am trying to lighten the code and remove redundant steps. Let me know if anyone wants me to post the whole src.

LMHmedchem