find map c++ is not finding key already added in the map - c++

The map cidades starts empty.
Consider the txt file as:
" city1, city2 "
It should add the city with a random number, which is count, when the city is not in the map
Here is the code:
int i,cont = 0; // i is used as a flag and count do add a key reference when I add a new element in the map
string line; // used to get everything untill the comma
map<string,int> cidades; // map of cities and their references .. like cidades<"Silicon Valley", 1>
ifstream arquivoTexto ("text.txt");
if (arquivoTexto.is_open()) { // open file
while (getline (arquivoTexto,line)){ // while EOF is false
stringstream element(line); i = 1; // i is always 1 when checking a new line
while(getline(element, line, ',')){ // line is the string untill the comma
if(i == 1) { // i = 1 means the current line is the city one
std::map<std::string, int>::iterator it = cidades.find(line); // try to find the current city in the map
if(cidades.empty()){ // insert first time because map is empty
cidades.insert(pair<string,int>(line,cont));
}
else if(it == cidades.end()){ // insert because is not in the map
cidades.insert(pair<string,int>(line,cont));
c1 = cont;
}else{ // get the key because is already in the map
c1 = cidades.at(line);
}
} else if( i == 2) {
// line is holding city 2, do the same above
}
cont++; i++; // increase i to tell we are working with the next string after the comma
}
But when I put the same code out of the conditionals above.. it works
string a = "teste"; // taken from the txt and added into the map
std::map<std::string, int>::iterator it = cidades.find(a);
cout << "ele: " << it->first << " chave: " << it->second;
it prints teste and the key... but when I try to print inside the if I got segmentation fault(core dumped).
Edit:
My program can't find a key that's already in the map.
when I compare the iterator in the if statement it looks like none key is in the map( even when the key is already on it,then it add the same key again with another integer reference). So now I'm trying to figure out another way to look for keys in the map,any good ideas?

It might be because line isnt found, in which case it would be set to map::end, and I think that basically means its set to 'NULL' or some invalid location which means you shouldn't dereference it i.e: it->first (hence the segmentation fault) (technically what happens if you dereference the iterator returned from find when it doesn't find anything, is undefined behaviour, i.e. don't do it.),
http://www.cplusplus.com/reference/map/map/find/
Don't do the cout there, do it in the 'else' below, where you know it is pointing to a valid item.

Related

Delete a line from a csv file c++

Here I have a file that reading in to a vector
typedef struct contacts
{
string name; //{jhonathan , anderson , felicia}
string nickName; //{jhonny , andy , felic}
string phoneNumber; // {13453514 ,148039 , 328490}
string carrier; // {atandt , coolmobiles , atandt }
string address; // {1bcd , gfhs ,jhtd }
} contactDetails;
vector <contactDetails> proContactFile;
I want to let user to delete a contact record from the file.For this I wrote a code.But from the code that i've written it deletes all the contact details from the file.But what I want here is when a user types a name then the program should delete only the name and the relevent nickname,carrier,phone number and address belongs to that name.Here is the code that i've written
string readString, selectContact;
cout << "Enter the name you want to delete" << endl;
cin >> selectContact;
ifstream fin;
fin.open(contactsFile);
if (!fin.is_open())
{
cout << "Unable to open Contacts.csv, please make sure file exists!" << endl;
}
ofstream fout;
fout.open("temp.csv" , ios::out);
while (getline(fin, readString))
{
if (((readString = readString.find(selectContact), 0)) == 0)
{
fout << readString <<',' << "\n";
}
cout << "Deleted Successfully" << endl;
showTableContacts();
}
if (((readString = readString.find(selectContact), 0) != 0))
{
cout << "\n" << selectContact << " not found" << endl;
}
fout.close();
fin.close();
remove("Contact.csv");//Deletes contacts.csv file
rename("temp.csv" , "Contact.csv");//Rename temp file as Contacts.csv
}
if (((readString = readString.find(selectContact), 0)) == 0)
This line has several issues.
readString.find(selectContact) returns the index of the match, or string::npos if it's not found. Checking that it is an exact match at position 0 is ok, technically, however, in your case if your string is "abcdefg" and you search for "abc" it will also match at position 0 when it shouldn't. You need to ensure the entire field matches, not just the first characters of it. (Find the first ',' in your line then ensure that all the bytes from the beginning up to the comma match your contact.)
Next consider this part:
readString = readString.find(selectContact)
You are assigning the result of the find to your string. That is, if find() returns 3, you will assign 3 to your string, which is interpreted as an ASCII character, and now your readString contains a single byte with junk in it. (In ASCII, 'A' is 65, etc)
So now your string is obliterated, and the index of your match is the contents, in the first byte (if the value is in range, or undefined behavior if it overflows.)
Code of this form:
if (xxx, 0)
is utilizing the comma operator, which sequences expressions by evaluating them from left to right, applying side effects (but throwing away the results) and the full expression evaluates to the value of the rightmost expression. Whatever xxx does (in your case, xxx is the code in step 2 above, obliterating your string) its return value is discarded and this whole expression evaluates to 0, which converts to a bool as false. Every time. (1,2,3) evaluates to 3, etc.
Therefore:
(readString = readString.find(selectContact), 0)
always evaluates to 0. And
if (((readString = readString.find(selectContact), 0)) == 0)
always obliterates your string, then tests if 0==0 which is always true.
Also, you almost never want to delete your input files. Rewriting them is dangerous enough, but you must be absolutely sure that everything succeeded before doing so; otherwise your program can end up causing a lot of pain if it deletes the input file and nothing else.

c++ appending text into a string until see a specific character

I have more than one input files like this:
>1aab_
GKGDPKKPRGKMSSYAFFVQTSREEHKKKHPDASVNFSEFSKKCSERWKT
MSAKEKGKFEDMAKADKARYEREMKTYIPPKGE
>1j46_A
MQDRVKRPMNAFIVWSRDQRRKMALENPRMRNSEISKQLGYQWKMLTEAE
KWPFFQEAQKLQAMHREKYPNYKYRPRRKAKMLPK
>1k99_A
MKKLKKHPDFPKKPLTPYFRFFMEKRAKYAKLHPEMSNLDLTKILSKKYK
ELPEKKKMKYIQDFQREKQEFERNLARFREDHPDLIQNAKK
>2lef_A
MHIKKPLNAFMLYMKEMRANVVAESTLKESAAINQILGRRWHALSREEQA
KYYELARKERQLHMQLYPGWSARDNYGKKKKRKREK
Here, what I have to do:
vector <string> names;
vector <string> seqs;
names.resize(total); //"total" is already known.
seqs.resize(total);
counter=0;char input;
while ((input = myInput.get()) != EOF)
{
if(input=='>')
names[counter]= take all line (>1aab_, >1j46_A, so...)
else
untill the see next '>' append the character into sequence[counter]
counter++;
}
Finally it will be like this:
names[0]=">1aab_"
sequence[0]="GKGDPKKPRGKMSSYAFFVQTSREEHKKKHPDASVNFSEFSKKCSERWKTMSAKEKGKFEDMAKADKARYEREMKTYIPPKGE"
and so on..
I am thinking about for 2 hours and I couldn't figure out it. Can anyone help about that? Thanks in advance.
There's a few ways to solve it; I'll present some examples but I'm not testing/compiling this code, so there may be minor bugs - the logic is the important bit.
Since your pseudocode appears to be processing the input character by character, I've taken that as a requirement.
The way you seem to be thinking about it would be implemented with essentially a pair of loops - one for reading the name, the other for reading the sequence - which are enclosed in an outer loop, in order to process all records.
This would look something like the following:
// first character in file should be a '>', indicating the start
// of a record.
input = myInput.get();
if (input != '>')
{
std::cerr << "Malformed input file!" << std::endl;
return /*...*/;
}
do
{
// record name continues up until the newline
while ((input = myInput.get()) != EOF)
{
if (input == '\n' || input == '\r')
break;
names[counter].push_back(input);
}
// read sequence until we hit a '>' or EOF
while ((input = myInput.get()) != EOF)
{
if (input == '>')
{
// advance to next record number
counter++;
break;
}
sequence[counter].push_back(input);
}
} while (input != EOF && counter < total);
You'll also notice I moved the check for the initial '>' to before the loop, just as a way of ingesting (and discarding) the character, as well as a basic sanity check of the input. This is because we really use this character to mark the end of the sequence (rather than the "start of a record") - when we enter the loop, we assume we're already reading the record name.
Another way to approach it is to use a state machine. Essentially, this utilises additional variables to track the state the parser is in.
For this particular case, you only have two states: either you're reading a record name, or the sequence. So, we can just use a single boolean to track which state we're in.
Armed with the state variable, we can then make decisions about what to do with the character we just read based upon the state we're in. At the simplest level here, if we're in "read the record name" state, we add the character to the names variable, otherwise we add it to the sequence variable.
// state flag to indicate if we're currently reading a name line,
// i.e. a line starting with ">"
// This should be set true by the first record we encounter, so
// we'll set it false (to indicate we're reading a sequence) in
// order to allow us to detect bad input files.
bool reading_name = false;
// indicate we're on the first record, so we can avoid incrementing
// the record counter
bool first_record = true;
// process input character-by-character until end of file
while ((input = myInput.get()) != EOF)
{
// check for start of new record
if (input == '>')
{
// for robustness, verify we're not already reading a name,
// as this probably indicates invalid input
if (reading_name)
{
std::cerr << "Input is malformed?!" << endl;
break;
}
// switch to reading name state
reading_name = true;
// advance to next record, but only if it isn't the first record
if (first_record)
{
// disable the first_record flag, and explicitly set the
// record counter to 0.
first_record = false;
counter = 0;
}
else if (++counter >= total)
{
std::cerr << "Error: too many records!" << std::endl;
break;
}
}
// first character in file should start a new record
else if (first_record)
{
std::cerr << "Missing record start character at beginning of input!" << std::endl;
break;
}
// make sure we are processing a valid record number
else if (counter >= total)
{
std::cerr << "Invalid record number!" << std::endl;
break;
}
// continue reading the name
else if (reading_name)
{
// check if we've reached the end of the line; you
// may also want/need to check for \r if your input
// files may have Windows-style line endings
if (input == '\n')
{
// switch to reading sequence state
reading_name = false;
}
else
{
// add character to current name
names[counter].push_back(input);
}
}
// continue reading the sequence
else
{
// you might need to handle line ending characters here,
// maybe just skipping them?
// add character to current sequence
sequence[counter].push_back(input);
}
}
This adds a fair amount of complexity, which is of questionable value for this particular exercise, but does make adding additional states easier in future. It also has the benefit of only a single place in the code where I/O is done, which reduces the chances of errors (not checking for EOF, overflow array bounds, etc.).
In this case, we're actually using the '>' character as an indicator that a new record is starting, so we add a bit of extra logic to make sure that all works properly with the record counter. You could also just use a signed integer for your counter variable and start it at -1, so it will increment to 0 at the start of the first record, but using signed variables to index into arrays isn't a good idea.
There are more ways to approach this problem, but hopefully this gives you somewhere to start on your own solution.

Reading a truth table in from plain text, translating it to a map<int,list<int>> in C++

I'm writing a file parser for standard C++ (no third-parties like Boost, unfortunately)...
I'm dealing with a situation where I have a plain-text file formatted like this:
1 ..header line 1, unimportant
2 ..header line 2, unimportant
3 ..header line 3, unimportant
4 1 0 0 0 0 0 0 1
5 2 0 1 0 2 1 0 0
...skipping ahead
14 11 1 0 0 0 0 1 1
15 12 0 0 1 0 0 1 2
16 13 2 0 0 0 1 0 0
...etc
(Note: The first column, 1 - 16, are line numbers. The skip ahead is meant to represent the gap of 8 spaces from the start of each line gets shorter as the second column, 1- 13, gets longer and longer numbers.
This text file denotes a truth table whereby items must be grouped by the columns, and each group will be composed of corresponding numbers from the first column. For instance, by the end of parsing this example, a map of type <int, list<int>> should look like (assuming there are no truths between lines 6 and 13):
[1: {11, 13}]
[2: {5, 15}]
[3: {12}]
[4: {5}]
[5: {5,16}]
[6: {14,15}]
[7: {4,14,15}]
In general, the number of columns in the text file can change, meaning the number of groups will change, so this must be accounted for. The number of rows is also variable, but will both will always start at 1 and the columns will not be numbered (but we can do that ourselves).
Now, were I to do this in Java I'd have a working solution rather quickly. However, I've never done work in C++ and am having trouble figuring out how to perform the operations properly, between its different structures and syntax. Despite scouring and finding lots of good guides, my lack of C++ foundation makes it hard to understand even the syntax differences that, I speculate, must be very basic.
Still, I've designed procedure, and it should work according to the following pseudocode:
//Begin Parse
//Create filereader "strmFileIn"
//To get past the first three lines, which will always be needless header info
string dummyLine;
for (i = 1; i <= 3; i++)
getline(strmFileIn, strDummyLine);
//Read first line to get count of how many groups are present
//(Copied from internet: gets the first line and puts the cursor back at its start)
int startPos = strmFileIn.tellg();
string strFirstLine;
getline(strmFileIn, strFirstLine);
strmFileIn.seekg(startPos, std::ios_base::beg);
//Tokenize strFirstLine into Array<int> tempArray
int numGroups = tempArray.size() - 1 //accounting for the row-header column, 1 - 13
//Create map (going to use java syntax, sorry)
Map<int,list<int>> myMap = new Map<int,list<int>>;
//Populate map with ints and empty lists (java again, sorry)
for (int i = 1; i <= numGroups; i++)
myMap.put(i, new List<int>);
//Iterate over lines in the file and appropriately populate the map's lists
while (fileIn != eof)
{
string fileInLine;
getline(strmFileIn, fileInLine);
//Tokenize fileInLine into Array<int> tempFileInArray
int intElemID = tempFileInArray[0];
//Remove element [0] from tempFileInArray (will be the row number, 1 - 13
//Iterate over remaining items in tempFileInArray, affect myMap where necessary
for (int i = 1; int i <= groupNum; i++)
if (tempFileInArray[i] != 0) //is not a strict truth-table, as any nonzero will be a truth
myMap.get[i].add(intElemID);
}
//Remove any entries in myMap with empty lists
//Kill strmFileIn for memory's sake
//End Parse
As you can see, my code is a broken mix of pseudocode and comparable Java I've already figured out. I just don't know how to turn this into C++; even with similar data structures, the syntax is a little daunting to someone with no experience. Is anyone here willing to help me out with it?
I really appreciate any insight.
Your code seems overly complicated, so lets do this one step at a time. Additionally, neither your code nor file format show how many bool columns should exist on each row, so I've ignored that part for this answer.
But first, a tip: In C++, the containers you care about 99.99% of the time are std::unordered_map, std::vector, and in very rare cases, std::map, boost::stable_vector and std::deque. In your case, you have rows with sequential indices, and the data for each row appears to be better stored as a vector of booleans. However, we'll do it your way, with the replacement of std::vector instead of std::list, and std::unordered_map instead of std::map.
This major data structures are mostly obvious:
std::unordered_map<int,std::vector<int>> myMap;
std::ifstream strmFileIn("input_file.txt");
Next your code reads in the first line, then ignores it entirely. I have no idea why, so I'll skip over that. Then, we parse out the lines one by one:
std::string full_current_line;
//for as long as we can read more lines, read them in
while(std::getline(strmFileIn, full_current_line)
{
//make the line into a stream so that we can parse data out
std::stringstream cur_line_stream(full_current_line);
//read in the line identifier
int identifier = 0;
cur_line_stream >> identifier;
//if that failed, abort.
if (!cur_line_stream)
{
//invalid identifer!
std::cerr << "identifier is invalid!\n"; //report
strmFileIn.setstate(std::ios::failbit); //failed to parse the data
break; //do not continue this loop
}
After that, we parse out the data for each row, which is surprisingly simple:
int column = 0;
int is_true = false;
//for each number remaining in the row...
while(cur_line_stream >> is_true)
{
//hooray we read a column!
++column;
if (is_true ==0)
{
//if it's zero, skip it
}
else if (is_true == 1)
{
//get the data for this column, and add this row's identifier
//myMap[column] will create a new empty entry if it didn't exist yet
//NOTE: This syntax only creates when used with map and unordered_map.
// This syntax does NOT create for vector and deque.
//once we have the vector, we push_back the new identifier into it.
myMap[column].push_back(identifier);
}
else
{
//invalid data!
std::cerr << is_true << " is invalid! found on row " << identifier << '\n';
cur_line_stream.setstate(std::ios::failbit); //failed to parse the data
strmFileIn.setstate(std::ios::failbit); //failed to parse the data
break; //do not continue this loop
}
}
}
If you know that groupNum contained the number of bools, you could replace that second while with something more like you already have:
for (int i = 1; int i <= groupNum; i++)
{
cur_line_stream >> is_true;
//if that failed, abort
if (!cur_line_stream)
{
//invalid data!
std::cerr << "data could not be read on row " << identifier << '\n';
cur_line_stream.setstate(std::ios::failbit); //failed to parse the data
strmFileIn.setstate(std::ios::failbit); //failed to parse the data
break; //do not continue this loop
}
else if (is_true == 0)
{
//if it's zero, skip it
}
etc etc etc
Work the other way. Code only in C++ (not in Java and don't think in Java), but start by parsing a small chunk of your syntax. First, code the lexer. Test it. Then code the parser, probably a recursive descent parser, and test it on short simple subelements of your language. Perhaps you'll need some small look-ahead (an easy task, use a std::list<Token>) Keep going up.
Start by formalizing, with pencil and paper, your input language. Could you for instance write a simple BNF grammar for it? (your question does not explain what is the input, it just gives an example)
In C++ parlance: to parse a map<int,list<int>> you certainly need to be able to parse int and list<int>. So write first the parsers for these.
As commented by Mooing Duck, your input language (which you did not define, just gave an example) seems simple enough to avoid most of this. But still, the idea is the same, think directly in C++ and start by reading a simple subpart of the input. Test your code. When that works, increase the part that is accepted. Repeat all this.
Here's a very simple solution that uses nothing but C++ and standard libraries. It just reads line by line and pulls each element out of the line with stream extraction using operator>>.
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <list>
int main(int argc, char* argv[])
{
// Parse command line
if( argc != 2 )
return 1;
std::fstream fin(argv[1]);
if( !fin.good() )
{
std::cerr << "Error opening file for reading: " << argv[1] << std::endl;
return 1;
}
// Skip first three lines
std::string line;
for( int i=0; i<3; ++i )
{
std::getline(fin, line);
}
// Read each line
std::map<int, std::list<int> > hits;
while( std::getline(fin, line) )
{
// Extract each element from the line
std::stringstream sstr(line);
// Read line number from first column
int linenum = 0;
sstr >> linenum;
// Interpret remaining columns as truth values
bool truth;
int col=1;
while( sstr >> truth )
{
// Store position in map if true
if( truth )
{
hits[col].push_back(linenum);
}
col++;
}
}
// Print results
std::map<int, std::list<int> >::const_iterator col_iter;
for( col_iter = hits.begin(); col_iter != hits.end(); ++col_iter )
{
std::cout << "[" << col_iter->first << ": {";
std::list<int>::const_iterator line_iter;
for( line_iter = col_iter->second.begin(); line_iter != col_iter->second.end(); ++line_iter )
{
std::cout << *line_iter << " ";
}
std::cout << "} ]" << std::endl;
}
return 0;
}

Basic C++ program, getline()/parsing a file

I've been tasked with creating a small program that is to parse through a text file and grab necessary info from it. The file is laid out as such
Tuesday*Info5051*10:00*11:00*M3039*Info5064*12:00*3:00*G1001;
Basically it's supposed to store each string in a struct so that I can later retrieve it, but I'm unable to get my program to work (I have a learning disability so things tend to get difficult). Here's my code so far. (I know it's a simple program but I tend to overthink/screw up stuff.) My big problem I've hit so far is that it won't open the file to start. I've saved the file to the bin->debug as well as the main folder of the program. I'm sure I'm using the getline method wrong.
struct Course
{
string _sDay;
string _sName;
string _sCode;
string _iStart;
string _iDuration;
string _sRoom;
};
int main()
{
ifstream fileIn;
fileIn.open("courseLoad.txt");
vector<Course> vCourse;
string str="*";
string line;
if (!fileIn)
{
cout<<"A error has occured, please contact support.";
}
while(!fileIn.eof())
{
for(int i=0; i!= fileIn.eof();i++)
{
//file.getline(entry.part_num, 6, '-');
getline(fileIn,line,'*');
vCourse[i]._sDay =line;
getline(fileIn,line,'*');
vCourse[i]._sName =line;
getline(fileIn,line,'*');
vCourse[i]._sCode = line;
getline(fileIn,line,'*');
vCourse[i]._iStart =line;
getline(fileIn,line,'*');
vCourse[i]._iDuration = line;
getline(fileIn,line,'*');
vCourse[i]._sRoom =line;
cout<<vCourse[i];
}//end for
}
--output to screen here--
There are several issue with this code:
1) That code is missing a return statement or an else statement to prevent the program from continuing its execution in case it cannot open the file:
if (!fileIn)
{
cout<<"A error has occured, please contact support.";
return -1;
}
2) Your getline all operate on the same input stream. You want to read in a line, then parse that line. For example:
// Read in a line
while (getline(fileIn,line))
{
string item;
std::stringstream sstr(line);
// Read in an item
while (getline(sstr, item, "*"))
{
std::cout << item << std::endl;
}
}
3) vCourse size is 0, so you cannot use the [] operator; but you can use push_back to expand the size of the vector and insert an element at the back of the vector:
// Read in a line
while (getline(fileIn,line))
{
string item;
// Default course construction
Course c;
std::stringstream sstr(line);
// Read in an item
getline(sstr,item,'*');
c._sDay = item;
getline(sstr,item,'*');
c._sName = item;
getline(sstr,item,'*');
c._sCode = item;
getline(sstr,item,'*');
c._iStart = item;
getline(sstr,item,'*');
c._iDuration = item;
getline(sstr,item,'*');
c._sRoom = item;
// Save the course into the vector
vCourse.push_back(c);
}
You could also add some more error checking in the above (in case some elements are missing from the line).
One immediate problem that is clear is that you are not actually adding any Course structs into your vector but you are assigning to the elements of them as if you are. For example
vCourse[i]._sDay =line;
but you have not actually added an instanct of a Course struct to the vector at index i. This means you assign to an instance that is not present and that is never good news. What you need prior to this is
Course newItem; // make a new Course object instance
vCourse.push_back(newItem); // This adds the instance to the end of the vector
// Now assign to the members of vCourse[i];
vCourse[i]._sDay =line;
getline(fileIn,line,'*');
vCourse[i]._sName =line;
getline(fileIn,line,'*');
vCourse[i]._sCode = line;
getline(fileIn,line,'*');
vCourse[i]._iStart =line;
getline(fileIn,line,'*');
vCourse[i]._iDuration = line;
getline(fileIn,line,'*');
then you can assign to the struct.
Also if you want to do this
cout<<vCourse[i];
you will need to overload the operator<<
If you are unable to open your file, you need to check that you have 1) spelled the filename correctly and 2) that the file is in the same location as your executable. Probably would be safer to write the full pathname anyway
You can also try to put the content of file into single string and use strtok() function.

need help with C++ using maps to keep track of words in a INPUT file

Let say i have a text file with
today is today but
tomorrow is today tomorrow
then using maps how can i keep track of the words that are repeated? and on which line it repeats?
so far i have each string in the file read in as a temp and it is stored in the following way:
map<string,int> storage;
int count = 1 // for the first line of the file
if(infile.is_open()){
while( !infile.eof() ){
getline(in, line);
istringstream my_string(line);
while(my_string.good()){
string temp;
my_string >> temp;
storage[temp] = count
}
count++;// so that every string read in the next line will be recorded as that line.
}
}
map<string,int>::iterator m;
for(int m = storage.begin(); m!= storage.end(); m++){
out<<m->first<<": "<<"line "<<m->second<<endl;
}
right now the output is just
but: line 1
is: line 2
today: line 2
tomorrow: line 2
But instead..
it should print out(no repeating strings):
today : line 1 occurred 2 times, line 2 occurred 1 time.
is: line 1 occurred 1 time, line 2 occurred 1 time.
but: line 1 occurred 1 time.
tomorrow: line 2 occurred 2 times.
Note: the order of the string does not matter.
Any help would be appreciated. Thanks.
map stores a (key, value) pair with a unique key. Meaning that if you assign to the same key more than once, only the last value that you assigned will be stored.
Sounds like what you want to do is instead of storing the line as the value, you want to store another map of lines->occurances.
So you could make your map like this:
typedef int LineNumber;
typedef int WordHits;
typedef map< LineNumber, WordHits> LineHitsMap;
typedef map< string, LineHitsMap > WordHitsMap;
WordHitsMap storage;
Then to insert:
WordHitsMap::iterator wordIt = storage.find(temp);
if(wordIt != storage.end())
{
LineHitsMap::iterator lineIt = (*wordIt).second.find(count);
if(lineIt != (*wordIt).second.end())
{
(*lineIt).second++;
}
else
{
(*wordIt).second[count] = 1;
}
}
else
{
LineHitsMap lineHitsMap;
lineHitsMap[count] = 1;
storage[temp] = lineHitsMap;
}
you're trying to get 2 items of information out of the collection, when you only store 1 item of information in there.
The easiest way to extend your current implementation is to store a struct instead of an int.
So instead of:
storage[temp] = count
you'd do:
storage[temp].linenumber = count;
storage[temp].wordcount++;
where the map is defined:
struct worddata { int linenumber; int wordcount; };
std::map<string, worddata> storage;
print the results using:
out << m->first << ": " << "line " << m->second.linenumber << " count: " << m->second.wordcount << endl;
edit: use a typedef for the definitions, eg:
typedef MYMAP std::map<std::string, struct worddata>;
MYMAP storage;
then MYMAP::iterator iter;
Your storage data type is insufficient to store all the information you want to report. You could get there by using a vector for count storage but you'd have to do a lot of book-keeping to make sure you actually insert a 0 when a word is not encountered and create the vector with the right size when a new word is encountered. Not a trivial task.
You could switch your count part to a map of numbers, first being line and second being count... That would reduce the complexity of your code but wouldn't exactly be the most efficient method.
At any rate, you can't do what you need to do with just a std::map
Edit: just thought of an alternative version that would be easier to generate but harder to report with: std::vector< std::map<std::string, unsigned int> >. For each new line in a file you'd generate a new map<string,int> and push it onto the vector. You could create a helper type set<string> to contain all the words that appear in a file to use in your reporting.
That's probably how I'd do it anyway except I'd encapsulate all that crap in a class so that I'd just do something like:
my_counter.word_appearance(word,line_no);
Apart from anything else, your loops are all wrong. You should never loop on the eof or good flags, but on the success of the read operation. You want something like:
while( getline(in, line) ){
istringstream my_string(line);
string temp;
while(my_string >> temp ){
// do something with temp
}
}