Як я повторюю слова рядка?


2985

Я намагаюся перебрати слова рядка.

Рядок можна вважати складеним зі слів, розділених пробілом.

Зауважте, що мене не цікавлять функції рядка 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);
}

Чи є більш елегантний спосіб це зробити?


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/sequences/strings/split
hB0

14
Тут більше елегантності, ніж просто досить ефективність. Елегантні атрибути включають низьку кількість рядків та високу розбірливість. IMHO Elegance - це не проксі для ефективності, але ремонтопридатності.
Метт

Відповіді:


1368

Для чого варто, ось ще один спосіб вилучення жетонів із вхідного рядка, спираючись лише на стандартні бібліотечні засоби. Це приклад сили та елегантності, що стоять за дизайном 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>{}};

164
Чи можна вказати для цього роздільник? Як, наприклад, розщеплення на коми?
l3dx

15
@Jonathan: \ n не розмежувач у цьому випадку, це роздільник для виведення до cout.
хуй

772
Це погане рішення, оскільки воно не приймає жодного іншого роздільника, тому не може бути масштабованим і не підтримувати.
HelloWorld

37
Насправді це може спрацювати чудово з іншими роздільниками (хоча робити це дещо негарно). Ви створюєте фасетний тип, який класифікує потрібні роздільники як пробіли, створює локаль, що містить цю грань, а потім нав'язує потік потоку з цією локальною точкою перед вилученням рядків.
Джеррі Труну

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
Caveat: split ("один: два: три", ':') і split ("один: два: три:", ':') повертають те саме значення.
dshin

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), використовуючи всі види роздільників.

Детальну інформацію див. У документації .


35
Швидкість тут не має значення, оскільки обидва ці випадки набагато повільніші, ніж функція, схожа на стручок.
Том,

45
А для тих, хто ще не має імпульсу ... bcp копіює понад 1000 файлів для цього :)
Роман Старков

12
Попередження, коли дається порожній рядок (""), цей метод повертає вектор, що містить рядок "". Тому додайте "if (! String_to_split.empty ())" перед розділенням.
Оффірмо

29
@Ian Embedded розробники не всі використовують boost.
ACK_stoverflow

31
як додаток: я використовую прискорення лише тоді, коли треба, як правило, я вважаю за краще додати до власної бібліотеки коду, який є самостійним і портативним, щоб я міг досягти невеликого точного конкретного коду, який досягає поставленої мети. Таким чином код є непублічним, виконавським, тривіальним та портативним. Boost має своє місце, але я б припустив, що це трохи надмірності для токенізації струн: ви б не перевезли весь будинок до інженерної фірми, щоб отримати новий цвях, забитий у стіну, щоб повісити картину .... вони можуть це зробити надзвичайно добре, але прозар значно перевищував мінуси.
GMasucci

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

12
Ви також можете розділити на інші роздільники, якщо ви використовуєте 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... інше - необов’язково. Він не використовує потоки або бібліотеку підсилення, але є досить гнучким, щоб можна було природним чином прийняти деякі з цих іноземних типів.


5
Я дуже прихильник цього, але для g ++ (і, мабуть, хорошої практики) кожен, хто використовує це, захоче вводити typedefs та typename: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Потім замінити відповідно value_type та size_types відповідно.
аїв

11
Для тих із нас, кому шаблони та перший коментар є абсолютно чужими, приклад використання, завершений із необхідними функціями, буде чудовим.
Уес Міллер

3
Ну добре, я зрозумів це. Я помістив рядки C ++ з коментаря aws всередину функції функції tokenize (), потім відредагував рядки tokens.push_back (), щоб змінити тип ContainerT :: value_type на просто ValueType і змінив (ContainerT :: value_type :: size_type) на ( SizeType). Виправлені біти, які g ++ скуготали. Просто викликайте його як tokenize (деякийringring, some_vector);
Уес Міллер

2
Окрім виконання декількох тестів на ефективність даних зразків, я, головним чином, скоротив їх до якомога менших інструкцій, а також якомога менше копій пам’яті, передбачених використанням класу підрядків, який посилається лише на компенсації / довжини в інших рядках. (Я прокатував свою, але є й інші реалізації). На жаль, ще не дуже багато можна зробити, щоб покращити це, але можливі додаткові збільшення.
Маріус

3
Це правильний вихід для того, коли trimEmpty = true. Майте на увазі, що "abo"в цій відповіді не є роздільником, а переліком символів. Було б просто змінити його, щоб взяти один розділовий рядок символів (я думаю, що str.find_first_ofслід змінити str.find_first, але я можу помилитися ... не можу перевірити)
Маріус,

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

10
Перша версія проста і виконує цю роботу ідеально. Єдина зміна, яку я вніс би, - це повернути результат безпосередньо, а не передавати його як параметр.
gregschlom

2
Вихід передається як параметр ефективності. Якщо результат буде повернуто, знадобиться або копія вектора, або виділення купи, яке потім доведеться звільнити.
Алек Томас

2
Невелике доповнення до мого коментаря вище: ця функція може повернути вектор без штрафних санкцій, якщо використовувати C ++ 11 семантику переміщення.
Алек Томас

7
@AlecThomas: Навіть перед C ++ 11, чи не більшість компіляторів не оптимізували б повернути копію через NRVO? (+1 у будь-якому випадку; дуже лаконічний)
Марсело Кантос

11
З усіх відповідей ця здається однією з найбільш привабливих і гнучких. Разом з лінійкою з роздільником, хоча це менш очевидне рішення. Чи стандарт c ++ 11 для цього нічого не має? Чи підтримує c ++ 11 перфокарти в наші дні?
Spacen Jasset

123

Це мій улюблений спосіб перебирати через рядок. Ви можете робити все, що завгодно за кожне слово.

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?
абатищев

Вибачте абатищев, C ++ - це не моя сильна точка. Але я думаю, було б не важко додати внутрішню петлю, щоб провести цикл через кожен символ у кожному слові. Але зараз я вважаю, що поточний цикл залежить від простору для розділення слів. Якщо ви не знаєте, що між кожним простором є лише один персонаж, і в цьому випадку ви можете просто кинути "слово" на знак ... Вибачте, я не можу бути
кориснішою

11
якщо ви оголосите слово як знака, воно перетвориться на будь-який символ, який не є пробілом. Досить просто спробувати:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner

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

Чи це матеріалізує копію всіх маркерів, чи зберігає лише початкове та кінцеве положення поточного маркера?
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 із is_any_of()чудовими можливостями.


Нарешті рішення, яке правильно обробляє порожні маркери з обох сторін рядка
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.

Не продайте цю угоду "Елегантність над продуктивністю" .


Я знаю про функції струни C і також знаю про проблеми з продуктивністю (про те, що я зазначив у своєму запитанні). Однак для цього конкретного питання я шукаю елегантне рішення C ++.
Ешвін Нанджаппа

11
@Nelson LaQuet: Дозвольте здогадатися: тому що strtok не є ретентом?
paercebal

40
@Nelson НЕ завжди проходять string.c_str () , щоб strtok! strtok руйнує вхідний рядок (вставляє символи "\ 0" для заміни кожного розділювача фудн), а c_str () повертає рядки, що не змінюються.
Еван Теран

3
@Nelson: Цей масив повинен бути розміром str.size () + 1 у вашому останньому коментарі. Але я погоджуюся з вашою тезою, що дурно уникати функцій C з "естетичних" причин.
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";,");

Ви забули додати до списку використання: "вкрай неефективно"
Xander Tulip

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

У мене є проблема в цій лінії:

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

Щоб використовувати його з вектором рядків (Edit: Оскільки хтось вказав, що не успадковувати 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 );

Обмежений уявою;)



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

Аргумент регулярного вираження дозволяє перевірити наявність численних аргументів (пробіли, коми тощо)

Я зазвичай перевіряю лише розділення на пробіли і коми, тому у мене також є така функція за замовчуванням:

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

Зауважте, ви також можете взяти аргумент рядка за посиланням, залежно від компілятора.


Це була б моя улюблена відповідь, але std :: regex розбита в GCC 4.8. Вони сказали, що правильно їх застосували в GCC 4.9. Я все ще
дарую

1
Це мій фаворит із незначними змінами: вектор повертається як посилання, як ви вже говорили, і аргументи "str" ​​і "regex" також передаються посиланнями. Дякую.
QuantumKarl

1
Сирі рядки досить корисні при роботі з моделями регулярних виразів. Таким чином, вам не потрібно використовувати послідовності втечі ... Ви можете просто використовувати 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;
}

Це працює лише для роздільників символів. Проста зміна дозволяє працювати з мультихарактером: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;
}

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
}

Подібні відповіді, можливо, краще підходять до регулярних виразів: тут і тут .
nobar

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

3
strtokпоходить із стандартної бібліотеки С, а не з C ++. Небезпечно використовувати в багатопотокових програмах. Він змінює вхідний рядок.
Кевін Панько

13
Оскільки він зберігає покажчик char від першого виклику в статичній змінній, так що при наступних викликах, коли NULL передано, він запам'ятовує, який вказівник слід використовувати. Якщо другий потік викликає, strtokколи інший потік ще обробляється, цей покажчик char буде перезаписаний, і обидва потоки матимуть неправильні результати. mkssoftware.com/docs/man3/strtok.3.asp
Кевін Панько

1
як згадувалося раніше, strtok небезпечний і навіть на C strtok_r рекомендується використовувати
замовчуванням

4
strtok_r можна використовувати, якщо ви знаходитесь у розділі коду, до якого можна отримати доступ. це єдине рішення з усього вищесказаного, що не є "лінійним шумом", і є свідченням того, що саме не так з c ++
Ерік Аронесті

Оновлено, щоб не було заперечень на основі безпеки потоку від C ++ підмовок.
Ерік Аронесті

17

Stringstream може бути зручно , якщо вам потрібно розібрати рядок по некосмічних символам:

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, ';')

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

Інструментарій має набагато більшу гнучкість, ніж показує цей простий приклад, але його корисність при розборі рядка на корисні елементи неймовірна.


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

1
Мені подобається це рішення, оскільки воно дозволяє роздільнику бути рядком, а не знаком, однак він змінює на місці рядка, тому він змушує створити копію оригінального рядка.
Алессандро Теруцці

11

Я зробив це, тому що мені потрібен був простий спосіб розділити рядки та рядки на основі с ... Сподіваємось, хтось ще може вважати це корисним. Крім того, він не покладається на лексеми, і ви можете використовувати поля як роздільники, що ще один необхідний мені ключ.

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

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 "));

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

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" та "Trade". Функція працює з вузькими та широкими рядками. Оскільки він поважає поточну локальність, він розбиває "RaumfahrtÜberwachungsVerordnung" на "Raumfahrt", "Überwachungs" та "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;
}

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";
    }
}

Yepp, діапазон на базі виглядає краще - я згоден
Porsche9II
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.