C удаление знаков препинания в строках, ошибка erase () / итератора

#c #string #iterator

#c #строка #итератор

Вопрос:

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

Я читаю содержимое файла, который содержит кучу слов. Когда я читаю в word, я хочу передать его функции, которую я назвал stripPunct . Однако я хочу убрать знаки препинания только в начале и конце строки, а не в середине.

Так, например:

(word) должен удалить ‘(‘ и ‘)’, в результате чего останется только word

ненадо! следует удалить ‘!’, в результате чего просто не

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

 void stripPunct(string amp; str) {
    string::iterator itr1 = str.begin();
    string::reverse_iterator itr2 = str.rbegin();

    while ( ispunct(*itr1) ) {
        str.erase(itr1);
        itr1  ;
    }

    while ( ispunct(*itr2) ) {
        str.erase(itr2);
        itr2--;
    }
}
  

Однако, очевидно, что это не работает, потому что erase () требует обычного итератора, а не reverse_iterator . Но в любом случае, я чувствую, что эта логика довольно неэффективна.

Кроме того, я попытался вместо reverse_iterator использовать обычный итератор, запустив его в str.end(), затем уменьшил его, но он говорит, что я не смогу разыменовать итератор, если я запущу его в str.end () .

Кто-нибудь может мне помочь с хорошим способом сделать это? Или, может быть, указать обходной путь для того, что у меня уже есть?

Заранее большое вам спасибо!

—————— [ ПРАВИТЬ ] —————————-

найдено решение, хотя оно может быть не лучшим решением:

 // Call the stripPunct method:

stripPunct(str);
if ( !str.empty() ) { // make sure string is still valid
  // perform other code
}
  

И вот метод stripPunct:

 void stripPunct(string amp; str) {
   string::iterator itr1 = str.begin();
   string::iterator itr2 = str.end();

   while ( !(str.empty()) amp;amp; ispunct(*itr1) ) 
       itr1 = str.erase(itr1);

   itr2--;
   if ( itr2 != str.begin() ) {

       while ( !(str.empty()) amp;amp; ispunct(*itr2) ) {
           itr2 = str.erase(itr2);
           itr2--;
       }
   }
}
  

Комментарии:

1. Что вы, безусловно, делаете неправильно, так это то, что вы используете itr1 и itr2 после того, как вы их удалили, вы должны заменить их значением, возвращаемым erase .

2. Забыл об этом, спасибо, что указали на это!

Ответ №1:

Во-первых, обратите внимание на пару проблем с вашим кодом:

  • после того, как вы вызываете, erase() используя itr1 , вы аннулируете itr2 .
  • при использовании reverse_iterator для обратного прохождения последовательности вы хотите использовать , а не -- (это своего рода причина существования обратных итераторов).

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

 int not_punct(char c) {
    return !ispunct((unsigned char) c);
}

void stripPunct(string amp; str) {
    string::iterator itr = find_if( str.begin(), str.end(), not_punct);

    str.erase( str.begin(), itr);

    string::reverse_iterator ritr = find_if( str.rbegin(), str.rend(), not_punct);

    str.erase( ritr.base(), str.end());
}
  

Обратите внимание, что я использовал base() для получения «обычного» итератора, соответствующего reverse_iterator . Я нахожу логику того, base() нужно ли ее корректировать, запутанной (обратные итераторы вообще меня смущают) — в данном случае это не так, потому что мы хотим начать стирание после найденного символа.

Эта статья Скотта Мейерса,http://drdobbs.com/cpp/184401406 , имеет хорошую обработку reverse_iterator::base() в разделе. «Руководство 3: Понять, как использовать базовый итератор reverse_iterator». Информация из этой статьи также была включена в книгу Мейера «Эффективный STL».

Комментарии:

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

2. @Rhinoo: обратите внимание, что я избавился от if тестов после find_if() : они не нужны и фактически предотвратят обрезку строки, содержащей только знаки препинания, до пустой строки.

3. Также обратите внимание, что вместо того, чтобы придумывать небольшую функцию-оболочку для передачи find_if() , вы можете быть более похожими на STL и использовать функции-помощники объекта из заголовка <functional> : вместо not_punct передачи not1(ptr_fun<int, int>(ispunct)) Однако, я, честно говоря, чаще всего нахожу такого рода вещи менее читаемыми.

Ответ №2:

Вы не можете разыменовать iterator::end(), потому что он указывает на недопустимую память (память сразу после конца массива), поэтому сначала вам нужно уменьшить ее.

И последнее замечание: если word состоит только из знаков препинания, ваша программа завершится сбоем, обязательно с этим разберитесь.

Комментарии:

1. Во-первых, да, я знаю, я сначала уменьшаю его, мой компилятор (visual studio 2010) все еще выдает мне эту ошибку. Извините, я должен был указать это в своем вопросе. Во-вторых, я обрабатываю случай ошибки, просто не поместил его в код в моем вопросе.

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

3. Нет, он по-прежнему выдает мне «строковый итератор, не разыменуемый» для itr2, если я использую обычный итератор, установите для него значение str.end (), а затем сначала уменьшите его.

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

5. Эрбурет, спасибо, что вообще мне помог. И на самом деле, это было не так. Где-то я не обрабатываю случай ошибки, но это работает для некоторых строк. Еще раз спасибо!

Ответ №3:

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

 string tmp_str="";
tmp_str.reserve(str.length());
for (string::iterator itr1 = str.begin(); itr1 != str.end(); itr1  )
{
   if (!ispunct(*itr1))
   {
      tmp_str.push_back(*itr1);
   }
}
str = tmp_str;