Как мне перебрать слова строки?

2989

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

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

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

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

#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);
}

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

Ashwin Nanjappa
источник
617
Чувак ... Элегантность - это просто модный способ сказать "эффективность-это-выглядит-красиво" в моей книге. Не стесняйтесь использовать функции C и быстрые методы для выполнения чего-либо только потому, что это не содержится в шаблоне;)
14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
Пион
21
@Eduardo: это тоже неправильно ... вам нужно проверить iss между попыткой передать другое значение и использовать это значение, то естьstring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy
9
Различные варианты в C ++, чтобы сделать это по умолчанию: cplusplus.com/faq/septions/strings/split
hB0
14
В элегантности есть нечто большее, чем просто красивая эффективность. Элегантные атрибуты включают низкое количество строк и высокую разборчивость. IMHO Elegance - это не показатель эффективности, а ремонтопригодность.
Мэтт

Ответы:

1369

Для чего стоит, вот еще один способ извлечь токены из входной строки, полагаясь только на стандартные библиотечные средства. Это пример силы и элегантности дизайна STL.

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

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

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

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... или создать vectorнепосредственно:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino
источник
164
Можно ли указать для этого разделитель? Как например разделение на запятые?
l3dx
15
@Jonathan: в данном случае \ n - это не разделитель, а разделитель для вывода в cout.
2010 г.
772
Это плохое решение, так как оно не требует какого-либо другого разделителя, поэтому не масштабируется и не поддерживается.
HelloWorld
37
На самом деле, это может прекрасно работать с другими разделителями (хотя некоторые из них несколько уродливы). Вы создаете фасет ctype, который классифицирует требуемые разделители как пробелы, создаете локаль, содержащую этот фасет, а затем наполняете поток строк этим языком перед извлечением строк.
Джерри Коффин
53
@ Kinderchocolate «Можно предположить, что строка состоит из слов, разделенных пробелами» - Хм, не похоже на плохое решение проблемы вопроса. "не масштабируется и не поддерживается" - Ха, хороший.
Кристиан Рау
2426

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

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

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Обратите внимание, что это решение не пропускает пустые токены, поэтому следующие найдут 4 элемента, один из которых пуст:

std::vector<std::string> x = split("one:two::three", ':');
Эван Теран
источник
86
Чтобы избежать пропуска пустых токенов, сделайте empty()проверку:if (!item.empty()) elems.push_back(item)
0x499602D2
11
Как насчет delim содержит два символа как ->?
herohuyongtao
7
@herohuyongtao, это решение работает только для разделителей.
Эван Теран
4
@JeshwanthKumarNK, в этом нет необходимости, но он позволяет вам выполнять такие вещи, как передача результата непосредственно в функцию, подобную этой: f(split(s, d, v))при этом, при желании, он по- прежнему обладает преимуществом предварительного выделения vector.
Эван Теран
8
Предостережение: split ("one: two :: three", ':') и split ("one: two :: three:", ':') возвращают одно и то же значение.
Дшин
834

Возможное решение с использованием Boost может быть:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Этот подход может быть даже быстрее, чем stringstream подход. А поскольку это универсальная шаблонная функция, ее можно использовать для разделения других типов строк (wchar и т. Д. Или UTF-8) с использованием всех видов разделителей.

Смотрите документацию для деталей.

ididak
источник
35
Скорость здесь не имеет значения, так как оба эти случая намного медленнее, чем функция, подобная strtok.
Том
45
А для тех, у кого еще нет boost ... bcp копирует более 1000 файлов для этого :)
Роман Старков
12
Предупреждение, когда передается пустая строка (""), этот метод возвращает вектор, содержащий строку "". Поэтому добавьте «if (! String_to_split.empty ())» перед разделением.
Offirmo
29
Разработчики @Ian Embedded не все используют boost.
ACK_stoverflow
31
в качестве дополнения: я использую boost только тогда, когда это необходимо, обычно я предпочитаю добавлять в свою собственную библиотеку кода, который является автономным и переносимым, чтобы я мог получить небольшой точный конкретный код, который выполняет поставленную задачу. Таким образом, код не является общедоступным, производительным, тривиальным и переносимым. У Boost есть свое место, но я бы предположил, что это немного излишне для токенизирующих струн: вам не пришлось бы транспортировать весь свой дом в инженерную фирму, чтобы вбить новый гвоздь в стену, чтобы повесить картину ... они могут это сделать очень хорошо, но плюсы намного перевесили минусы.
GMasucci
364
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}
кев
источник
13
Вы также можете разделить на другие разделители, если вы используете getlineв whileусловии, например, разделить запятыми, используйте while(getline(ss, buff, ',')).
Али
181

Для тех, кому неудобно жертвовать всей эффективностью ради размера кода и рассматривать «работоспособность» как тип элегантности, следующее должно быть приятно: (и я думаю, что класс контейнера шаблонов является удивительно элегантным дополнением):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Я обычно выбираю использовать std::vector<std::string>типы в качестве второго параметра ( ContainerT) ... но list<>это намного быстрее, чем vector<>для случаев, когда прямой доступ не требуется, и вы даже можете создать свой собственный класс строки и использовать что-то вроде std::list<subString>гдеsubString не делает никаких копий с невероятной скоростью увеличивается.

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

Кроме того, он не выполняет (крайне неэффективный) возврат результата, а скорее передает токены в качестве ссылки, что также позволяет вам создавать токены с использованием нескольких вызовов, если вы того пожелаете.

Наконец, он позволяет указать, следует ли обрезать пустые токены из результатов с помощью последнего необязательного параметра.

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

Marius
источник
5
Я большой поклонник этого, но для g ++ (и, вероятно, хорошей практики) любой, кто использует это, захочет typedefs и typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; затем соответственно заменить value_type и size_types.
AWS
11
Для тех из нас, для кого материал шаблона и первый комментарий совершенно незнакомы, было бы неплохо использовать пример использования cmplete с необходимыми включениями.
Уэс Миллер
3
Ах, хорошо, я понял это. Я поместил строки C ++ из комментария aws в тело функции tokenize (), затем отредактировал строки tokens.push_back (), изменив ContainerT :: value_type на просто ValueType, и изменил (ContainerT :: value_type :: size_type) на ( SizeType). Исправлены биты, о которых жаловался g ++. Просто вызовите его как tokenize (some_string, some_vector);
Уэс Миллер
2
Помимо выполнения нескольких тестов производительности на примерах данных, в первую очередь я сократил их до как можно меньшего числа возможных инструкций, а также как можно меньше копий памяти, допускаемых использованием класса подстроки, который ссылается только на смещения / длины в других строках. (Я накатил свой, но есть и другие реализации). К сожалению, не так уж много можно сделать, чтобы улучшить это, но постепенное увеличение было возможно.
Мариус
3
Это правильный выход для когда trimEmpty = true. Имейте в виду, что "abo"это не разделитель в этом ответе, а список символов-разделителей. Было бы просто изменить его так, чтобы он содержал одну строку символов-разделителей (я думаю, что str.find_first_ofдолжен измениться на str.find_first, но я могу ошибаться ... не могу проверить)
Marius
158

Вот еще одно решение. Это компактно и достаточно эффективно:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Он может быть легко настроен для работы с разделителями строк, широкими строками и т. Д.

Обратите внимание, что разделение ""приводит к одной пустой строке, а разделение ","(т. Е. Sep) приводит к двум пустым строкам.

Он также может быть легко расширен для пропуска пустых токенов:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Если желательно разделить строку на несколько разделителей, пропуская пустые токены, можно использовать эту версию:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}
Alec Thomas
источник
10
Первая версия проста и отлично справляется со своей работой. Единственное изменение, которое я сделал бы, - возвращал результат напрямую, а не передавал его в качестве параметра.
gregschlom
2
Выходные данные передаются в качестве параметра эффективности. Если бы результат был возвращен, ему потребовалась бы либо копия вектора, либо выделение кучи, которое затем нужно было бы освободить.
Алек Томас
2
Небольшое дополнение к моему комментарию выше: эта функция может возвращать вектор без штрафа, если используется семантика перемещения C ++ 11.
Алек Томас
7
@AlecThomas: Даже до C ++ 11 большинство компиляторов не оптимизируют обратную копию через NRVO? (+1 в любом случае; очень кратко)
Марсело Кантос
11
Из всех ответов это кажется одним из самых привлекательных и гибких. Вместе с getline с разделителем, хотя это менее очевидное решение. Стандарт C ++ 11 не имеет ничего для этого? Поддерживает ли c ++ 11 перфокарты в наши дни?
Спейсен Джассет
124

Это мой любимый способ перебора строки. Вы можете делать все, что вы хотите на слово.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}
гномед
источник
Можно ли объявить wordкак char?
Абатищев
Извините абатищев, С ++ не моя сильная сторона. Но я предполагаю, что было бы нетрудно добавить внутренний цикл для циклического прохождения каждого символа в каждом слове. Но сейчас я считаю, что текущий цикл зависит от пробелов для разделения слов. Если вы не знаете, что между каждым пробелом есть только один символ, в этом случае вы можете просто привести «слово» к символу ... извините, я не могу помочь, я
намеревался
11
если вы объявите слово как символ, оно будет перебирать каждый непробельный символ. Это достаточно просто попробовать:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Уэйн Вернер
79

Это похоже на вопрос переполнения стека. Как мне токенизировать строку в C ++? ,

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}
Ferruccio
источник
Материализует ли это копию всех токенов или сохраняет только начальную и конечную позиции текущего токена?
einpoklum
66

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

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Конечно, в Boost есть функция, split()которая работает частично так. И, если под «пустым пространством» вы действительно подразумеваете любой тип пустого пространства, использование Boost's split с is_any_of()великолепно работает.

Shadow2531
источник
Наконец, решение, которое правильно обрабатывает пустые токены с обеих сторон строки
fmuecke
53

В STL такого метода уже нет.

Тем не менее, вы можете использовать strtok()функцию C, используя std::string::c_str()член, или вы можете написать свою собственную. Вот пример кода, который я нашел после быстрого поиска в Google ( «разделение строки STL» ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Взято из: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Если у вас есть вопросы по поводу примера кода, оставьте комментарий, и я объясню.

И то, что он не реализует typedefвызываемый итератор или перегружает <<оператор, не означает, что это плохой код. Я использую функции C довольно часто. Например, printfи scanfоба быстрее, чем std::cinи std::cout(значительно),fopen синтаксис намного более удобен для двоичных типов, и они также имеют тенденцию создавать меньшие EXE-файлы.

Не продавайте по этой сделке «Элегантность над производительностью» .

user19302
источник
Я знаю о строковых функциях C и знаю о проблемах с производительностью (оба я отметил в своем вопросе). Однако для этого конкретного вопроса я ищу элегантное решение C ++.
Эшвин Нанджаппа
11
@ Нельсон LaQuet: Дай угадаю: потому что strtok не возвращается?
paercebal
40
@Nelson не всегда проходят string.c_str () , чтобы strtok! strtok уничтожает входную строку (вставляет символы \ 0 для замены каждого разделителя foudn), а c_str () возвращает неизменяемую строку.
Эван Теран
3
@ Нельсон: Этот массив должен иметь размер str.size () + 1 в вашем последнем комментарии. Но я согласен с вашим тезисом, что глупо избегать функций С по «эстетическим» причинам.
j_random_hacker
2
@paulm: Нет, медлительность потоков C ++ вызвана аспектами. Они по-прежнему работают медленнее, чем функции stdio.h, даже если синхронизация отключена (и для потоковых потоков, которые не могут синхронизироваться).
Бен Фойгт
42

Вот функция разделения, которая:

  • является родовым
  • использует стандарт C ++ (без повышения)
  • принимает несколько разделителей
  • игнорирует пустые токены (может быть легко изменено)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

Пример использования:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
Марко М.
источник
Вы забыли добавить в список использования: "крайне неэффективно"
Ксандер Тюлип
1
@XanderTulip, можете ли вы быть более конструктивным и объяснить, как и почему?
Марко М.
3
@XanderTulip: я полагаю, вы ссылаетесь на него, возвращая вектор по значению. Оптимизация возвращаемого значения (RVO, Google it) должна позаботиться об этом. Также в C ++ 11 вы можете вернуться по ссылке.
Джозеф Гарвин
3
На самом деле это можно оптимизировать дальше: вместо .push_back (str.substr (...)) можно использовать .emplace_back (str, start, pos-start). Таким образом, строковый объект создается в контейнере, и таким образом мы избегаем операции перемещения + другие махинации, выполняемые функцией .substr.
Михай Бишог
@zoopp да. Хорошая идея. VS10 не имел поддержки emplace_back, когда я писал это. Я обновлю свой ответ. Спасибо
Марко М.
36

У меня есть решение этой проблемы в 2 строки:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Тогда вместо печати вы можете поместить его в вектор.

рому
источник
35

Еще один гибкий и быстрый способ

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Чтобы использовать его с вектором строк (Правка: поскольку кто-то указал не наследовать классы STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

Это оно! И это только один из способов использовать токенизатор, например, как просто считать слова:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Ограничено воображением;)

Роберт
источник
Ницца. Относительно Appenderпримечания "Почему мы не должны наследовать класс от классов STL?"
Андреас Шпиндлер
32

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

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

Аргумент regex позволяет проверять множественные аргументы (пробелы, запятые и т. Д.)

Я обычно проверяю только разделение на пробелы и запятые, поэтому у меня также есть эта функция по умолчанию:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

В "[\\s,]+"проверяет пространства ( \\s) и запятых (, ).

Обратите внимание, если вы хотите разделить wstringвместо string,

  • изменить все std::regexнаstd::wregex
  • изменить все sregex_token_iteratorнаwsregex_token_iterator

Обратите внимание, что вы также можете получить строковый аргумент по ссылке, в зависимости от вашего компилятора.

dk123
источник
Это был бы мой любимый ответ, но std :: regex не работает в GCC 4.8. Они сказали, что правильно реализовали это в GCC 4.9. Я все еще даю тебе мой +1
mchiasson
1
Это мой фаворит с небольшими изменениями: вектор возвращается как ссылка, как вы сказали, а аргументы "str" ​​и "regex" также передаются по ссылкам. Спасибо.
QuantumKarl
1
Необработанные строки очень полезны при работе с шаблонами регулярных выражений. Таким образом, вам не нужно использовать escape-последовательности ... Вы можете просто использовать R"([\s,]+)".
Сэм
26

Использование std::stringstreamкак у вас работает отлично, и делать именно то, что вы хотели. Если вы просто ищете другой способ сделать что-то, вы можете использовать std::find()/ std::find_first_of()и std::string::substr().

Вот пример:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}
KTC
источник
Это работает только для разделителей одного символа. Простое изменение позволяет работать с мультисимволом:prev_pos = pos += delimiter.length();
Дэвид Дория
25

Если вам нравится использовать boost, но вы хотите использовать целую строку в качестве разделителя (вместо отдельных символов, как в большинстве ранее предложенных решений), вы можете использовать boost_split_iterator.

Пример кода, включающий удобный шаблон:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}
zerm
источник
20

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

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}
AJMansfield
источник
Подобные ответы с возможно лучшим подходом регулярных выражений: здесь и здесь .
Нобар
20

Есть функция с именем strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}
Pratik Deoghare
источник
3
strtokиз стандартной библиотеки C, а не C ++. Не безопасно использовать в многопоточных программах. Он изменяет строку ввода.
Кевин Панко
13
Поскольку он хранит указатель на символ из первого вызова в статической переменной, так что при последующих вызовах, когда передается NULL, он запоминает, какой указатель следует использовать. Если второй поток вызывает, strtokкогда другой поток все еще обрабатывает, этот указатель на символ будет перезаписан, и тогда оба потока будут иметь неправильные результаты. mkssoftware.com/docs/man3/strtok.3.asp
Кевин Панко
1
как упоминалось ранее, strtok небезопасен и даже в C рекомендуется использовать
strtok_r
4
strtok_r может использоваться, если вы находитесь в разделе кода, к которому можно получить доступ. это единственное решение из всего вышеперечисленного, которое не является «шумом линии» и является свидетельством того, что именно не так с c ++
Эрик Аронести,
Обновлен, поэтому не может быть никаких возражений по поводу безопасности потоков из C ++ Wicks.
Эрик Аронесты
17

Поток строки может быть удобен, если вам нужно проанализировать строку по непробельным символам:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')
lukmac
источник
14

До сих пор я использовал тот в Boost , но мне нужно что-то, что не зависит от этого, поэтому я пришел к этому:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Хорошим моментом является то, что в separatorsвас может пройти более одного персонажа.

Горан
источник
13

Я свернул свой собственный, используя strtok, и использовал boost, чтобы разбить строку. Лучший метод, который я нашел, - это библиотека C ++ String Toolkit . Это невероятно гибкий и быстрый.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
        std::string s("Somewhere down the road");
        std::vector<std::string> result;
        if( strtk::parse( s, whitespace, result ) )
        {
            for(size_t i = 0; i < result.size(); ++i )
                std::cout << result[i] << std::endl;
        }
    }

    {  // parsing a string into a vector of floats with other separators
        // besides spaces

        std::string s("3.0, 3.14; 4.0");
        std::vector<float> values;
        if( strtk::parse( s, whitespace_and_punctuation, values ) )
        {
            for(size_t i = 0; i < values.size(); ++i )
                std::cout << values[i] << std::endl;
        }
    }

    {  // parsing a string into specific variables

        std::string s("angle = 45; radius = 9.9");
        std::string w1, w2;
        float v1, v2;
        if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
        {
            std::cout << "word " << w1 << ", value " << v1 << std::endl;
            std::cout << "word " << w2 << ", value " << v2 << std::endl;
        }
    }

    return 0;
}

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

DannyK
источник
13

Коротко и элегантно

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

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

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

с помощью:

auto a = split("this!!is!!!example!string", "!!");

вывод:

this
is
!example!string
user1438233
источник
1
Мне нравится это решение, потому что оно позволяет разделителю быть строкой, а не символом, однако он изменяет строку вместо нее, поэтому он вызывает создание копии исходной строки.
Алессандро Теруцци
11

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

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

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Примеры:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

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

    return 0;
}

Будет выводить:

Это
является примером CString



int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

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

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

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

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

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

    return 0;
}

Чтобы оставить пустые записи (по умолчанию пустые места будут исключены):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

Цель состояла в том, чтобы сделать его похожим на метод Split () в C #, где разбиение строки так же просто, как:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Я надеюсь, что кто-то еще может найти это столь же полезным, как и я.

Стив Делл
источник
10

Что насчет этого:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}
гиббз
источник
Это лучший ответ здесь, если вы хотите разделить только один символ-разделитель. Оригинальный вопрос хотел разбить на пробел, хотя, означая любую комбинацию одного или нескольких последовательных пробелов или табуляции. Вы действительно ответили stackoverflow.com/questions/53849
Oktalist
10

Этот ответ берет строку и помещает ее в вектор строк. Он использует библиотеку наддува.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));
NL628
источник
9

Вот еще один способ сделать это ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}
оборота user246110
источник
9

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

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}
Марти Б
источник
9

Недавно мне пришлось разбить верблюжье слово на подслово. Здесь нет разделителей, только верхние символы.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Например, это разделяет «AQueryTrades» на «A», «Query» и «Trades». Функция работает с узкими и широкими строками. Поскольку он уважает текущую локаль, он разделяет "RaumfahrtÜberwachungsVerordnung" на "Raumfahrt", "Uberwachungs" и "Verordnung".

Примечание std::upperдолжно действительно передаваться как аргумент шаблона функции. Тогда более обобщенные из этой функции могут быть разделены на разделители как ",", ";"или " "тоже.

Андреас Шпиндлер
источник
2
Там было 2 оборота. Это мило. Похоже, мой английский был в значительной степени "немецкий". Тем не менее, ревизионист не исправил две незначительные ошибки, возможно, потому что они были очевидны в любом случае: std::isupperмогут быть переданы в качестве аргумента, а не std::upper. Второго положить typenameперед собой String::const_iterator.
Андреас Шпиндлер
9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}
san45
источник
9

Использование 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";
    }
}
Porsche9II
источник
Yepp, диапазон для основанного выглядит лучше - я согласен
Porsche9II