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

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

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

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

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

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

using namespace std;

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

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

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

Ответы [ 76 ]

0 голосов
/ 24 ноября 2017
#include <iostream>
#include <string>
#include <deque>

std::deque<std::string> split(
    const std::string& line, 
    std::string::value_type delimiter,
    bool skipEmpty = false
) {
    std::deque<std::string> parts{};

    if (!skipEmpty && !line.empty() && delimiter == line.at(0)) {
        parts.push_back({});
    }

    for (const std::string::value_type& c : line) {
        if (
            (
                c == delimiter 
                &&
                (skipEmpty ? (!parts.empty() && !parts.back().empty()) : true)
            )
            ||
            (c != delimiter && parts.empty())
        ) {
            parts.push_back({});
        }

        if (c != delimiter) {
            parts.back().push_back(c);
        }
    }

    if (skipEmpty && !parts.empty() && parts.back().empty()) {
        parts.pop_back();
    }

    return parts;
}

void test(const std::string& line) {
    std::cout << line << std::endl;

    std::cout << "skipEmpty=0 |";
    for (const std::string& part : split(line, ':')) {
        std::cout << part << '|';
    }
    std::cout << std::endl;

    std::cout << "skipEmpty=1 |";
    for (const std::string& part : split(line, ':', true)) {
        std::cout << part << '|';
    }
    std::cout << std::endl;

    std::cout << std::endl;
}

int main() {
    test("foo:bar:::baz");
    test("");
    test("foo");
    test(":");
    test("::");
    test(":foo");
    test("::foo");
    test(":foo:");
    test(":foo::");

    return 0;
}

Выход:

foo:bar:::baz
skipEmpty=0 |foo|bar|||baz|
skipEmpty=1 |foo|bar|baz|


skipEmpty=0 |
skipEmpty=1 |

foo
skipEmpty=0 |foo|
skipEmpty=1 |foo|

:
skipEmpty=0 |||
skipEmpty=1 |

::
skipEmpty=0 ||||
skipEmpty=1 |

:foo
skipEmpty=0 ||foo|
skipEmpty=1 |foo|

::foo
skipEmpty=0 |||foo|
skipEmpty=1 |foo|

:foo:
skipEmpty=0 ||foo||
skipEmpty=1 |foo|

:foo::
skipEmpty=0 ||foo|||
skipEmpty=1 |foo|
0 голосов
/ 21 мая 2014

Вот мой подход, разрезать и разделить:

string cut (string& str, const string& del)
{
    string f = str;

    if (in.find_first_of(del) != string::npos)
    {
        f = str.substr(0,str.find_first_of(del));
        str = str.substr(str.find_first_of(del)+del.length());
    }

    return f;
}

vector<string> split (const string& in, const string& del=" ")
{
    vector<string> out();
    string t = in;

    while (t.length() > del.length())
        out.push_back(cut(t,del));

    return out;
}

Кстати, если я могу что-то сделать, чтобы оптимизировать это ..

0 голосов
/ 05 декабря 2012
void splitString(string str, char delim, string array[], const int arraySize)
{
    int delimPosition, subStrSize, subStrStart = 0;

    for (int index = 0; delimPosition != -1; index++)
    {
        delimPosition = str.find(delim, subStrStart);
        subStrSize = delimPosition - subStrStart;
        array[index] = str.substr(subStrStart, subStrSize);
        subStrStart =+ (delimPosition + 1);
    }
}
0 голосов
/ 02 апреля 2014

На этот вопрос уже есть много хороших ответов, это всего лишь маленькая деталь.

Разделение строки для вывода - это одно, но если разделить на контейнер , как vector, вызов reserve() может повлиять на производительность, потому что разделение приведет к «одновременному» выделению фрагментов разных размеров.

Даже если от этого может пострадать элегантность , может предшествовать небольшой предшествующий анализ :

#include <algorithm>
size_t n = std::count(s.begin(), s.end(), ' ');
0 голосов
/ 16 января 2019

У меня совершенно другой подход, чем у других решений, который предлагает большую ценность в том смысле, что другим решениям по-разному не хватает, но, конечно, у него есть и свои недостатки. Здесь - рабочая реализация с примером размещения <tag></tag> вокруг слов.

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

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

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

using namespace std;

#include <iostream>
#include <string>

#include <cctype>

typedef enum boundary_type_e {
    E_BOUNDARY_TYPE_ERROR = -1,
    E_BOUNDARY_TYPE_NONE,
    E_BOUNDARY_TYPE_LEFT,
    E_BOUNDARY_TYPE_RIGHT,
} boundary_type_t;

typedef struct boundary_s {
    boundary_type_t type;
    int pos;
} boundary_t;

bool is_delim_char(int c) {
    return isspace(c); // also compare against any other chars you want to use as delimiters
}

bool is_word_char(int c) {
    return ' ' <= c && c <= '~' && !is_delim_char(c);
}

boundary_t maybe_word_boundary(string str, int pos) {
    int len = str.length();
    if (pos < 0 || pos >= len) {
        return (boundary_t){.type = E_BOUNDARY_TYPE_ERROR};
    } else {
        if (pos == 0 && is_word_char(str[pos])) {
            // if the first character is word-y, we have a left boundary at the beginning
            return (boundary_t){.type = E_BOUNDARY_TYPE_LEFT, .pos = pos};
        } else if (pos == len - 1 && is_word_char(str[pos])) {
            // if the last character is word-y, we have a right boundary left of the null terminator
            return (boundary_t){.type = E_BOUNDARY_TYPE_RIGHT, .pos = pos + 1};
        } else if (!is_word_char(str[pos]) && is_word_char(str[pos + 1])) {
            // if we have a delimiter followed by a word char, we have a left boundary left of the word char
            return (boundary_t){.type = E_BOUNDARY_TYPE_LEFT, .pos = pos + 1};
        } else if (is_word_char(str[pos]) && !is_word_char(str[pos + 1])) {
            // if we have a word char followed by a delimiter, we have a right boundary right of the word char
            return (boundary_t){.type = E_BOUNDARY_TYPE_RIGHT, .pos = pos + 1};
        }
        return (boundary_t){.type = E_BOUNDARY_TYPE_NONE};
    }
}

int main() {
    string str;
    getline(cin, str);

    int len = str.length();
    for (int i = 0; i < len; i++) {
        boundary_t boundary = maybe_word_boundary(str, i);
        if (boundary.type == E_BOUNDARY_TYPE_LEFT) {
            // whatever
        } else if (boundary.type == E_BOUNDARY_TYPE_RIGHT) {
            // whatever
        }
    }
}

Как видите, код очень прост для понимания и точной настройки, а фактическое использование кода очень короткое и простое. Использование C ++ не должно останавливать нас от написания самого простого и легко настраиваемого кода, даже если это означает, что мы не используем STL. Я думаю, это пример того, что Линус Торвальдс мог бы назвать «вкус» , поскольку мы устранили всю логику, которая нам не нужна, при написании в стиле, который, естественно, позволяет обрабатывать больше случаев, когда если возникает необходимость справиться с ними.

Что могло бы улучшить этот код, так это использование enum class, принимающего указатель функции на is_word_char в maybe_word_boundary вместо прямого вызова is_word_char и передавающего лямбду.

0 голосов
/ 01 июля 2015
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <vector>

int main() {
    using namespace std;
   int n=8;
    string sentence = "10 20 30 40 5 6 7 8";
    istringstream iss(sentence);

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

     for(int i=0;i<n;i++){
        cout<<tokens.at(i);
     }


}
...