Как я могу токенизировать строку в C ++? - PullRequest
389 голосов
/ 10 сентября 2008

Java имеет удобный метод разделения:

String str = "The quick brown fox";
String[] results = str.split(" ");

Есть ли простой способ сделать это в C ++?

Ответы [ 35 ]

4 голосов
/ 01 августа 2012

Здесь много слишком сложных предложений. Попробуйте это простое решение std :: string:

using namespace std;

string someText = ...

string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
    sepOff = someText.find(' ', sepOff);
    string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
    string token = someText.substr(tokenOff, tokenLen);
    if (!token.empty())
        /* do something with token */;
    tokenOff = sepOff;
}
4 голосов
/ 26 июля 2016

Ответ Адама Пирса предоставляет токенайзер с ручным вращением, принимающий const char*. С итераторами это немного более проблематично, потому что увеличение конечного итератора string не определено . Тем не менее, учитывая string str{ "The quick brown fox" } мы, безусловно, можем достичь этого:

auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };

while (start != cend(str)) {
    const auto finish = find(++start, cend(str), ' ');

    tokens.push_back(string(start, finish));
    start = finish;
}

Живой пример


Если вы хотите абстрагироваться от сложности с помощью стандартной функциональности, как На Фрейнде предлагает strtok - простой вариант:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);

Если у вас нет доступа к C ++ 17, вам нужно заменить data(str), как в этом примере: http://ideone.com/8kAGoa

Хотя это и не показано в примере, strtok не обязательно использовать один и тот же разделитель для каждого токена. Наряду с этим преимуществом, есть несколько недостатков:

  1. strtok нельзя использовать для нескольких strings одновременно: либо необходимо передать nullptr для продолжения токенизации текущего string, либо необходимо передать новый char* для токенизации (есть некоторые нестандартные реализации, которые поддерживают это, такие как: strtok_s)
  2. По той же причине strtok нельзя использовать в нескольких потоках одновременно (однако это может быть определено реализацией, например: Реализация Visual Studio является поточно-ориентированной )
  3. Вызов strtok изменяет string, на котором он работает, поэтому его нельзя использовать на const string s, const char* s или литеральных строках для токенизации любого из них с помощью strtok или для работы с ним. string, чье содержимое необходимо сохранить, str должно быть скопировано, затем копия может быть обработана на

Оба предыдущих метода не могут генерировать токенизированный vector на месте, то есть без абстрагирования их в вспомогательную функцию, которую они не могут инициализировать const vector<string> tokens. Эта функциональность и возможность принимать любой пробел можно использовать с помощью istream_iterator. Например, учитывая: const string str{ "The quick \tbrown \nfox" } мы можем сделать это:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };

Живой пример

Требуемая конструкция istringstream для этого варианта имеет гораздо большую стоимость, чем предыдущие 2 варианта, однако эта стоимость обычно скрывается за счет выделения string.


Если ни один из вышеперечисленных вариантов не является достаточно гибким для ваших нужд токенизации, наиболее гибкий вариант - это, конечно, использовать regex_token_iterator, поскольку такая гибкость сопряжена с большими затратами, но, опять же, это, вероятно, скрыто в string стоимость размещения. Скажем, например, что мы хотим токенизировать на основе неэкранированных запятых, также используя пробел, с учетом следующего ввода: const string str{ "The ,qu\\,ick ,\tbrown, fox" } мы можем сделать это:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };

Живой пример

2 голосов
/ 09 мая 2018

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

vector<string> get_words(string const& text)
{
    vector<string> result;
    string tmp = text;

    size_t first_pos = 0;
    size_t second_pos = tmp.find(" ");;

    while (second_pos != string::npos)
    {
        if (first_pos != second_pos)
        {
            string word = tmp.substr(first_pos, second_pos - first_pos);
            result.push_back(word);
        }
        tmp = tmp.substr(second_pos + 1);
        second_pos = tmp.find(" ");
    }

    result.push_back(tmp);

    return result;
}

Пожалуйста, прокомментируйте, есть ли лучший подход к чему-либо в моем коде или если что-то не так.

2 голосов
/ 26 октября 2012

Вот подход, который позволяет вам контролировать, включены ли пустые токены (например, strsep) или исключены (например, strtok).

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)
{
  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  {
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    }

  return tokens;
}
2 голосов
/ 26 июля 2014

Мне кажется странным, что со всеми нами, любителями скорости, здесь, на SO, никто не представил версию, которая использует сгенерированную во время компиляции справочную таблицу для разделителя (пример реализации ниже). Использование справочной таблицы и итераторов должно превзойти эффективность std :: regex, если вам не нужно разбивать регулярное выражение, просто используйте его, его стандарт на C ++ 11 и супер гибкий.

Некоторые уже предложили регулярное выражение, но для noobs вот упакованный пример, который должен делать именно то, что ожидает OP:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
    std::smatch m{};
    std::vector<std::string> ret{};
    while (std::regex_search (it,end,m,e)) {
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    }
    return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
    std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
    auto v = split(str);
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex{"[^e]+"});  //using e as delim shows flexibility
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    return 0;
}

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

template<bool...> struct BoolSequence{};        //just here to hold bools
template<char...> struct CharSequence{};        //just here to hold chars
template<typename T, char C> struct Contains;   //generic
template<char First, char... Cs, char Match>    //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
    Contains<CharSequence<Cs...>, Match>{};     //strip first and increase index
template<char First, char... Cs>                //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {}; 
template<char Match>                            //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};

template<int I, typename T, typename U> 
struct MakeSequence;                            //generic
template<int I, bool... Bs, typename U> 
struct MakeSequence<I,BoolSequence<Bs...>, U>:  //not last
    MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U> 
struct MakeSequence<0,BoolSequence<Bs...>,U>{   //last  
    using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
    /* could be made constexpr but not yet supported by MSVC */
    static bool isDelim(const char c){
        static const bool table[256] = {Bs...};
        return table[static_cast<int>(c)];
    }   
};
using Delims = CharSequence<'.',',',' ',':','\n'>;  //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;

С этим легко сделать функцию getNextToken:

template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
    begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
    auto second = std::find_if(begin,end,Table{});      //find first delim or end
    return std::make_pair(begin,second);
}

Использовать его также просто:

int main() {
    std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
    auto it = std::begin(s);
    auto end = std::end(s);
    while(it != std::end(s)){
        auto token = getNextToken(it,end);
        std::cout << std::string(token.first,token.second) << std::endl;
        it = token.second;
    }
    return 0;
}

Вот живой пример: http://ideone.com/GKtkLQ

1 голос
/ 10 сентября 2008

Нет прямого способа сделать это. Обратитесь к этому исходному коду проекта кода , чтобы узнать, как создать класс для этого.

1 голос
/ 03 августа 2011

вы можете воспользоваться boost :: make_find_iterator. Нечто похожее на это:

template<typename CH>
inline vector< basic_string<CH> > tokenize(
    const basic_string<CH> &Input,
    const basic_string<CH> &Delimiter,
    bool remove_empty_token
    ) {

    typedef typename basic_string<CH>::const_iterator string_iterator_t;
    typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;

    vector< basic_string<CH> > Result;
    string_iterator_t it = Input.begin();
    string_iterator_t it_end = Input.end();
    for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
        i != string_find_iterator_t();
        ++i) {
        if(remove_empty_token){
            if(it != i->begin())
                Result.push_back(basic_string<CH>(it,i->begin()));
        }
        else
            Result.push_back(basic_string<CH>(it,i->begin()));
        it = i->end();
    }
    if(it != it_end)
        Result.push_back(basic_string<CH>(it,it_end));

    return Result;
}
0 голосов
/ 15 января 2015

Ранее я делал лексер / токенизатор с использованием только стандартных библиотек Вот код:

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

using namespace std;

string seps(string& s) {
    if (!s.size()) return "";
    stringstream ss;
    ss << s[0];
    for (int i = 1; i < s.size(); i++) {
        ss << '|' << s[i];
    }
    return ss.str();
}

void Tokenize(string& str, vector<string>& tokens, const string& delimiters = " ")
{
    seps(str);

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

int main(int argc, char *argv[])
{
    vector<string> t;
    string s = "Tokens for everyone!";

    Tokenize(s, t, "|");

    for (auto c : t)
        cout << c << endl;

    system("pause");

    return 0;
}
0 голосов
/ 17 марта 2014

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

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

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

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

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

    return tokens;
}

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

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

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

0 голосов
/ 25 февраля 2014
/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
/// 
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end(), i;
    iter sep_b = sep.begin(), sep_e = sep.end();

    // search through s
    while (b != e){
        i = search(b, e, sep_b, sep_e);

        // no more separator found
        if (i == e){
            // it's not an empty string
            if (b != e)
                v.push_back(string(b, e));
            break;
        }
        else if (i == b){
            // the separator is found and right at the beginning
            // in this case, we need to move on and search for the
            // next separator
            b = i + sep.length();
        }
        else{
            // found the separator
            v.push_back(string(b, i));
            b = i;
        }
    }
}

Библиотека буста хороша, но она не всегда доступна. Делать такие вещи вручную - тоже хорошее упражнение для мозга. Здесь мы просто используем алгоритм std :: search () из STL, см. Приведенный выше код.

...