Solved

Using a vector of maps - building and accessing data

Posted on 2003-10-26
12
822 Views
Last Modified: 2013-12-14
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("");
}
0
Comment
Question by:cliff_m
  • 6
  • 5
12 Comments
 

Expert Comment

by:gdean
Comment Utility
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
 
LVL 15

Expert Comment

by:efn
Comment Utility
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
 

Author Comment

by:cliff_m
Comment Utility
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
 

Author Comment

by:cliff_m
Comment Utility
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
 
LVL 15

Accepted Solution

by:
efn earned 125 total points
Comment Utility
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
 

Author Comment

by:cliff_m
Comment Utility
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
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 15

Expert Comment

by:efn
Comment Utility
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
 

Author Comment

by:cliff_m
Comment Utility
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
 
LVL 15

Expert Comment

by:efn
Comment Utility
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
 

Author Comment

by:cliff_m
Comment Utility
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
 

Author Comment

by:cliff_m
Comment Utility
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
 
LVL 15

Expert Comment

by:efn
Comment Utility
Looks good to me.  I hope it works!

--efn
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Introduction This article is the first in a series of articles about the C/C++ Visual Studio Express debugger.  It provides a quick start guide in using the debugger. Part 2 focuses on additional topics in breakpoints.  Lastly, Part 3 focuses on th…
Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now