3

I have few strings, each one contains one word and several integer numbers (One string is whole line):

Adam 2 5 1 5 3 4
John 1 4 2 5 22 7
Kate 7 3 4 2 1 15
Bill 2222 2 22 11 111

As you can see, each word/number is separated with space. Now, I want to load these data into a map, where word (name) would be the key and the value would be vector of the numbers in line. I already have key values in separated temporary stl container, so the task is to load only the integer numbers from each line to 2D vector and then merge these two into map. The question is, is there any C++ function, which would avoid words and white spaces and get only integers from a string, or I have to search strings char-by-char like here ?

I found only partial solution, which is not able to get more than one digit number:

vector<int> out;

for (int i = 0; i < line.length(); i++) {

    if (isdigit(line.at(i))) {  
        stringstream const_char;
        int intValue;
        const_char << line.at(i); 
        const_char >> intValue;
        out.push_back(intValue);
    }
}
Community
  • 1
  • 1
witcher
  • 135
  • 2
  • 13

5 Answers5

2

If every line has the format "word number number number ...", use a stringstream and skip the word by reading it.

If the current line is in line:

vector<int> out;
istringstream in(line);
string word;
in >> word;
int x = 0;
while (in >> x)
{
    out.push_back(x);
}
molbdnilo
  • 64,751
  • 3
  • 43
  • 82
2

split the string on spaces since that seems to be your delimiter. Then check that each substring contains an int with strol.

Then use stoi to convert the integer substrings to int.

If no stoi conversion can be performed (the string does not contain a number), an invalid_argument exception is thrown, so don't try to convert the name substring.

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

std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}


std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, elems);
    return elems;
}

inline bool isInteger(const std::string & s)
{
   if(s.empty() || ((!isdigit(s[0])) && (s[0] != '-') && (s[0] != '+'))) return false ;

   char * p ;
   strtol(s.c_str(), &p, 10) ;

   return (*p == 0) ;
}


int main()
{
   std::cout << "Hello World" << std::endl;

   std::string example="Adam 2 5 1 5 3 4";

   std::vector<std::string> subStrings;

   subStrings = split(example, ' ');

   std::string sItem;
   for(std::vector<std::string>::iterator it = subStrings.begin(); it != subStrings.end(); ++it) {
        sItem = *it;
        if( isInteger(sItem) ){
            int nItem = std::stoi (sItem);
            std::cout << nItem << '\n';
        }
    }

   return 0;
}
Community
  • 1
  • 1
ldgorman
  • 1,553
  • 1
  • 14
  • 39
1

use find() and substr() of string Class to find the name if it is always at the beginning of the string.

std::string s = "Adam 2 5 1 5 3 4";
std::string delimiter = " ";
s.substr(0, s.find(delimiter)); //To get the name
s.erase(0, s.find(delimiter)); //To delete the name
//Repeat the mechanism with a for or a while for the numbers

I do not test this solution but I use something similar with always the label in first place.

If the name could be anywhere, I do not see how test it without check for every character.

Nogebour
  • 141
  • 7
1

Assuming that the name comes first, here is a function that will read the string and add to the map.

#include <map>
#include <vector>
#include <sstream>
#include <string>
#include <algorithm>

using namespace std;

typedef std::map<std::string, std::vector<int> > StringMap;

void AddToMap(StringMap& sMap, const std::string& line)
{
    // copy string to stream and get the name
    istringstream strm(line);
    string name;
    strm >> name;

    // iterate through the ints and populate the vector
    StringMap::iterator it = sMap.insert(make_pair(name, std::vector<int>())).first;
    int num;
    while (strm >> num)
        it->second.push_back(num);
}

The function above adds a new entry to the map with the first read, and on subsequent reads, populates the vector.

Note that the map::insert function returns a std::pair, where the first of that pair is the iterator to the map entry that was created. So we just get the iterator, and from there, push_back the entries.

Here is a test program:

    int main()
    {
        vector<std::string> data = { "Adam 2 5 1 5 3 4", "John 1 4 2 5 22 7", 
                                     "Kate 7 3 4 2 1 15", "Bill 2222 2 22 11 111" };
        StringMap vectMap;

        // Add results to map
        for_each(data.begin(), data.end(), 
                 [&](const std::string& s){AddToMap(vectMap, s); });

        // Output the results
        for_each(vectMap.begin(), vectMap.end(),
                 [](const StringMap::value_type& vt)
                 {cout << vt.first << " "; copy(vt.second.begin(), vt.second.end(), 
                  ostream_iterator<int>(cout, " ")); cout << "\n"; });
    }

Live example: http://ideone.com/8UlnX2

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
1

Here is a program that demonstrates an approach to the task that can be used.

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <iterator>

int main() 
{
    std::string s( "Adam 2 5 1 5 3 4" );
    std::map<std::string, std::vector<int>> m;
    std::string key;
    std::istringstream is( s );

    if ( is >> key )
    {
        m[key] = std::vector<int>( std::istream_iterator<int>( is ),
                                   std::istream_iterator<int>() );
    }

    for ( const auto &p : m ) 
    {
        std::cout << p.first << ": ";
        for ( int x : p.second ) std::cout << x << ' ';
        std::cout << std::endl;
    }

    return 0;
}

The output is

Adam: 2 5 1 5 3 4
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335