C ++ how to get ONLY integers from a complex string
I have multiple lines, each containing one word and multiple integers (one line is a 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 by a space. Now I want to load this data into a map where the word (name) will be the key and the value will be a vector of numbers in the string. I already have the key values ββin an isolated temporary stl container, so the challenge is to load only integers from each row into a 2D vector and then concatenate those two into a map. The question is, is there any C ++ function that will avoid words and whitespace and only get integers from a string, or do I need to look for char -by- char like strings here ?
I only found a partial solution that cannot get more than one digit:
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);
}
}
source to share
split the line on spaces as that appears to be your delimiter. Then check that each substring contains an int with strol .
Then use stoi to convert integer substrings to int.
If no conversion of stoi can be performed (the string does not contain a number) an invalid_argument exception is thrown, so do not try to convert the substring of names.
#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;
}
source to share
use find () and substr () of the 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 am not testing this solution, but I use something like this, always with the label first.
If the name could be anywhere, I can't see how to check it without checking for every character.
source to share
Assuming the name comes first, here is a function that will read the string and append 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 card with the first read and the subsequent read fills the vector.
Note that the function map::insert
returns std::pair
where first
this pair is an iterator to the generated map. So we just get an iterator and write push_back from there.
Here's 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
source to share
Here is a program that demonstrates an approach to a 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;
}
Output signal
Adam: 2 5 1 5 3 4
source to share