Выборочный итератор - PullRequest
       34

Выборочный итератор

0 голосов
/ 15 июня 2010

К вашему сведению: нет повышения, да, это так, я хочу заново изобрести колесо;)

Существует ли какая-либо форма выборочного итератора (возможного) в C ++?Я хочу разделить строки следующим образом:

some:word{or other

на такую ​​форму:

some : word { or other

Я могу сделать это с помощью двух циклов и find_first_of (":") и ("{") но это кажется (очень) неэффективным для меня.Я подумал, что, возможно, найдется способ создать / определить / написать итератор, который будет перебирать все эти значения с помощью for_each.Боюсь, что из-за этого я напишу полноценный пользовательский итеративный класс для std :: string.

Поэтому я подумал, что, возможно, это подойдет:

std::vector<size_t> list;
size_t index = mystring.find(":");
while( index != std::string::npos )
{
    list.push_back(index);
    index = mystring.find(":", list.back());
}
std::for_each(list.begin(), list.end(), addSpaces(mystring));

Этовыглядит грязно для меня, и я вполне уверен, что существует более элегантный способ сделать это.Но я не могу думать об этом.У кого-нибудь есть блестящая идея?Спасибо

PS: я не проверял опубликованный код, просто быстрое написание того, что я бы попробовал

ОБНОВЛЕНИЕ: после того, как я учел все ваши ответы, я придумал это,и это работает на мой вкус :).при этом предполагается, что последний символ является новой строкой или чем-то еще, в противном случае окончание {, } или : не будет обработано.

void tokenize( string &line )
{
    char oneBack = ' ';
    char twoBack = ' ';
    char current = ' ';
    size_t length = line.size();

    for( size_t index = 0; index<length; ++index )
    {
        twoBack = oneBack;
        oneBack = current;
        current = line.at( index );
        if( isSpecial(oneBack) )
        {
            if( !isspace(twoBack) ) // insert before
            {
                line.insert(index-1, " ");
                ++index;
                ++length;
            }
            if( !isspace(current) ) // insert after
            {
                line.insert(index, " ");
                ++index;
                ++length;
            }
        }
    }

Комментарии приветствуются как всегда:)

Ответы [ 5 ]

4 голосов
/ 15 июня 2010

Это сравнительно легко с помощью std :: istream_iterator.

Что вам нужно сделать, это определить свой собственный класс (скажем, Term).Затем определите, как читать одно «слово» (термин) из потока, используя оператор >>.

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

  • Любая последовательная последовательность буквенно-цифровых символов - это термин
  • Любой отдельный непробельный символ, который также не является буквенно-цифровым, является словом.this:
    #include <string>
    #include <sstream>
    #include <iostream>
    #include <iterator>
    #include <algorithm>
    
    class Term
    {
        public:
    
            // This cast operator is not required but makes it easy to use
            // a Term anywhere that a string can normally be used.
            operator std::string const&() const {return value;}
    
        private:
            // A term is just a string
            // And we friend the operator >> to make sure we can read it.
            friend std::istream& operator>>(std::istream& inStr,Term& dst);
            std::string     value;
    };
    

    Теперь все, что нам нужно сделать, это определить оператор >>, который читает слово в соответствии с правилами:

    // This function could be a lot neater using some boost regular expressions.
    // I just do it manually to show it can be done without boost (as requested)
    std::istream& operator>>(std::istream& inStr,Term& dst)
    {
       // Note the >> operator drops all proceeding white space.
       // So we get the first non white space
       char first;
       inStr >> first;
    
       // If the stream is in any bad state the stop processing.
       if (inStr)
       {
           if(std::isalnum(first))
           {
               // Alpha Numeric so read a sequence of characters
               dst.value = first;
    
               // This is ugly. And needs re-factoring.
               while((first = insStr.get(), inStr) && std::isalnum(first))
               {
                   dst.value += first;
               }
    
               // Take into account the special case of EOF.
               // And bad stream states.
               if (!inStr)
               {
                   if (!inStr.eof())
                   {
                       // The last letter read was not EOF and and not part of the word
                       // So put it back for use by the next call to read from the stream.
                       inStr.putback(first);
                   }
                   // We know that we have a word so clear any errors to make sure it
                   // is used. Let the next attempt to read a word (term) fail at the outer if.
                   inStr.clear();
               }
           }
           else
           {
               // It was not alpha numeric so it is a one character word.
               dst.value   = first;
           }
      }
      return inStr;
    }
    

    Так что теперь мы можем использовать его в стандартных алгоритмахпросто используя istream_iterator

    int main()
    {
        std::string         data    = "some:word{or other";
        std::stringstream   dataStream(data);
    
    
        std::copy(  // Read the stream one Term at a time.
                    std::istream_iterator<Term>(dataStream),
                    std::istream_iterator<Term>(),
    
                    // Note the ostream_iterator is using a std::string
                    // This works because a Term can be converted into a string.
                    std::ostream_iterator<std::string>(std::cout, "\n")
                 );
    
    }
    

    Вывод:

    > ./a.exe
    some
    :
    word
    {
    or
    other
    
1 голос
/ 15 июня 2010
std::string const str = "some:word{or other";

std::string result;
result.reserve(str.size());
for (std::string::const_iterator it = str.begin(), end = str.end();
     it != end; ++it)
{
  if (isalnum(*it))
  {
    result.push_back(*it);
  }
  else
  {
    result.push_back(' '); result.push_back(*it); result.push_back(' ');
  }
}

Вставьте версию для ускорения

std::string str = "some:word{or other";

for (std::string::iterator it = str.begin(), end = str.end(); it != end; ++it)
{
  if (!isalnum(*it))
  {
    it = str.insert(it, ' ') + 2;
    it = str.insert(it, ' ');
    end = str.end();
  }
}

Обратите внимание, что std::string::insert вставляет ДО пройденного итератора и возвращает итератор вновь вставленному символу. Назначение важно, поскольку буфер мог быть перераспределен в другом месте памяти (итераторы недействительны при вставке). Также обратите внимание, что вы не можете оставить end для всего цикла, каждый раз, когда вы вставляете его, вам нужно пересчитать его.

0 голосов
/ 15 июня 2010

существует более элегантный способ сделать это.

Я не знаю, как BOOST реализует это, но традиционным способом является ввод входной строки за символом в FSM , который определяет, где начинаются и заканчиваются токены (слова, символы).

Я могу сделать это с двумя циклами и find_first_of (":") и ("{")

Достаточно одного цикла с std :: find_first_of ().

Хотя я все еще большой поклонник автоматов для таких задач разбора.

P.S. Аналогичный вопрос

0 голосов
/ 15 июня 2010

Вы хотите токенизировать входную строку, ala strtok?

Если это так, вот функция токенизации, которую вы можете использовать. Он принимает входные данные string и строку разделителей (каждый символ в строке является возможным разделителем) и возвращает вектор token s. Каждый token является кортежем со строкой с разделителями, и в этом случае используется разделитель:

#include <cstdlib>
#include <vector>
#include <string>
#include <functional>
#include <iostream>
#include <algorithm>
using namespace std;

//  FUNCTION :      stringtok(char const* Raw, string sToks)
//  PARAMATERS :    Raw     Pointer to NULL-Terminated string containing a string to be tokenized.
//                  sToks   string of individual token characters -- each character in the string is a token
//  DESCRIPTION :   Tokenizes a string, much in the same was as strtok does.  The input string is not modified.  The
//                  function is called once to tokenize a string, and all the tokens are retuned at once.
//  RETURNS :       Returns a vector of strings.  Each element in the vector is one token.  The token character is
//                  not included in the string.  The number of elements in the vector is N+1, where N is the number
//                  of times the Token character is found in the string.  If one token is an empty string (as with the
//                  string "string1##string3", where the token character is '#'), then that element in the vector
//                  is an empty string.
//  NOTES :         
//
typedef pair<char,string> token;    // first = delimiter, second = data
inline vector<token> tokenize(const string& str, const string& delims, bool bCaseSensitive=false)   // tokenizes a string, returns a vector of tokens
{
    bCaseSensitive;

    // prologue
    vector<token> vRet;
    // tokenize input string
    for( string::const_iterator itA = str.begin(), it=itA; it != str.end(); it = find_first_of(++it,str.end(),delims.begin(),delims.end()) )
    {
        // prologue
        // find end of token
        string::const_iterator itEnd = find_first_of(it+1,str.end(),delims.begin(),delims.end());
        // add string to output
        if( it == itA ) vRet.push_back(make_pair(0,string(it,itEnd)));
        else            vRet.push_back(make_pair(*it,string(it+1,itEnd)));
        // epilogue
    }
    // epilogue
    return vRet;
}

using namespace std;

int main()
{
    string input = "some:word{or other";
    typedef vector<token> tokens;
    tokens toks = tokenize(input.c_str(), " :{");
    cout << "Input: '" << input << " # Tokens: " << toks.size() << "'\n";
    for( tokens::iterator it = toks.begin(); it != toks.end(); ++it )
    {
        cout << "  Token : '" << it->second << "', Delimiter: '" << it->first << "'\n";
    }
    return 0;

}
0 голосов
/ 15 июня 2010

Как насчет чего-то вроде:

std::string::const_iterator it, end = mystring.end();
for(it = mystring.begin(); it != end; ++it) {
  if ( !isalnum( *it ))
    list.push_back(it);
}

Таким образом, вы будете перебирать строку только один раз, и isalnum из ctype.h, кажется, делает то, что вы хотите.Конечно, приведенный выше код является очень упрощенным и неполным и предлагает только решение.

...