Home > Mobile >  reading a file into map with list as value
reading a file into map with list as value

Time:11-18

I have a file where each row is separated by a '|' delimiter e.g:

1|23|1234|car|10
2|12|345|taxi|11

where each first value is unique. I want to read it into a map so that first value is the key and the following ones are a list of values for that key.

I have the following implementation, but I'm not sure if my way of locating the delimiter is correct or how to assign the needed value as key.

   std::map<int, list<>> sample_map;
   
   

   std::ifstream in("file.txt");
   unsigned sum = 0;
   string line;
   while (getline(in, line)) {
      const char *last = nullptr;
      unsigned col = 0;
      for (char &c : line)
         if (c == '|') {
              col;
            if (col == 1) {
               unsigned v;
               from_chars(last, &c, v);
               // assign v to key and rest of the data 
               // as values (excluding delimiter) 
               
               break;
            }
         }
   }


CodePudding user response:

I highly recommend using a struct or class to model the input row (record):

struct Record  
{  
    int id;
    int column2;
    int column3;  
    int name;  
    int last_column;
    // Overload operator >> for this record.
    friend std::istream& operator>>(std::istream& input, Record& r);
};

std::istream& operator>>(std::istream& input, Record& r)
{
    char separator;  
    input >> r.id;
    input >> separator;
    input >> r.column2; input >> separator;
    input >> r.column3; input >> separator;
    std::getline(input, r.name, '|');
    input >> r.last_column;
    input.ignore(100000, '\n'); // Reset to next line.  
    return input;
}

Your input code could look like this:

std::map<int, Record> database;
Record r;
int id;
while (in >> r)
{
  id = r.id;
  database[id] = r;
}

There is a duplication of the ID in this implementation, but it uses the ID field as a key and you have your ID field in the record if you need it.

CodePudding user response:

I suggest adding a struct to keep the data and to overload operator>> to help with the input. I've also added a mini-struct to eat up the delimiters | and \n - which could be done purely with std::getline but you'd then need to convert the integers so I decided to use the existing operator>> to read integers from the stream.

Example:

#include <iostream>
#include <map>
#include <string>

//---------------------------------- a class to eat a delimiter
struct eat { char ch; };

// eat the expected character or set the stream in a failed state:
std::istream& operator>>(std::istream& is, const eat& e) {
    if(is.peek() == e.ch) is.ignore();
    else is.setstate(std::ios::failbit);
    return is;
}
//----------------------------------

struct data {    // the data the key in your map will map to
    unsigned a;
    unsigned b;
    std::string c;
    unsigned d;
};

// read one `data` from the stream:
std::istream& operator>>(std::istream& is, data& d) {
    eat e{'|'}; // used to eat up `|`
    return std::getline(is >> d.a >> e >> d.b >> e, d.c, '|') >> d.d >> eat{'\n'};
}

// print one `data`
std::ostream& operator<<(std::ostream& os, const data& d) {
    return os << d.a << '|' << d.b << '|' << d.c << '|' << d.d << '\n';
}

Reading the map from the file and printing it:

#include <fstream>

int main() {
    if(std::ifstream in("file.txt"); in) {

        std::map<int, data> m;

        int key;
        data d;

        // read one key and one `data` until that fails:
        while(in >> key >> eat{'|'} >> d) {
            m[key] = std::move(d);          // put it in the map
        }

        // print the result:
        for(auto&[k, v] : m) {
            std::cout << k << '|' << v;
        }
    }
}

Demo

  • Related