Search code examples
c++delimiterstringstream

stringstream with two delimiters


I am trying to take a string and delimit it first using the first delimiter and then using the second one.I am trying to minimize resources used and to avoid unnecessary loops (then again I am not sure how many of them might be necessary). I am also fairly new C++ programmer. The code I am currently using is shown in the following function.

vector<string> dualDelimit(string str,char del1,char del2)
{
    vector<string> sub;
    stringstream ss(str);
    stringstream ss2;
    while(ss.good())
    {
        string substring;
        getline(ss,substring,del1);

        ss2<<substring;
        while(ss2.good())
        {
            getline(ss2,substring,del2);
            sub.push_back(substring);
        }
    }
    return sub;
}

So I also use the following method to get input, call dualDelimit and then print out its contents

void delimitTest()
{
    //variables
    string input;
    char c;
    char d;
    vector<string> result;

    //main program
    cout<<"Insert the string to be delimited: ";
    getline(cin,input);
    cout<<"Insert the delimiter characters separated by whitespace:\t";
    cin>>c>>d;


    result=dualDelimit(input,c,d);

    for(int i=0;i<result.size();i++)
    {
        cout<<result[i]<<endl;
    }
}

So let's say I use the following data:

Insert the string to be delimited: This|is;a|test.
Insert two delimiter characters separated by whitespace:    ; |

The result printed and stored in result vector is only:

This
is

It appears that the first string that is taken from ss is then delimited with the second delimiter and its substrings are added to the vector normally yet it appears that the first while stops and does not pass the rest of the strings. What is causing this behaviour? When and how does the first while of the function break?


Solution

  • I've been using this for quite a while and it works great.

    Utility.h

    #ifndef UTILITY_H
    #define UTILITY_H
    
    class Utility {
    public:
        Utility() = delete;
        Utility( const Utility& ) = delete;
        Utility& operator=( const Utility& ) = delete;
    
        static std::string trim( const std::string& str, 
                                 const std::string elementsToTrim = " \t\n\r" ); 
    
        static std::vector<std::string> splitString( const std::string& stringToSplit, 
                                                     const std::string& strDelimiter, 
                                                     const bool keepEmpty = true );    
    };
    
    #endif // !UTILITY_H
    

    Utlity.cpp

    #include "Utility.h"
    
    #include <vector>
    #include <string>
    #include <algorithm>
    
    std::string Utility::trim( const std::string& str, 
                              const std::string elementsToTrim ) {
        std::basic_string<char>::size_type firstIndex = str.find_first_not_of( elementsToTrim );
        if ( firstIndex == std::string::npos )
            return std::string(); // Nothing Left    
        std::basic_string<char>::size_type lastIndex = str.find_last_not_of( elementsToTrim );
        return str.substr( firstIndex, lastIndex - firstIndex + 1 );
    }
    
    std::vector<std::string> Utility::splitString( const std::string& strStringToSplit, 
                                                   const std::string& strDelimiter, 
                                                   const bool keepEmpty ) {
        std::vector<std::string> vResult;
        if ( strDelimiter.empty() ) {
            vResult.push_back( strStringToSplit );
            return vResult;
        }    
        std::string::const_iterator itSubStrStart = strStringToSplit.begin(), itSubStrEnd;
        while ( true ) {
            itSubStrEnd = search( itSubStrStart, strStringToSplit.end(), strDelimiter.begin(), strDelimiter.end() );
            std::string strTemp( itSubStrStart, itSubStrEnd );
            if ( keepEmpty || !strTemp.empty() )
                vResult.push_back( strTemp );
            if ( itSubStrEnd == strStringToSplit.end() )
                break;
            itSubStrStart = itSubStrEnd + strDelimiter.size();
        }    
        return vResult;    
    }
    

    main.cpp

    #include <iostream>
    #include <vector>
    #include <string>
    
    #include "Utility.h"
    
    int main() {
        std::vector<std::tring> result;
        std::string str( "This|is;a|test." );
        std::cout << str << std::endl;
    
        result = Utility::splitString( str, ";" );
    
        str.clear();
        for ( auto& s : result )
            str += s + " ";
        std::cout << str << std::endl;
    
        result.clear();
        result = Utility::splitString( str, "|" );
    
        str.clear();
        for ( auto& s : result )
            str += s + " ";
        std::cout << str << std::endl;
    
        system( "PAUSE" );
        return 0;
    }
    

    What makes this splitString function so nice is that lets say I have a pattern of strings, and I want to eliminate sets of characters within a string as such such; you can reuse the same vector and string in the main above and run this:

    {
        // [...]
    
        str.clear();
        result.clear();
    
        str = std::string( "cruelcruelhellocruelcruelmadryochcruel" );
        result = Utility::splitString( str,  "cruel" );
        str.clear();
        for ( auto& s : result )
            str += s + " ";
        str = Utility::trim( str );
        std::cout << str << std::endl;
    
        system( "PAUSE" );
        return 0;
    }
    

    It can eliminate sets of characters or sets of strings within a string. Quick Examples - Results.

    test string - wellaaabbbdone

    splitString( s, "a" )   = "well   bbbdone"
                   "aa"     = "well abbbdone"
                   "aaa"    = "well bbbdone";
                   "ab"     = "wellaa bbdone";      
                   "aabb"   = "wella bdone";
                   "aaabbb" = "well done";
    // without calling trim.* This may not be accurate; I did add it & removed it
    // several times; but you should still get the idea.
    

    You could easily substitute a stringstream into this splitString() algorithm and just use its str() function where needed.


    EDIT - User: Christian Hackl was complaining about my use of containing all related static functions within a class and said to use a namespace instead because this is C++ and not Java. Personally I do not see what the big deal is, but if it is of concern you can remove the wrapping class Utility and place all of the commonly related standalone functions in a namespace as such:

    // Utility.h
    #ifndef UTILITY_H
    #define UTILITY_H
    
    namespace util {
        // function declarations;
    
    } // namespace util 
    
    #endif // !UTILITY_H 
    
    //===================================
    
    // Utility.cpp
    #include "Utility.h"
    // One of tree ways here:
    
    // First:
    using namespace util;
    
    // function definitions;
    
    // Second:
    util::function definitions
    
    // Third: 
    namespace util {
        // function definitions
    } // namespace util
    

    Final Note:

    In my original class Utility above with the contained static methods for working with strings I have them in a class for a specific reason instead of just stand alone functions residing in a namespace.

    User – Christian Hackl stated:

    "There is no object of Util ever created; it sort of acts as a namespace." - Then use an actual namespace. As I said, this isn't Java.

    I will object to that. Do not get me wrong; namespaces are an important and vital part of C++ and should be used accordingly where appropriate. However, there are some cases where a namespace will not suffice and the needed mechanics of the language and compiler requires the form of a class or structure instead. What do I mean by this? It is a slightly more advanced topic but it is quite simple. You see, in my class above that I provided; I only showed a couple of functions from this class. I have about a dozen other functions and some of them are function templates. So, the same argument can be said about having stand a lone function templates in a namespace. This is not the situation with my class. My class has a couple of private function templates that are used to take a string as input and converts it to default basic types (int, unsigned, float, double, etc.). I even have functions that will convert a string to glm::vec2, vec3, vec4, glm::ivec2, ivec3, ivec4, glm::mat3, glm::mat4 and these follow the same convention.

    These conversion functions call a function template that gets the value of that type but it relies on function template specialization; and this can not be done in a namespace. If you try to do this without a class or struct; the code will compile but you will get linker errors because a template's parameter list can not accept a namespace; but it can accept a class, struct, or integral type.

    Here is a pseudo example:

    {    // header file
         ...
         static int converToInt( const std::string& str );
         static float convertToFloat( const std::string& str );
    private:
         template<typename T>
         bool hasValue( const std::string& str, T* pValue );
    
         template<typename T>
         T getValue( const std::string );
    }
    
    
    // *.inl file
    template<typename T>
    bool Utility::hasValue( const std::string& str, T* pValue ) {
        // string manipulation
        pValue[0] = getValue<T>( str );
    
        // check some condition return true or false
    }
    
    // *.cpp file
    int convertToInt( const std::string& str ) {
       // return converted int from string
    }
    
    float convertToFloat( const std::string& str ) {
       // return converted float from string
    }
    
    template<>
    int getValue( const std::string& ) {
        return int that was converted from (convertToInt)
    }
    
    template<>
    float getValue( const std::string& ) {
        return float that was converted from (convertToFloat)
    }
    

    You can not do this outside of a class and just solely in a namespace. So that is why my functions above in the original demonstration are static methods in a non constructible class.

    The reason why this kind of pattern is needed here is that the getValue() function takes a string as its parameter in all cases, however the only difference is the return type. In C++ and most other languages overload resolution can not be performed on the return type alone. It can end up being an ambiguous call, or if the return type is never used; the compiler may not even call that function to begin with. So in order to simulate this; is where the functions need to be a template type; and they need to be contained with in a class in order to specialize those functions, otherwise you can not specialize function templates that are just solely in a namespace.


    The whole comment about this is C++ and not Java is truly a bad assessment. Why? Not to put the user down in any way shape or form, but the statement appears to be completely biased. First; putting static methods into a struct or class without any members and making the constructor private is a completely legal and valid piece of C++ code.

    It has nothing to do with Java what so ever; and frankly I've never programmed any thing in Java. I've only ever programmed in C and C++. I've done maybe a couple of tiny applications many years back in visual basic and C# when I was in high school in the late 90s but for almost the past 2 decades its been 95% all C++. And you are right this is C++ and not Java.

    Welcome to the world of template programming with polymorphic behavior, recurring templates, template meta-programming, generic programing while still supporting the roles of being both a Procedural and an Object Oriented Paradigm in a statically typed Language that now supports richer features such as auto type deduction, lambdas, ranged-based loops, variadic templates, and so much more...!