Как перебрать слова строки? - 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 ]

3 голосов
/ 10 января 2019

Использование std::string_view и библиотеки Эрика Ниблера range-v3:

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Используя цикл for вместо алгоритма ranges::for_each:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}
3 голосов
/ 22 февраля 2012

Быстрая версия, которая использует vector в качестве базового класса, предоставляя полный доступ ко всем его операторам:

    // Split string into parts.
    class Split : public std::vector<std::string>
    {
        public:
            Split(const std::string& str, char* delimList)
            {
               size_t lastPos = 0;
               size_t pos = str.find_first_of(delimList);

               while (pos != std::string::npos)
               {
                    if (pos != lastPos)
                        push_back(str.substr(lastPos, pos-lastPos));
                    lastPos = pos + 1;
                    pos = str.find_first_of(delimList, lastPos);
               }
               if (lastPos < str.length())
                   push_back(str.substr(lastPos, pos-lastPos));
            }
    };

Пример, используемый для заполнения набора STL:

std::set<std::string> words;
Split split("Hello,World", ",");
words.insert(split.begin(), split.end());
2 голосов
/ 08 апреля 2014

Без Boost, без потоков строк, только стандартная библиотека C, взаимодействующая с std::string и std::list: функции библиотеки C для простого анализа, типы данных C ++ для простого управления памятью.

Пробелами считается любая комбинация символов новой строки, табуляции и пробелов. Набор пробельных символов устанавливается переменной wschars.

#include <string>
#include <list>
#include <iostream>
#include <cstring>

using namespace std;

const char *wschars = "\t\n ";

list<string> split(const string &str)
{
  const char *cstr = str.c_str();
  list<string> out;

  while (*cstr) {                     // while remaining string not empty
    size_t toklen;
    cstr += strspn(cstr, wschars);    // skip leading whitespace
    toklen = strcspn(cstr, wschars);  // figure out token length
    if (toklen)                       // if we have a token, add to list
      out.push_back(string(cstr, toklen));
    cstr += toklen;                   // skip over token
  }

  // ran out of string; return list

  return out;
}

int main(int argc, char **argv)
{
  list<string> li = split(argv[1]);
  for (list<string>::iterator i = li.begin(); i != li.end(); i++)
    cout << "{" << *i << "}" << endl;
  return 0;
}

Пробег:

$ ./split ""
$ ./split "a"
{a}
$ ./split " a "
{a}
$ ./split " a b"
{a}
{b}
$ ./split " a b c"
{a}
{b}
{c}
$ ./split " a b c d  "
{a}
{b}
{c}
{d}

Хвосто-рекурсивная версия split (сама разбита на две функции). Все разрушительные манипуляции с переменными исчезли, за исключением добавления строк в список!

void split_rec(const char *cstr, list<string> &li)
{
  if (*cstr) {
    const size_t leadsp = strspn(cstr, wschars);
    const size_t toklen = strcspn(cstr + leadsp, wschars);

    if (toklen)
      li.push_back(string(cstr + leadsp, toklen));

    split_rec(cstr + leadsp + toklen, li);
  }
}

list<string> split(const string &str)
{
  list<string> out;
  split_rec(str.c_str(), out);
  return out;
}
2 голосов
/ 17 марта 2014

Я искал способ разбить строку на разделитель любой длины, поэтому я начал писать ее с нуля, так как существующие решения меня не устраивали.

Вот мой маленький алгоритм, использующий только STL:

//use like this
//std::vector<std::wstring> vec = Split<std::wstring> (L"Hello##world##!", L"##");

template <typename valueType>
static std::vector <valueType> Split (valueType text, const valueType& delimiter)
{
    std::vector <valueType> tokens;
    size_t pos = 0;
    valueType token;

    while ((pos = text.find(delimiter)) != valueType::npos) 
    {
        token = text.substr(0, pos);
        tokens.push_back (token);
        text.erase(0, pos + delimiter.length());
    }
    tokens.push_back (text);

    return tokens;
}

Насколько я тестировал, его можно использовать с разделителем любой длины и формы. Создание экземпляра с типом string или wstring.

Все, что делает алгоритм, - это ищет разделитель, получает часть строки, которая соответствует разделителю, удаляет разделитель и ищет снова, пока не найдет его.

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

Надеюсь, это поможет.

2 голосов
/ 08 марта 2012

Я использую следующее

void split(string in, vector<string>& parts, char separator) {
    string::iterator  ts, curr;
    ts = curr = in.begin();
    for(; curr <= in.end(); curr++ ) {
        if( (curr == in.end() || *curr == separator) && curr > ts )
               parts.push_back( string( ts, curr ));
        if( curr == in.end() )
               break;
        if( *curr == separator ) ts = curr + 1; 
    }
}

PlasmaHH, я забыл включить дополнительную проверку (curr> ts) для удаления токенов с пробелами.

2 голосов
/ 28 декабря 2013

LazyStringSplitter:

#include <string>
#include <algorithm>
#include <unordered_set>

using namespace std;

class LazyStringSplitter
{
    string::const_iterator start, finish;
    unordered_set<char> chop;

public:

    // Empty Constructor
    explicit LazyStringSplitter()
    {}

    explicit LazyStringSplitter (const string cstr, const string delims)
        : start(cstr.begin())
        , finish(cstr.end())
        , chop(delims.begin(), delims.end())
    {}

    void operator () (const string cstr, const string delims)
    {
        chop.insert(delims.begin(), delims.end());
        start = cstr.begin();
        finish = cstr.end();
    }

    bool empty() const { return (start >= finish); }

    string next()
    {
        // return empty string
        // if ran out of characters
        if (empty())
            return string("");

        auto runner = find_if(start, finish, [&](char c) {
            return chop.count(c) == 1;
        });

        // construct next string
        string ret(start, runner);
        start = runner + 1;

        // Never return empty string
        // + tail recursion makes this method efficient
        return !ret.empty() ? ret : next();
    }
};
  • Я называю этот метод LazyStringSplitter по одной причине - он не разбивает строку за один раз.
  • По сути, он ведет себя как генератор питона
  • Он предоставляет метод с именем next, который возвращает следующую строку, которая отделена от оригинала
  • Я использовал unordered_set из c ++ 11 STL, так что поиск разделителей намного быстрее
  • А вот как это работает

ПРОГРАММА ИСПЫТАНИЙ

#include <iostream>
using namespace std;

int main()
{
    LazyStringSplitter splitter;

    // split at the characters ' ', '!', '.', ','
    splitter("This, is a string. And here is another string! Let's test and see how well this does.", " !.,");

    while (!splitter.empty())
        cout << splitter.next() << endl;
    return 0;
}

OUTPUT

This
is
a
string
And
here
is
another
string
Let's
test
and
see
how
well
this
does

Следующий план по улучшению этого заключается в реализации методов begin и end, чтобы можно было сделать что-то вроде:

vector<string> split_string(splitter.begin(), splitter.end());
2 голосов
/ 05 сентября 2016

Это моё решение этой проблемы:

vector<string> get_tokens(string str) {
    vector<string> dt;
    stringstream ss;
    string tmp; 
    ss << str;
    for (size_t i; !ss.eof(); ++i) {
        ss >> tmp;
        dt.push_back(tmp);
    }
    return dt;
}

Эта функция возвращает вектор строк.

2 голосов
/ 31 мая 2017

Да, я просмотрел все 30 примеров.

Я не смог найти версию split, которая работает для разделителей с несколькими символами, так что вот моя:

#include <string>
#include <vector>

using namespace std;

vector<string> split(const string &str, const string &delim)
{   
    const auto delim_pos = str.find(delim);

    if (delim_pos == string::npos)
        return {str};

    vector<string> ret{str.substr(0, delim_pos)};
    auto tail = split(str.substr(delim_pos + delim.size(), string::npos), delim);

    ret.insert(ret.end(), tail.begin(), tail.end());

    return ret;
}

Вероятно, не самая эффективная из реализаций, но это очень простое рекурсивное решение, использующее только <string> и <vector>.

Ах, он написан на C ++ 11, но в этом коде нет ничего особенного, так что вы можете легко адаптировать его к C ++ 98.

2 голосов
/ 27 сентября 2012

Это функция, которую я написал, которая помогает мне многое сделать. Это помогло мне при составлении протокола для WebSockets.

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

vector<string> split ( string input , string split_id ) {
  vector<string> result;
  int i = 0;
  bool add;
  string temp;
  stringstream ss;
  size_t found;
  string real;
  int r = 0;
    while ( i != input.length() ) {
        add = false;
        ss << input.at(i);
        temp = ss.str();
        found = temp.find(split_id);
        if ( found != string::npos ) {
            add = true;
            real.append ( temp , 0 , found );
        } else if ( r > 0 &&  ( i+1 ) == input.length() ) {
            add = true;
            real.append ( temp , 0 , found );
        }
        if ( add ) {
            result.push_back(real);
            ss.str(string());
            ss.clear();
            temp.clear();
            real.clear();
            r = 0;
        }
        i++;
        r++;
    }
  return result;
}

int main() {
    string s = "S,o,m,e,w,h,e,r,e, down the road \n In a really big C++ house.  \n  Lives a little old lady.   \n   That no one ever knew.    \n    She comes outside.     \n     In the very hot sun.      \n\n\n\n\n\n\n\n   And throws C++ at us.    \n    The End.  FIN.";
    vector < string > Token;
    Token = split ( s , "," );
    for ( int i = 0 ; i < Token.size(); i++)    cout << Token.at(i) << endl;
    cout << endl << Token.size();
    int a;
    cin >> a;
    return a;
}
2 голосов
/ 20 апреля 2016

Для тех, кому нужна альтернатива в разбиении строки с разделителем строк, возможно, вы можете попробовать мое следующее решение.

std::vector<size_t> str_pos(const std::string &search, const std::string &target)
{
    std::vector<size_t> founds;

    if(!search.empty())
    {
        size_t start_pos = 0;

        while (true)
        {
            size_t found_pos = target.find(search, start_pos);

            if(found_pos != std::string::npos)
            {
                size_t found = found_pos;

                founds.push_back(found);

                start_pos = (found_pos + 1);
            }
            else
            {
                break;
            }
        }
    }

    return founds;
}

std::string str_sub_index(size_t begin_index, size_t end_index, const std::string &target)
{
    std::string sub;

    size_t size = target.length();

    const char* copy = target.c_str();

    for(size_t i = begin_index; i <= end_index; i++)
    {
        if(i >= size)
        {
            break;
        }
        else
        {
            char c = copy[i];

            sub += c;
        }
    }

    return sub;
}

std::vector<std::string> str_split(const std::string &delimiter, const std::string &target)
{
    std::vector<std::string> splits;

    if(!delimiter.empty())
    {
        std::vector<size_t> founds = str_pos(delimiter, target);

        size_t founds_size = founds.size();

        if(founds_size > 0)
        {
            size_t search_len = delimiter.length();

            size_t begin_index = 0;

            for(int i = 0; i <= founds_size; i++)
            {
                std::string sub;

                if(i != founds_size)
                {
                    size_t pos  = founds.at(i);

                    sub = str_sub_index(begin_index, pos - 1, target);

                    begin_index = (pos + search_len);
                }
                else
                {
                    sub = str_sub_index(begin_index, (target.length() - 1), target);
                }

                splits.push_back(sub);
            }
        }
    }

    return splits;
}

Эти фрагменты состоят из 3 функций. Плохая новость заключается в том, что для использования функции str_split вам понадобятся две другие функции. Да, это огромный кусок кода. Но хорошая новость заключается в том, что эти две дополнительные функции могут работать независимо друг от друга, а иногда и могут быть полезны ...

Протестировал функцию в main() блоке так:

int main()
{
    std::string s = "Hello, world! We need to make the world a better place. Because your world is also my world, and our children's world.";

    std::vector<std::string> split = str_split("world", s);

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

И это даст:

Hello, 
! We need to make the 
 a better place. Because your 
 is also my 
, and our children's 
.

Я считаю, что это не самый эффективный код, но, по крайней мере, он работает. Надеюсь, это поможет.

...