Как перебрать слова строки? - PullRequest
2822 голосов
/ 25 октября 2008

Я пытаюсь перебрать слова строки.

Можно предположить, что строка состоит из слов, разделенных пробелом.

Обратите внимание, что меня не интересуют строковые функции C или подобные манипуляции / доступ к символам Кроме того, в своем ответе просьба отдавать предпочтение элегантности, а не эффективности.

Лучшее решение, которое у меня есть сейчас:

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

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

Есть ли более элегантный способ сделать это?

Ответы [ 76 ]

2 голосов
/ 19 августа 2014

Вот моя версия

#include <vector>

inline std::vector<std::string> Split(const std::string &str, const std::string &delim = " ")
{
    std::vector<std::string> tokens;
    if (str.size() > 0)
    {
        if (delim.size() > 0)
        {
            std::string::size_type currPos = 0, prevPos = 0;
            while ((currPos = str.find(delim, prevPos)) != std::string::npos)
            {
                std::string item = str.substr(prevPos, currPos - prevPos);
                if (item.size() > 0)
                {
                    tokens.push_back(item);
                }
                prevPos = currPos + 1;
            }
            tokens.push_back(str.substr(prevPos));
        }
        else
        {
            tokens.push_back(str);
        }
    }
    return tokens;
}

Работает с многосимвольными разделителями. Это предотвращает попадание пустых токенов в ваши результаты. Он использует один заголовок. Возвращает строку как один токен, если вы не указали разделитель. Он также возвращает пустой результат, если строка пуста. К сожалению, это неэффективно из-за огромной std::vector копии UNLESS , которую вы компилируете с использованием C ++ 11, который должен использовать схему перемещения. В C ++ 11 этот код должен быть быстрым.

1 голос
/ 03 июня 2015

Мы можем использовать strtok в C ++,

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
    char str[]="Mickey M;12034;911416313;M;01a;9001;NULL;0;13;12;0;CPP,C;MSC,3D;FEND,BEND,SEC;";
    char *pch = strtok (str,";,");
    while (pch != NULL)
    {
        cout<<pch<<"\n";
        pch = strtok (NULL, ";,");
    }
    return 0;
}
1 голос
/ 02 ноября 2010

Цикл на getline с символом '' в качестве токена.

1 голос
/ 13 марта 2012

Мой код:

#include <list>
#include <string>
template<class StringType = std::string, class ContainerType = std::list<StringType> >
class DSplitString:public ContainerType
{
public:
    explicit DSplitString(const StringType& strString, char cChar, bool bSkipEmptyParts = true)
    {
        size_t iPos = 0;
        size_t iPos_char = 0;
        while(StringType::npos != (iPos_char = strString.find(cChar, iPos)))
        {
            StringType strTemp = strString.substr(iPos, iPos_char - iPos);
            if((bSkipEmptyParts && !strTemp.empty()) || (!bSkipEmptyParts))
                push_back(strTemp);
            iPos = iPos_char + 1;
        }
    }
    explicit DSplitString(const StringType& strString, const StringType& strSub, bool bSkipEmptyParts = true)
    {
        size_t iPos = 0;
        size_t iPos_char = 0;
        while(StringType::npos != (iPos_char = strString.find(strSub, iPos)))
        {
            StringType strTemp = strString.substr(iPos, iPos_char - iPos);
            if((bSkipEmptyParts && !strTemp.empty()) || (!bSkipEmptyParts))
                push_back(strTemp);
            iPos = iPos_char + strSub.length();
        }
    }
};

Пример:

#include <iostream>
#include <string>
int _tmain(int argc, _TCHAR* argv[])
{
    DSplitString<> aa("doicanhden1;doicanhden2;doicanhden3;", ';');
    for each (std::string var in aa)
    {
        std::cout << var << std::endl;
    }
    std::cin.get();
    return 0;
}
1 голос
/ 04 августа 2013

Я только что написал прекрасный пример того, как разделить символ на символ, который затем помещает каждый массив символов (слов, разделенных вашим символом) в вектор. Для простоты я сделал векторный тип строки std.

Надеюсь, это поможет вам и будет доступно для чтения.

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

void push(std::vector<std::string> &WORDS, std::string &TMP){
    WORDS.push_back(TMP);
    TMP = "";
}
std::vector<std::string> mySplit(char STRING[]){
        std::vector<std::string> words;
        std::string s;
        for(unsigned short i = 0; i < strlen(STRING); i++){
            if(STRING[i] != ' '){
                s += STRING[i];
            }else{
                push(words, s);
            }
        }
        push(words, s);//Used to get last split
        return words;
}

int main(){
    char string[] = "My awesome string.";
    std::cout << mySplit(string)[2];
    std::cin.get();
    return 0;
}
1 голос
/ 26 июля 2014
// adapted from a "regular" csv parse
std::string stringIn = "my csv  is 10233478 NOTseparated by commas";
std::vector<std::string> commaSeparated(1);
int commaCounter = 0;
for (int i=0; i<stringIn.size(); i++) {
    if (stringIn[i] == " ") {
        commaSeparated.push_back("");
        commaCounter++;
    } else {
        commaSeparated.at(commaCounter) += stringIn[i];
    }
}

в конце у вас будет вектор строк с каждым элементом в предложении, разделенным пробелами. std :: vector является единственным нестандартным ресурсом (но поскольку задействована std :: string, я решил, что это будет приемлемо).

пустые строки сохраняются как отдельные элементы.

1 голос
/ 28 июля 2013

Я думаю, что никто еще не опубликовал это решение. Вместо непосредственного использования разделителей, он в основном делает то же самое, что и boost :: split (), то есть он позволяет передавать предикат, который возвращает true, если символ является разделителем, и false в противном случае. Я думаю, что это дает программисту намного больше контроля, и самое замечательное в том, что вам не нужно ускорение.

template <class Container, class String, class Predicate>
void split(Container& output, const String& input,
           const Predicate& pred, bool trimEmpty = false) {
    auto it = begin(input);
    auto itLast = it;
    while (it = find_if(it, end(input), pred), it != end(input)) {
        if (not (trimEmpty and it == itLast)) {
            output.emplace_back(itLast, it);
        }
        ++it;
        itLast = it;
    }
}

Тогда вы можете использовать это так:

struct Delim {
    bool operator()(char c) {
        return not isalpha(c);
    }
};    

int main() {
    string s("#include<iostream>\n"
             "int main() { std::cout << \"Hello world!\" << std::endl; }");

    vector<string> v;

    split(v, s, Delim(), true);
    /* Which is also the same as */
    split(v, s, [](char c) { return not isalpha(c); }, true);

    for (const auto& i : v) {
        cout << i << endl;
    }
}
1 голос
/ 16 мая 2017

На основании ответа Галика Я сделал это. Это в основном здесь, поэтому мне не нужно продолжать писать это снова и снова. Это безумие, что в C ++ до сих пор нет встроенной функции split. Особенности:

  • Должно быть очень быстро.
  • Легко понять (я думаю).
  • Объединяет пустые секции.
  • Тривиально использовать несколько разделителей (например, "\r\n")
#include <string>
#include <vector>
#include <algorithm>

std::vector<std::string> split(const std::string& s, const std::string& delims)
{
    using namespace std;

    vector<string> v;

    // Start of an element.
    size_t elemStart = 0;

    // We start searching from the end of the previous element, which
    // initially is the start of the string.
    size_t elemEnd = 0;

    // Find the first non-delim, i.e. the start of an element, after the end of the previous element.
    while((elemStart = s.find_first_not_of(delims, elemEnd)) != string::npos)
    {
        // Find the first delem, i.e. the end of the element (or if this fails it is the end of the string).
        elemEnd = s.find_first_of(delims, elemStart);
        // Add it.
        v.emplace_back(s, elemStart, elemEnd == string::npos ? string::npos : elemEnd - elemStart);
    }
    // When there are no more non-spaces, we are done.

    return v;
}
1 голос
/ 03 октября 2015

Вот моя запись:

template <typename Container, typename InputIter, typename ForwardIter>
Container
split(InputIter first, InputIter last,
      ForwardIter s_first, ForwardIter s_last)
{
    Container output;

    while (true) {
        auto pos = std::find_first_of(first, last, s_first, s_last);
        output.emplace_back(first, pos);
        if (pos == last) {
            break;
        }

        first = ++pos;
    }

    return output;
}

template <typename Output = std::vector<std::string>,
          typename Input = std::string,
          typename Delims = std::string>
Output
split(const Input& input, const Delims& delims = " ")
{
    using std::cbegin;
    using std::cend;
    return split<Output>(cbegin(input), cend(input),
                         cbegin(delims), cend(delims));
}

auto vec = split("Mary had a little lamb");

Первое определение - обобщенная функция в стиле STL, принимающая две пары итераторов. Вторая - это удобная функция, которая избавляет вас от необходимости делать все begin() s и end() s самостоятельно. Вы также можете указать тип выходного контейнера в качестве параметра шаблона, если вы хотите использовать, например, list.

Что делает его элегантным (IMO), так это то, что в отличие от большинства других ответов, он не ограничивается строками, но будет работать с любым STL-совместимым контейнером. Без каких-либо изменений в коде выше, вы можете сказать:

using vec_of_vecs_t = std::vector<std::vector<int>>;

std::vector<int> v{1, 2, 0, 3, 4, 5, 0, 7, 8, 0, 9};
auto r = split<vec_of_vecs_t>(v, std::initializer_list<int>{0, 2});

, который будет разбивать вектор v на отдельные векторы каждый раз, когда встречается 0 или 2.

(Существует также дополнительный бонус, заключающийся в том, что со строками эта реализация быстрее, чем обе версии на основе strtok() - и getline(), по крайней мере, в моей системе.)

1 голос
/ 10 сентября 2016

Вот мой взгляд на это. Мне приходилось обрабатывать входную строку слово за словом, что можно было сделать, используя пробел для подсчета слов, но я чувствовал, что это будет утомительно, и я должен разбить слова на векторы.

#include<iostream>
#include<vector>
#include<string>
#include<stdio.h>
using namespace std;
int main()
{
    char x = '\0';
    string s = "";
    vector<string> q;
    x = getchar();
    while(x != '\n')
    {
        if(x == ' ')
        {
            q.push_back(s);
            s = "";
            x = getchar();
            continue;
        }
        s = s + x;
        x = getchar();
    }
    q.push_back(s);
    for(int i = 0; i<q.size(); i++)
        cout<<q[i]<<" ";
    return 0;
}
  1. Не заботится о нескольких пробелах.
  2. Если за последним словом не сразу следует символ новой строки, он включает пробел между последним символом последнего слова и символом новой строки.
...