15

I am new to programming. I have been trying to write a function in C++ that explodes the contents of a string into a string array at a given parameter, example:

string str = "___this_ is__ th_e str__ing we__ will use__";

should return string array:

cout << stringArray[0]; // 'this'
cout << stringArray[1]; // ' is'
cout << stringArray[2]; // ' th'
cout << stringArray[3]; // 'e str'
cout << stringArray[4]; // 'ing we'
cout << stringArray[5]; // ' will use'

I can tokenize the string just fine, but the hardest part for me is how can i specify the number of elements in stringArray before assigning it the current string toke and also how to return stringArray from the function.

Would someone show me how to write the function?

Edit1: I don't necessarily need the results to been in string array just any container that i can call as a regular variable with some sort of indexing.

outis
  • 75,655
  • 22
  • 151
  • 221
Babiker
  • 18,300
  • 28
  • 78
  • 125

13 Answers13

16

Here's my first attempt at this using vectors and strings:

vector<string> explode(const string& str, const char& ch) {
    string next;
    vector<string> result;

    // For each character in the string
    for (string::const_iterator it = str.begin(); it != str.end(); it++) {
        // If we've hit the terminal character
        if (*it == ch) {
            // If we have some characters accumulated
            if (!next.empty()) {
                // Add them to the result vector
                result.push_back(next);
                next.clear();
            }
        } else {
            // Accumulate the next character into the sequence
            next += *it;
        }
    }
    if (!next.empty())
         result.push_back(next);
    return result;
}

Hopefully this gives you some sort of idea of how to go about this. On your example string it returns the correct results with this test code:

int main (int, char const **) {
    std::string blah = "___this_ is__ th_e str__ing we__ will use__";
    std::vector<std::string> result = explode(blah, '_');

    for (size_t i = 0; i < result.size(); i++) {
        cout << "\"" << result[i] << "\"" << endl;
    }
    return 0;
}
paddy
  • 60,864
  • 6
  • 61
  • 103
Eric Scrivner
  • 1,839
  • 1
  • 18
  • 23
  • 1
    The first parameter to explode() should be a constant reference. Compiler will then complain, so 'it' needs to be a string::const_iterator. – rtn May 20 '09 at 21:13
  • 1
    You should check if `next` is not empty and if so append it to the results AFTER THE LOOP. Otherwise, the last element after the explode char will not be contained. – ddinchev Oct 11 '12 at 15:45
  • 2
    The last element is ignored! I suggested a solution below, it works! – Ouadie Nov 26 '12 at 01:26
  • @Veseliq I edited the comment and added the if statement - once my edit gets approved it'll show up. – Natalie Adams Feb 27 '13 at 04:33
11

Using STL (sorry no compiler not tested)

#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::vector<std::string>   result;

    std::string str = "___this_ is__ th_e str__ing we__ will use__";

    std::stringstream  data(str);

    std::string line;
    while(std::getline(data,line,'_'))
    {
        result.push_back(line); // Note: You may get a couple of blank lines
                                // When multiple underscores are beside each other.
    }
}

// or define a token

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

struct Token: public std::string  // Yes I know this is nasty.
{                                 // But it is just to demosntrate the principle.    
};

std::istream& operator>>(std::istream& s,Token& t)
{
    std::getline(s,t,'_');
   
    // *** 
    // Remove extra '_' characters from the stream.
    char c;
    while(s && ((c = s.get()) != '_')) {/*Do Nothing*/}
    if (s)
    {
        s.unget(); // Put back the last char as it is not '_'
    }
    return s;
}

int main()
{   

    std::string str = "___this_ is__ th_e str__ing we__ will use__";

    std::stringstream  data(str);

    std::vector<std::string>   result(std::istream_iterator<Token>(data),
                                      std::istream_iterator<Token>());
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
3

It works for me :

#include <iostream>
#include <vector>
#include <string>

using namespace std;

vector<string> explode( const string &delimiter, const string &explodeme);

int main(int argc, char *argv[])
{
    string str = "I have a lovely bunch of cocoa nuts";
    cout<<str<<endl;
    vector<string> v = explode(" ", str);
    for(int i=0; i<v.size(); i++)
        cout <<i << " ["<< v[i] <<"] " <<endl;
}

vector<string> explode( const string &delimiter, const string &str)
{
    vector<string> arr;

    int strleng = str.length();
    int delleng = delimiter.length();
    if (delleng==0)
        return arr;//no change

    int i=0;
    int k=0;
    while( i<strleng )
    {
        int j=0;
        while (i+j<strleng && j<delleng && str[i+j]==delimiter[j])
            j++;
        if (j==delleng)//found delimiter
        {
            arr.push_back(  str.substr(k, i-k) );
            i+=delleng;
            k=i;
        }
        else
        {
            i++;
        }
    }
    arr.push_back(  str.substr(k, i-k) );
    return arr;
}

source : http://www.zedwood.com/article/106/cpp-explode-function

Ouadie
  • 13,005
  • 4
  • 52
  • 62
1

If you insist on making stringArray an array as oppossed to a std::vector<> (which would be the right thing to do) you have to either:

  1. Make two passes (one to count, you see)
  2. Implement a dynamic array yourself.

Using a vector is easier vector::push_back() appends new stuff to the end. So:

vector* explode(string s){
  vector<string> *v = new vector<string>
  //...
  // in a loop
    v->push_back(string_fragment);
  //...
  return v;
}

Not needed after all Left in for completeness.

To return the array of strings you use char **.

As in

char ** explode(const char *in){
  ...

}

BTW-- How will the calling function know how many elements are in the returned array? You'll have to solve that too. Use std::vector<> unless you are constrained by outside forces...

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
1

You can use a vector of string (std::vector<std::string>), append each token to it with push_back, and then return it from your tokenize function.

Jem
  • 2,255
  • 18
  • 25
1

Use std::vector as a dynamic array and return that as your result.

workmad3
  • 25,101
  • 4
  • 35
  • 56
1

Perhaps you should use a list instead of an array. That way you would not need to know the number of elements ahead of time. You may also consider using the STL containers.

user79755
  • 2,623
  • 5
  • 30
  • 36
0

Here is my cooked up code (complete). May be it's useful for some with the same need.

#include <string>
#include <iostream>
#include <sstream>
#include <vector>
using namespace std;

int main(){
        std::string s = "scott:tiger:mushroom";
        std::string delimiter = ":";

        std::vector<std::string> outputArr;
        size_t pos = 0;
        std::string token;
        while ((pos = s.find(delimiter)) != std::string::npos) {
            token = s.substr(0, pos);
            s.erase(0, pos + delimiter.length());
            outputArr.push_back(token);
        }
        outputArr.push_back(s);

        // Printing Array to see the results
        std::cout<<"====================================================================================\n";
        for ( int i=0;i<outputArr.size();i++){
                std::cout<<outputArr[i]<<"\n";
        }
        std::cout<<"====================================================================================\n";
}

Cheers!!

Vivek
  • 3,523
  • 2
  • 25
  • 41
0

I think that I have wrote a much simpler solution.

std::vector<std::string> explode(const std::string& string, const char delimiter) {

std::vector<std::string> result;
unsigned int start = 0, pos = 0;

while (pos != string.length()) {
    if (string.at(pos) == delimiter || pos + 1 == string.length()) {
        unsigned int size = (pos - start) + ((pos + 1) == string.length() ? 1 : 0);
        if (size != 0) { // Make this 'if' as a option? like a parameter with removeEmptyString?
            result.push_back(string.substr(start, size));
        }
        start = pos + 1;
    }
    pos++;
}

return std::move(result);

}

InusualZ
  • 49
  • 1
  • 5
0

The code below:

template <typename OutputIterator>
int explode(const string &s, const char c, OutputIterator output) {
    stringstream  data(s);
    string line;
    int i=0;
    while(std::getline(data,line,c)) { *output++ = line; i++; }
    return i;
}

int main(...) {
  string test="H:AMBV4:2:182.45:182.45:182.45:182.45:182.41:32:17700:3229365:201008121711:0";
  cout << test << endl; 
  vector<string> event;
**This is the main call**
  int evts = explode(test,':', back_inserter(event));
  for (int k=0; k<evts; k++) 
    cout << event[k] << "~";
  cout << endl;
}

Outputs

H:AMBV4:2:182.45:182.45:182.45:182.45:182.41:32:17700:3229365:201008121711:0
H~AMBV4~2~182.45~182.45~182.45~182.45~182.41~32~17700~3229365~201008121711~0~
Cosmin
  • 21,216
  • 5
  • 45
  • 60
0

This worked for me:

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

using namespace std;

vector<string> split(string str, char delimiter) {
  vector<string> internal;
  stringstream ss(str); // Turn the string into a stream.
  string tok;

  while(getline(ss, tok, delimiter)) {
    internal.push_back(tok);
  }

  return internal;
}

int main(int argc, char **argv) {
  string myCSV = "one,two,three,four";
  vector<string> sep = split(myCSV, ',');

  // If using C++11 (which I recommend)
  /* for(string t : sep)
   *  cout << t << endl;
   */

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

Source: http://code.runnable.com/VHb0hWMZp-ws1gAr/splitting-a-string-into-a-vector-for-c%2B%2B

Mukesh Chapagain
  • 25,063
  • 15
  • 119
  • 120
0
# turn a string into a deque based on a delimiter string
bool tolist(deque<string>& list,string basis,const string& cutter) {
    bool found = false;
    if (!cutter.empty()) {
        while (!basis.empty() ) {
            string::size_type pos = basis.find(cutter);
            if (pos != string::npos) {
                found = true;
                list.push_back(basis.substr(0, pos)); //pos says 2
                basis = basis.substr(pos+cutter.size(),string::npos);
            } else {
                list.push_back(basis);
                basis.clear();
            }
        }
    }
    return found;
}
Konchog
  • 1,920
  • 19
  • 23
0

Wait until your data structures class and then code it with a linked list. If it is for homework though, you may be able to get away with just initing the array be very large.

JasonRShaver
  • 4,344
  • 3
  • 32
  • 39