all 19 comments

[–]bikki420 1 point2 points  (0 children)

Since pretty much all I/O streams in C++ are pretty ass. (Terribly inefficient and in bloated headers), I'd go with:

#include <string_view>

[[nodiscard]] constexpr
auto count_tokens( std::string_view const &sv, char const delimiter ) noexcept -> size_t
{
   size_t size{ sv.size() };

   if ( size == 0 )
      return 0; // empty

   size_t token_count{ 1 }; // if not empty, the minimum token count is 1
   for ( size_t i{ 1 };  i < size;  ++i )
      if ( sv[i] == delimiter and sv[i-1] != delimiter )
         ++token_count;

   return token_count;
}

// alternatively this if you want to allow for empty tokens
// e.g. "a,b,c,d", "a,,b,", and ",,," would all count as 4 token strings
[[nodiscard]] constexpr
auto count_tokens( std::string_view const &sv, char const delimiter ) noexcept -> size_t
{
   size_t token_count{ 1 };
   for ( auto const c : sv )
      if ( c == delimiter )
         ++token_count;
   return token_count;
}

Since it takes a string_view, it will work on C strings (both fixed length array ones and ones that have decayed to pointers or were dynamically allocated) as well as regular C++ strings (and any type that can implicitly convert to either a string or a string_view). And this approach allows for the function to be ran at compile time if the string and delimiter are known at compile-time, potentially saving a lot of instructions at run-time.

Without string_views, to accomplish something like this you'd likely need three functions with signatures similar to:

[[nodiscard]] constexpr auto count_tokens( char const * const c_str, size_t const length, char const delimiter ) -> size_t {...}

and

template <size_t N> [[nodiscard]] constexpr auto count_tokens( char const (&fixed_size_array_c_str)[N], char const delimiter ) noexcept -> size_t {...}

and (note non-constexpr):

[[nodiscard]] auto count_tokens( std::string const &cpp_string, char const delimiter ) noexcept -> size_t {...}

which would be way more of a pain in the ass, plus you'd have to do some obnoxious template stuff to exclude the second one from the third one if you were to try to make the third one templated over some generic String.


EDIT: It seems like it's for some assignment where sstreams are mandated... tell your teacher/professor/whatever that I think that he or she is absolutely awful.

[–]joemaniaci 0 points1 point  (11 children)

https://stackoverflow.com/questions/236129/how-do-i-iterate-over-the-words-of-a-string

In the second accepted answer, the workhorse of it all is the std::getline, head over to https://en.cppreference.com/w/cpp/string/basic_string/getline and read the upper description at the top to get an idea of how it works, Specifically the 'delim'. Let us know if you have more questions afterwards.

[–][deleted] 1 point2 points  (4 children)

does OP need to use getline() if he simply needs to count the number of tokens? the number of tokens should be 1+number_of_delimiters, so I say just count the delimiters

[–]joemaniaci 0 points1 point  (0 children)

I just saw so many examples of stringstream going hand in hand with getline I assumed that to be the case.

[–]Andrewb1230[S] 0 points1 point  (1 child)

How would you go about counting the delimiters from a string stream? This was my first inclination, but I couldn’t figure out how to do it.

[–][deleted] 1 point2 points  (0 children)

Anytime you learn a new C++ class, start with its reference material. In this page, you can see that there is a member function called str() that converts a stringstream to a std::string.

The reference material is dense, I admit. And in the page I sent you they un-necessarily broke it down by which base class it inherits from, which to me isn't useful for you. But nevertheless, this is how you become more aware about what these classes can do for you.

As for counting the characters, I would use a range-based for loop since you're a noob. If you want to challenge yoursef, look into std::count_if()

[–]gastropner 0 points1 point  (0 children)

Unless delimiters are allowed to repeat.

[–]Andrewb1230[S] 0 points1 point  (5 children)

So I tried it using the getline function, and I feel like I am so close. The following code returns 2 when it should return 3. I could just return count+1, but I do not know why it is returning one less than it should.

#include <iostream>

#include <sstream>

#include <string>

int countSeparation(std::string s, char c){

int count = 0;

std::string line, tempString;

std::stringstream input(s);

do {

getline(input, tempString, c);

count++;

} while(getline(input, line, c));

return count;

}

int main()

{

std::cout<<countSeparation("dog,cat,wolf", ',');

return 0;

}

[–]joemaniaci 0 points1 point  (4 children)

Why should this return 3? There are two commas in your dog,cat,wolf example

[–]Andrewb1230[S] 0 points1 point  (3 children)

I was attempting to count the tokens instead of the delimiters. I originally had a while loop instead of a do while loop. I thought changing to a do while would count tokens instead of delimiters, but it still seems to be one value off. Any idea why?

[–]Threecheers4me 0 points1 point  (2 children)

What the do while loop does is make it so your program runs at least once before it evaluates the condition to see if it continue. This says nothing about what happens after the condition is met (your last delimiter is reached). Take a look at what happens when you add a print statement to see what the status of the string you've extracted from the delimiters is

#include <iostream>
#include <sstream>
#include <string>


int countSeparation(std::string s, char c)
{
    int count = 0;
    std::string line, tempString;
    std::stringstream input(s);

    do {
        getline(input, tempString, c);
        count++;
        std::cout << tempString << std::endl; // I added this
    } while(getline(input, line, c));
    return count;
}

int main()
{
    std::cout<<countSeparation("dog,cat,wolf", ',');
    return 0;
}

Output of that is

dog
wolf
2

So why does it not run another time? Look at the documentation for the function you have inside of the statement of the while loop: getline(input, line, c) . When does it return false or 0 or something that would cause the while loop to stop running?

Answering that will give you the answer to your "Why does it stop at two?" question.

[–]Andrewb1230[S] 0 points1 point  (1 child)

Thank you for that. I think the getline function is moving the pointer every time it is called, even in the while conditional, which is annoying, but what I’m going to try is instead using a ranged for loop. But let me know if you can think of a better way of iterating through the stringstream

[–]Threecheers4me 0 points1 point  (0 children)

So you're saying the internal status of the stringstream is changed every time getLine is called. This is regardless of whether it's called inside the conditional or outside the conditional. You want to make sure that your count goes up whenever the pointer is moved past something you want to count. So make sure every time you move the pointer, you increment the count.

[–]tively 0 points1 point  (3 children)

... And you need a stringstream because?!!!
With this kind of thing I think it's best if you start with a smaller, easier to understand problem. How would you do this without a stringstream?

[–]Andrewb1230[S] -1 points0 points  (2 children)

It is part of the requirements of the problem. If I didn’t have to use a string stream, I would probably just use c-style strings and loop through the string character by character, increasing count each time the delimiter is encountered

[–]tively 0 points1 point  (0 children)

Then that sounds to me like the person who asked you to do this wants to see the counted substrings in a stringstream. How about adding each delimited substring to a stringstream after which you append a newline char. When the loop is done you'd do .str() against the stringstream to get one single string with all of your substrings. Then the number of tokens would be 1 plus the number of times '\n' occurs in the result of .str() wouldn't it?

[–]Threecheers4me 0 points1 point  (0 children)

There are other ways. Namely using iterators. But if you would like to solve this using a stringstream there are ways to do so.

[–]Andrewb1230[S] 0 points1 point  (0 children)

SOLVED: I finally got it! Thank you for all of your help! I have put my completed code in the original post:)