Using a vector of maps - building and accessing data

Hi,

I am trying to build a vector of maps, and I don't know if I am building it right. Secondly, once I have built this structure, I don't know how to get at the data.

I am trying to read a quoted comma separated file that is divded into records into one structure once, so I can access different parts of it throughout the object's life. I originally was just storing the file as a string, and then parsing and splitting as needed, but now I want to see about storing it as a better data structure.

So my question is:

How do I build a vector of maps? How do I access the data once it built?

Comments on style or substance appreciated.

This is what I have so far: (edited and partial)

typedef std::map<std::string, std::string> Record; // create a string map type
std::vector<Record> Records; // make a vector of string maps

void test::getFileContents()
{
      std::ifstream fh(Path.c_str());
      if (! fh )
      {
            std::cout << "Cannot open " << Path << std::endl;
            return;
      }
      
      std::stringstream temp;
      temp << fh.rdbuf();
      std::string FileContents( temp.str() );
      fh.close();

      std::vector<std::string> recordsOfInterest;
      recordsOfInterest.push_back("foo");
      recordsOfInterest.push_back("bar");
      recordsOfInterest.push_back("baz");
      
      for(std::vector<std::string>::iterator i = recordsOfInterest.begin(); i!=recordsOfInterest.end(); i++)
      {
            size_t start_rec = 0, end_rec = 0;
            size_t pos = 0;
            
            if ((pos = FileContents.find('#' + *i, 0)) != std::string::npos)
            {
                  start_rec = pos + i->size() + 1; // start of record is at end of record name
                  
                  if ((pos = FileContents.find("#end", pos)) != std::string::npos)
                  {
                        end_rec = pos;
                  }
            }
            
            std::string foundRecord(FileContents.substr(start_rec, end_rec - start_rec));
            Record rec;

            size_t next_pos = 0;

            // load the record map from the file fragment `foundRecord`
            while (next_pos != foundRecord.size())
            {
                  size_t pos[4];
                  for (int i=0; i<4; i++)
                  {
                        pos[i] = foundRecord.find('\"',next_pos);
                        next_pos = ++pos[i]; // look for next quote starting from next character along
                  }

                  // extract the key and value from the record buffer
                  std::string k(foundRecord.substr(pos[0],(pos[1]-pos[0])-1));
                  std::string v(foundRecord.substr(pos[2],(pos[3]-pos[2])-1));

                  // put the key and value into the map
                  rec[k] = v;

                  // look for next key starting from next character along
                  next_pos = pos[3]+1;
            }

            Records.push_back(rec);
      }
}

std::string test::getValueFromKey( const std::string& key)
{
      getFileContents(); // todo: shoud only be called once

      // iterate through our records, trying to locate matching key
      // return value at first occurance of key
      for(std::vector<Record>::iterator i = Records.begin(); i!=Records.end(); i++)
      {
            // what to do here??
            
            // have tried several variations of the following:

            //if ( *i[key].size() ) or i->[key]
            //{
            //      return i->[key];
            //}
      }

      // didn't find the key in the file
      std::cout << "No valid " << key << " found in file" << std::endl;
      return std::string("");
}
cliff_mAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

gdeanCommented:
IMO

from just a cursory examination it appears that your problem most likely lies with scoop of data.

Someone correct me if im wrong, but i believe your records are going out of scope after you exit your first function.  hence having to having to do this:

"getFileContents(); // todo: shoud only be called once"

change this
typedef std::map<std::string, std::string> Record; // create a string map type
std::vector<Record> Records; // make a vector of string maps

to this
typedef std::map<std::string, std::string> Record; // create a string map type
std::vector<Record*> Records; // make a vector of string maps

and use the new operator to dynamically create Record objects as needed.  Don't forget to clean up after your done with you vector;


my comments are going to assume you make this change, as you said you plan on doing this.

as far as accessing the maps once you've built the structure try this

vector<Record*>::iterator it;
for(it=Records.begin();it!=Records.end();it++)
{
     if((*it).find(key)
       return (*it)->second;
}

I think i have everything derefernce properly let me know if it doesn't compile and/or you dont understand;




0
efnCommented:
I don't see a problem with objects going out of scope, assuming Records is global and has static storage duration.  The line

          Records.push_back(rec);

copies the local object rec into the vector, where it should be safe.

gdean is correct that you need to use a function other than operator [] to search the map.  Operator [] will create an entry with the specified key if it doesn't already exist. (*i).find(key) returns an iterator that points to the first matching map element, if there is one.  If there is no match, it returns (*i).end().

--efn
0
cliff_mAuthor Commented:
I missed one thing in pasting the code in:

class test
{

public:
      test(const std::string& );
      ~test() { };
      std::string getValueFromKey( const std::string& );

private:
      void getFileContents();
      void setPath();

      std::string Path;

      typedef std::map<std::string, std::string> Record;
      std::vector<Record> Records;
};
0
Cloud Class® Course: Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

cliff_mAuthor Commented:
I don't know what I am doing wrong, but it does not compile. I have a feeling that I am not doing a couple of things right as well.

c:\Local\EETest\test.cpp(109): error C2451: conditional expression of type 'std::_Tree<_Traits>::iterator' is illegal
        with
        [
            _Traits=std::_Tmap_traits<std::string,std::string,std::less<std::string>,std::allocator<std::pair<const std::string,std::string>>,false>
        ]

c:\Local\EETest\test.cpp(111): error C2039: 'second' : is not a member of 'std::map<_Kty,_Ty>'
        with
        [
            _Kty=std::string,
            _Ty=std::string
        ]


relevant code snippets:

class test
{

...

private:

...

      typedef std::map<std::string, std::string> Record;
      std::vector<Record*> Records;
};

void test::getFileContents()
{

...
            criRecord* rec = new criRecord;

...

                  // put the key and value into the map
                  (*rec)[k] = v;
....

            Records.push_back(rec);
      }
}

std::string test::getValueFromKey( const std::string& key)
{

...

      for(std::vector<Record*>::iterator it = Records.begin(); it!=Records.end(); it++)
      {
            if((*it)->find(key))
            {
                  return (*it)->second;
            }
      }
}
0
efnCommented:
The line

if((*it)->find(key))

is generating the first error message because find returns an iterator, not a Boolean value, as noted in my comment above.

The line

            return (*it)->second;

is generating the second error message because it is a std::vector<Record*>::iterator, so *it is a Record*.  So with (*it)->second, you are looking for a second part of a Record.  But a Record is a map, which doesn't have a second part.  What you want is the second part of the map element that was found, so you should save the iterator returned by find() and dereference it to get a pair that has a second element.

To make my previous hint a little more explicit, I don't think you need to change the original design of vector<Record> to vector<Record*>.  The original design can work and is simpler.

--efn
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
cliff_mAuthor Commented:
This works, thanks efn.

std::string Crifile::getValueFromKey( const std::string& key)
{
      ...

      for(std::vector<Record>::iterator it = Records.begin(); it!=Records.end(); it++)
      {
            Record::iterator found = (*it).find(key);
            if (found->second.size())
            {
                  return found->second;
            }
            else
            {
                  return std::string("");
            }
      }
...

}

Now to clean all this up, I need to add some sort of delete method to the classes destructor? Do I just iterate through the vector deleting the maps?

0
efnCommented:
I don't know whether you need a destructor.  There have been two designs discussed here, one with dynamic memory allocation and one without.  If you are using dynamic memory allocation, you probably need a destructor; if you're not, you probably don't.  If you show how the vector and maps are currently declared and filled, I can give you a more specific answer.

By the way, if find does not find the specified key, it returns end().  end() is defined to point past the end of the map, so it may not point to a valid element, so it's not advisable to dereference it.  It would be safer to compare found to (*it).end() than to test found->second.size().

--efn
0
cliff_mAuthor Commented:
The following should show all the info requested as well as show your recommended changes. Thanks for pointing that out.

class test
{
...

private:
      typedef std::map<std::string, std::string> Record;
      std::vector<Record> Records;
};

void test::getFileContents()
{
...
            Record rec;

...

                  // put the key and value into the map
                  rec[k] = v;
...

            criRecords.push_back(rec);
      }
}

std::string Crifile::getValueFromKey( const std::string& key)
{
...
      for(std::vector<Record>::iterator it = Records.begin(); it!=Records.end(); it++)
      {
            Record::iterator found = (*it).find(key);
            if (found != (*it).end())
            {
                  return found->second;
            }
            else
            {
                  return std::string("");
            }
      }
}
0
efnCommented:
Thanks for accepting my answer.

I don't see any dynamic memory allocation, so I don't see any need for a destructor.

The for loop still doesn't look quite right.  Notice that control returns from the Crifile function whether the key is found or not.  If you are always going to return after checking just the first record, why have a loop?  Or to put it another way, if you don't find the key in the first record, do you really want to give up without checking the other records?

--efn
0
cliff_mAuthor Commented:
Doh!! (mixing up project code with test code, oops!)

that should read:

class test
{
...

private:
     typedef std::map<std::string, std::string> Record;
     std::vector<Record> Records;
};

void test::getFileContents()
{
...
          Record rec;

...

               // put the key and value into the map
               rec[k] = v;
...

          Records.push_back(rec);
     }
}

std::string test::getValueFromKey( const std::string& key)
{
...
     for(std::vector<Record>::iterator it = Records.begin(); it!=Records.end(); it++)
     {
          Record::iterator found = (*it).find(key);
          if (found != (*it).end())
          {
               return found->second;
          }
          else
          {
               return std::string("");
          }
     }
}
0
cliff_mAuthor Commented:
that's a pretty stupid logic error on my part. I am up @ 3:30 my time, worried about my parents in San Diego/El Cajon. :(

of course it should read:

std::string test::getValueFromKey( const std::string& key)
{
...
     for(std::vector<Record>::iterator it = Records.begin(); it!=Records.end(); it++)
     {
          Record::iterator found = (*it).find(key);
          if (found != (*it).end())
          {
               return found->second;
          }
     }
     
     // didn't find key
     return std::string("");
}
0
efnCommented:
Looks good to me.  I hope it works!

--efn
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Editors IDEs

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.