Питання Найбільш елегантний спосіб ітерації слів рядка [closed]


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

Зауважте, що я не зацікавлений в рядках 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);
}

2638


походження


Чувак ... Елегант - це просто чудовий спосіб сказати в "моїй книзі" "efficiency-that-looks-pretty". Не соромтеся від використання функцій C та швидких методів, щоб досягти чогось завдяки тому, що він не міститься в шаблоні;) - nlaq
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; } - pyon
@ Едуардо: це теж не так ... потрібно протестувати між намаганнями передавати інше значення та використовувати це значення, тобто string sub; while (iss >> sub) cout << "Substring: " << sub << '\n'; - Tony Delroy
Різні варіанти в C ++, щоб зробити це за замовчуванням: cplusplus.com/faq/sequences/strings/split - hB0
Там більше елегантності, ніж просто досить ефективна. Елегантні атрибути включають низький лінійний підрахунок і високу чіткість. IMHO Elegance не є проксі для ефективності, але ремонтопридатність. - Matt


Відповіді:


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

1188



Чи можна вказати роздільник для цього? Як, наприклад, розбивання на коми? - l3dx
@ Jonathan: \ n не є роздільником у цьому випадку, це делімінер для виведення на cout. - huy
Це поганий варіант, оскільки він не використовує будь-який інший роздільник, тому не масштабований і не змінний. - SmallChess
Насправді це може робота прекрасно з іншими делімітаторами (хоча робити деякі це трохи потворно). Ви створюєте стиль ctype, який класифікує потрібні роздільники як пробіли, створює локаль, що містить цей аспект, а потім перенаправляє stringstream з цим мовою перед вилученням рядків. - Jerry Coffin
@Kinderchocolate "Стрічку можна вважати складеним з слів, розділених пробілами" - Хм, не звучить як поганий шлях вирішення проблеми. "не масштабована і не змінна" - Ага, приємно. - Christian Rau


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

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

template<typename Out>
void split(const std::string &s, char delim, Out result) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, 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", ':');

2308



Щоб уникнути пропускання пустих жетонів, виконайте такі дії empty() перевірити: if (!item.empty()) elems.push_back(item) - 0x499602D2
Як щодо delim містить дві символи, як ->? - herohuyongtao
@herohuyongtao, це рішення працює тільки для окремих символів розділення символів. - Evan Teran
@JeshwanthKumarNK, це не обов'язково, але це дозволяє робити такі речі, як передавати результат безпосередньо до такої функції: f(split(s, d, v)) в той же час користуючись переважно заздалегідь виділеним vector якщо ти хочеш. - Evan Teran
Caveat: split ("one: two :: three", ':') і split ("one: two :: three:", ':') повертають одне і те ж значення. - dshin


Можливим рішенням з використанням 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), використовуючи всі види обмежувачів.

Див документація для подробиць.


794



Швидкість тут не має значення, оскільки обидва ці випадки значно повільні, ніж функція, що нагадує сттрук. - Tom
І для тих, хто ще не має підсилення ... bcp копіює більше 1000 файлів для цього :) - Roman Starkov
Сттток - пастка. його нитка небезпечна. - tuxSlayer
@Ian Embedded розробники не всі використовують прискорення. - ACK_stoverflow
як доповнення: я використовую boost тільки тоді, коли я повинен, звичайно, я вважаю за краще додавати в власну бібліотеку коду, який є автономним та портативним, щоб я міг досягти точного конкретного коду, який виконує певну мету. Таким чином, код не публікується, виконується, тривіальний і портативний. Boost має своє місце, але я б припустив, що це трохи перебільшення для tokenising рядків: ви wouldnt ваш весь будинок транспортується до інженерної фірми, щоб отримати новий цвях, забитий в стіну, щоб повісити картину .... вони можуть це зробити надзвичайно добре, але прозар набагато переважає мінуси. - GMasucci


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

321



занадто погано, він розбивається на просторах ' '... - Offirmo


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

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 не робить копій для неймовірної швидкості.

Це більш ніж удвічі швидше, ніж найшвидший tokenize на цій сторінці та майже в 5 разів швидше, ніж деякі інші. Також за допомогою ідеальних типів параметрів ви можете виключити всі рядки та списки копій для збільшення додаткової швидкості.

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

Нарешті, ви можете вказати, чи обробляти пусті токени від результатів за допомогою останнього необов'язкового параметра.

Все, що потрібно std::string... інші - необов'язкові. Вона не використовує потоки або бібліотеку підвищення, але є достатньо гнучкою, щоб природно приймати деякі з цих іноземних типів.


168



Я дуже вболіваю за це, але для g ++ (і, мабуть, хорошої практики) кожен, хто цього використовує, захоче мати typedefs та типові назви: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType;  Потім замінити значення value_type і size_types відповідно. - aws
Для тих з нас, для яких матеріал шаблону та перший коментар є абсолютно чужими, приклад використання cpplete з обов'язковими включеннями буде чудовим. - Wes Miller
Ага добре, я зрозумів це. Я поклав лінії C + + з коментарям aws всередині тіла функції tokenize (), потім редагував лінії tokens.push_back (), щоб змінити ContainerT :: value_type тільки на ValueType і змінити (ContainerT :: value_type :: size_type) на ( SizeType). Виправлено біти g + +, що прокинулися. Просто викликайте його як tokenize (some_string, some_vector); - Wes Miller
Крім виконання декількох тестів продуктивності на зразкових даних, в основному я зменшив його до якомога менше можливих вказівок, а також якомога менше можливих копій пам'яті, увімкнених за допомогою класу підрядків, який лише вказує на відхилення / довжини в інших рядках. (Я прокотився самостійно, але є й інші реалізації). На жаль, для досягнення цієї мети нема чого іншого, але можливе поступове збільшення. - Marius
Це правильний результат для коли trimEmpty = true. Майте на увазі, що "abo" не є роздільником у цьому відповіді, але список символів делімітатора. Було б простіше змінити його, щоб взяти одну рядок символів розділювача (я думаю str.find_first_of слід змінити на str.find_first, але я міг би помилятися ... не можу перевірити) - Marius


Ось ще одне рішення. Це компактно і досить ефективно:

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

150



Перша версія проста і добре виконує роботу. Єдина зміна, яку я хотів би зробити, полягає в тому, щоб повернути результат безпосередньо, а не передавати його як параметр. - gregschlom
Вихід переданий як параметр для ефективності. Якщо результат було повернуто, це вимагатиме копію вектора або розподіл купу, який потім повинен бути звільнений. - Alec Thomas
Невеликий додаток до мого коментарю вище: ця функція може повернути вектора без покарань, якщо використовувати семантику C ++ 11. - Alec Thomas
@AlecThomas: ще до C ++ 11 більшість компіляторів не оптимізують зворотну копію через NRVO? (+1 все одно, дуже стислі) - Marcelo Cantos
З усіх відповідей це, здається, є одним з найбільш привабливих і гнучких. Разом з getline з роздільником, хоча це менш очевидне рішення. Чи не має стандарт c + + 11 для цього? Чи підтримує c + +11 перфокарти в ці дні? - Spacen Jasset


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

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

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

106



Чи можна оголосити? word як char? - abatishchev
Вибачте абатищев, C ++ - не моя сила. Але я уявляю, що не буде важко додати внутрішній цикл для циклу через кожен символ кожного слова. Але зараз я вважаю, що поточний цикл залежить від простору для розділення слів. Якщо ви не знаєте, що між кожним простором є лише один символ, у такому випадку ви можете просто передати "слово" на символ ... вибачте, я не можу більше допомогти, я мав сенс розібратися в моєму C ++ - gnomed
якщо ви оголошувати слово як символ, він буде повторюватися над будь-яким символом непробіл. Це досить просто, щоб спробувати: stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c; - Wayne Werner


Це схоже на питання переповнення Stack Як токенувати рядок в 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;
    }
}

76



Чи це матеріалізує копію всіх токенів, чи зберігає вона тільки початкову та кінцеву позиції поточного токена? - einpoklum


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

#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() чудово працює


65



Нарешті, рішення, яке правильно обробляє пусті токени з обох боків рядка - fmuecke