Convert String containing several numbers into integers
Asked Answered
B

8

34

I realize that this question may have been asked several times in the past, but I am going to continue regardless.

I have a program that is going to get a string of numbers from keyboard input. The numbers will always be in the form "66 33 9" Essentially, every number is separated with a space, and the user input will always contain a different amount of numbers.

I'm aware that using 'sscanf' would work if the amount of numbers in every user-entered string was constant, but this is not the case for me. Also, because I'm new to C++, I'd prefer dealing with 'string' variables rather than arrays of chars.

Broadside answered 24/8, 2009 at 8:32 Comment(1)
IMO generally preferring std::string over raw character buffers isn't a sign of "newbieness", but rather of maturity.Jacobina
S
41

I assume you want to read an entire line, and parse that as input. So, first grab the line:

std::string input;
std::getline(std::cin, input);

Now put that in a stringstream:

std::stringstream stream(input);

and parse

while(1) {
   int n;
   stream >> n;
   if(!stream)
      break;
   std::cout << "Found integer: " << n << "\n";
}

Remember to include

#include <string>
#include <sstream>
Shirtmaker answered 24/8, 2009 at 8:38 Comment(2)
You mean parse: int n; while(stream >> n) {std::cout << "Found integer: " << n << "\n";}? Much cleanerIfy
Or even parse: for (int n; stream >> n;) {std::cout << "Found integer: " << n << "\n";}San
R
20

The C++ String Toolkit Library (Strtk) has the following solution to your problem:

#include <iostream>
#include <string>
#include <deque>
#include <algorithm>
#include <iterator>

#include "strtk.hpp"

int main()
{
   std::string s = "1 23 456 7890";

   std::deque<int> int_list;
   strtk::parse(s," ",int_list);

   std::copy(int_list.begin(),
             int_list.end(),
             std::ostream_iterator<int>(std::cout,"\t"));

   return 0;
}

More examples can be found Here

Ramey answered 26/8, 2009 at 9:28 Comment(0)
S
11
#include <string>
#include <vector>
#include <iterator>
#include <sstream>
#include <iostream>

int main() {
   std::string input;
   while ( std::getline( std::cin, input ) )
   {
      std::vector<int> inputs;
      std::istringstream in( input );
      std::copy( std::istream_iterator<int>( in ), std::istream_iterator<int>(),
         std::back_inserter( inputs ) );

      // Log process: 
      std::cout << "Read " << inputs.size() << " integers from string '" 
         << input << "'" << std::endl;
      std::cout << "\tvalues: ";
      std::copy( inputs.begin(), inputs.end(), 
         std::ostream_iterator<int>( std::cout, " " ) );
      std::cout << std::endl;
   }
 }
Sparrow answered 24/8, 2009 at 8:42 Comment(12)
+1 once you replace 1000 "std::"s with a single "using namespace std;" and mention the headers you need.Facture
+1 as long as you keep the std:: prefixes. Their readability is subjective (I, being used to them, find it much better to read), but the enhanced clarity through fully qualified names is objective.Jacobina
+1 after replacing with 'using namespace'. Then you can replace implementation with some other, and dont need to cope with namespace changing.Glossographer
If you fear namespace pollution, enclose the code block with braces and use "using namespace std;" inside them. Best of both worlds?Facture
@Yossarian: I'd like to see the library that comes with getline, cin, vector, istringstream, copy, istream_iterator, back_inserter, endl, cout, and ostream_iterator -- all with the same signature, with similar, but slightly different semantics, so that replacing std with some other lib prefix actually gains something.Jacobina
@j_random_hacker: I don't actually fear namespace pollution. However, having each name fully qualified makes it easier to see where it comes from. ("Oh, this is a proprietary vectortemplate!") Many years ago, in a project I then worked on, we decided to fully qualify all names. It seemed hilarious back then, but became normal within maybe two weeks. I've been on both side of the argument and would never go back. Readability is a subjective question alterable by usage. Clarity is objective.Jacobina
Interesting perspective sbi. I've certainly become much more maintenance-centric with time, though I haven't gone so far as to favour this approach... yet. (Half-serious taunt: I would prefer to see "vector<vector<int> >" in a compiler error message, but I suppose you would prefer the more precise "std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > >"? :-P) Also just to clarify, it is abhorrent to "using namespace std;" at file scope in a header, so you must std::-qualify there in any case.Facture
Usually error messages are processed by the compiler and the compiler fully qualifies the types. I am usually explicit with respect of std:: namespace. As a matter of fact, only in a few cases I employ the using directive, in some cases create short aliases. Fully qualifying has the advantage of being easier on the casual reader, names as 'find', 'copy', can be common names for member/free functions. The casual reader will know directly when the symbol is part of STL or not. Then again, I don't have such a great drive not to change it besides having to do more editing to the answer :PPodvin
Well I'll +1 since you added the headers, but for the time being I remain firmly in the "std:: everywhere makes my eyes bleed" camp. It's almost <shudder> Perlish to look at. :-PFacture
@j_random_hacker: I would prefer to see my typedef name bla_foo_bar_data istead of std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > -- preferably with an option to expand that to the fully qualified identifier but with all the standard template parameters omitted. And as I said: Readability is a subjective thing and I believe that most programmers can get used to either way (and prefer that) within weeks. I used to be in your camp, but learned to like fully qualified names in two weeks. From that perspective, I wouldn't go back.Jacobina
@sbi: By fully qualify you surely mean ::std::vector<int>, right?Jacalynjacamar
"Readed" should be replaced with "Read" :)Contempt
B
4
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
using namespace std;

int ReadNumbers( const string & s, vector <int> & v ) {
    istringstream is( s );
    int n;
    while( is >> n ) {
        v.push_back( n );
    }
    return v.size();
}

int main() {
    string s;
    vector <int> v;
    getline( cin, s );
    ReadNumbers( s, v );
    for ( int i = 0; i < v.size(); i++ ) {
        cout << "number is " <<  v[i] << endl;
    }
}
Backpack answered 24/8, 2009 at 8:42 Comment(0)
B
3
// get string
std::string input_str;
std::getline( std::cin, input_str );

// convert to a stream
std::stringstream in( input_str );

// convert to vector of ints
std::vector<int> ints;
copy( std::istream_iterator<int, char>(in), std::istream_iterator<int, char>(), back_inserter( ints ) );
Blankly answered 24/8, 2009 at 10:51 Comment(1)
Or use direct initialization: std::vector<int> ints{std::istream_iterator<int, char>{in}, std::istream_iterator<int, char>{}};Alicia
H
1

Here is how to split your string into strings along the spaces. Then you can process them one-by-one.

Helping answered 24/8, 2009 at 8:37 Comment(0)
G
1

Generic solution for unsigned values (dealing with prefix '-' takes an extra bool):

template<typename InIter, typename OutIter>
void ConvertNumbers(InIter begin, InIter end, OutIter out)
{
    typename OutIter::value_type accum = 0;
    for(; begin != end; ++begin)
    {
        typename InIter::value_type c = *begin;
        if (c==' ') {
            *out++ = accum; accum = 0; break;
        } else if (c>='0' && c <='9') {
            accum *= 10; accum += c-'0';
        }
    }
    *out++ = accum;
       // Dealing with the last number is slightly complicated because it
       // could be considered wrong for "1 2 " (produces 1 2 0) but that's similar
       // to "1  2" which produces 1 0 2. For either case, determine if that worries
       // you. If so: Add an extra bool for state, which is set by the first digit,
       // reset by space, and tested before doing *out++=accum.
}
Gomez answered 24/8, 2009 at 10:53 Comment(3)
+1 since you answer the question as stated ("integers"), but I think this is actually a step in the less-generic direction in some respects, as it will clearly only work for integers and not other types (IOW: the "genericity" of "typename OutIter::value_type accum = 0;" is overstated). How would you handle floating point numbers for example? If you want to write another mini-lexer, don't forget to handle scientific notation and Inf, NaN etc.Facture
Well, it will accept short[], std::vector<int> and std::list<long> or in fact any other integral type (including implementation-defined types). Plain 0 will convert to all of those. The reason I used the valuetype there is so I won't accidentily overflow an int when the user passes as __int128[] and a string with numbers that big.Gomez
Using value_type for accum is absolutely the right way to go, but I guess I don't see why you couldn't just use an istringstream instead of your own handcrafted lexer. Then you could work with any type understood by operator<<() (i.e. improved "typewise" genericity) without sacrificing the "iteratorwise" genericity of your current solution. I also suspect that's more likely to work with wide chars, locales etc.Facture
N
0

Try strtoken to separate the string first, then you will deal with each string.

Noonday answered 24/8, 2009 at 8:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.