C++: удаление всех звездочек из строки, где звездочки не являются символами умножения

Итак, в основном, у меня может быть строка, которая выглядит так:"Эй, это строка * эта строка потрясающая 97 * 3 = 27 * эта строка классная".

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

In чтобы определить, является ли Звездочка для умножения, я, очевидно, могу просто проверить, зажата ли она между двумя числами.

таким образом, я думал, что могу сделать что-то вроде (псевдокод):

wasNumber = false
Loop through string
   if number 
      set wasNumber = true
   else
      set wasNumber = false
   if asterisk
      if wasNumber
         if the next word is a number
            do nothing
         else
            remove asterisk
      else
         remove asterisk

однако, это ^ уродливо и неэффективно на огромной строке. Можете ли вы придумать лучший способ сделать это на C++?

кроме того, как я могу проверить, является ли слово числом? Допускается десятичное число. Я знаю, что есть функция проверьте, является ли символ числом...

4 ответов


полностью функционирующий код:

#include <iostream>
#include <string>
using namespace std;

string RemoveAllAstericks(string);
void RemoveSingleAsterick(string&, int);
bool IsDigit(char);

int main()
{
    string myString = "hey this is a string * this string is awesome 97 * 3 = 27 * this string is cool";
    string newString = RemoveAllAstericks(myString);

    cout << "Original: " << myString << "\n";
    cout << "Modified: " << newString << endl;

    system("pause");
    return 0;
}

string RemoveAllAstericks(string s)
{
    int len = s.size();
    int pos;

    for(int i = 0; i < len; i++)
    {
       if(s[i] != '*') 
          continue;

       pos = i - 1;
       char cBefore = s[pos];
       while(cBefore == ' ')
       {
          pos--;
          cBefore = s[pos];
       }

       pos = i + 1;
       char cAfter  = s[pos];
       while(cAfter == ' ')
       {
          pos++;
          cAfter = s[pos];
       }

       if( IsDigit(cBefore) && IsDigit(cAfter) )
          RemoveSingleAsterick(s, i);
    }

    return s;
}

void RemoveSingleAsterick(string& s, int i)
{
    s[i] = ' '; // Replaces * with a space, but you can do whatever you want
}

bool IsDigit(char c)
{
   return (c <= 57 && c >= 48);
}

верхнего уровня описание:

код ищет строку, пока не встретит *. Затем он смотрит на первый символ без пробелов до и после *. Если оба символа числовые, код решает, что это операция умножения, и удаляет звездочку. В противном случае он игнорируется.

см. историю изменений этого поста, если вы хотите другое подробности.

Важное Замечание:

  • вы должны серьезно рассмотреть возможность добавления граничных проверок в строку (т. е. не пытайтесь получить доступ к индексу, который меньше 0 или более len
  • если вас беспокоят круглые скобки, измените условие, которое проверяет пробелы, чтобы также проверить круглые скобки.
  • проверка один символ - это число плохая идея. в крайней мере, это потребует двух логических проверок (см. Мой

вы можете начать с реализации медленной версии, это может быть намного быстрее, чем вы думаете. Но, скажем так, это слишком медленно. Тогда это проблема оптимизации. Причем тут неэффективность лжи?

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

теперь это часть" удалить звездочку", которая является проблемой для вас. Ключевым моментом здесь является то, что вам не нужно дублировать строку: вы можете фактически изменить ее на месте, так как вы только удаление элементы.

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

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

Если вы решили сохранить текущую строку, вам просто нужно продвигать каждое из ваших целых чисел/итераторов по одному и копировать соответственно. Если вы не хотите сохранить его, просто продвиньте строку чтения! Тогда вам нужно только разрезать строку на количество звездочек, которые вы удалили. Сложность просто O (n), без какого-либо дополнительного используемого буфера.

Также обратите внимание, что ваш алгоритм будет проще (но эквивалент), если написано так:

wasNumber = false
Loop through string
   if number 
      set wasNumber = true
   else
      set wasNumber = false
      if asterisk and wasNumber and next word is a number
          do nothing // using my algorithm, "do nothing" actually copies what you intend to keep
      else
          remove asterisk

Я нашел вашу маленькую проблему интересной, и я написал (и испытана) небольшая и простая функция, которая будет делать именно это на std::string. Вот тебе:

// TestStringsCpp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <string>
#include <iostream>

using namespace std;

string& ClearAsterisk(string& iString)
{
    bool bLastCharNumeric = false;
    string lString = "0123456789";

    for (string::iterator it = iString.begin(); it != iString.end() ; ++it) {
        switch (*it) {
        case ' ':   break;//ignore whitespace characters
        case '*':
            if (bLastCharNumeric) {
                //asterisk is preceded by numeric character. we have to check if
                //the following non space character is numeric also
                for (string::iterator it2 = it + 1; it2 != iString.end() ; ++it2) {
                    if (*it2 != ' ') {
                        if (*it2 <= '9' && *it2 >= '0') break;
                        else iString.erase(it);
                        break;  //exit current for
                    }
                }
            }
            else iString.erase(it);;
            break;

        default:
            if (*it <= '9' && *it >= '0') bLastCharNumeric= true;
            else bLastCharNumeric = false;  //reset flag
        }
    }
    return iString;
}

int _tmain(int argc, _TCHAR* argv[])
{
    string testString = "hey this is a string * this string is awesome 97 * 3 = 27 * this string is cool";

    cout<<ClearAsterisk(testString).c_str();
    cin >> testString;  //this is just for the app to pause a bit :)

    return 0;
}

Он будет отлично работать с вашей строкой образца, но он потерпит неудачу, если у вас есть такой текст:"this is a happy 5 * 3day menu" потому что он проверяет только первый символ после'*'. Но, честно говоря, я не могу представить себе много случаев, когда у вас была бы такая конструкция в предложение.

HTH,
JP.


регулярное выражение не обязательно будет более эффективным, но оно позволит вам полагаться на кого-то другого для анализа и манипулирования строками.

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