Подсчет групп букв, цифр, символов в строке, и условно заменить символы


У меня есть с задач программирования С++: чтение строки символов и выделить группы букв, цифр и символов. Если в данной строке больше групп букв, чем групп символов, заменить все группы символов "123":

Группы подмассивы, каждый из которых содержит один определенный тип символа.

Пример входных данных

asd;=-0pa82)(*ui1oo

Соответствующий вывод

asd1230pa82123ui1oo

Кроме того, было бы полезно распечатать дополнительная информация на эту строку:

Groups of letters: 4: asd pa ui oo 
Groups of symbols: 2: ;=- )(* 
Groups of numbers: 3: 0 82 1 

There are more groups of letters than groups of symbols in this line

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

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

void CharsCount(string& str){
    vector<string> letters, symbols, numbers;
    string temp = "";
    static const string symbols_list = "!#$%&'()*+,-./:;<=>?[]^_{|}~`\\";
    auto pos = string::npos;
    for (const auto& x: str){
        if ( isdigit(x) ) { // this char is digit
            if (temp.empty()){
                temp += x;
            } else if (isdigit(temp.back())){    //last char in temporary string - digit?
                temp += x;  //add that digit to the temp string
                } else if (isalpha(temp.back())){ //last char - digit?
                    letters.push_back(temp);    //save temp string in a digit vector
                    temp = "";
                    temp += x;
                } else {        //last digit is a symbol?
                    symbols.push_back(temp); //save temp to the symbols vector
                    temp = "";
                    temp += x;
                }
        }
        if (isalpha(x)) { 
            if (temp.empty()){
                temp += x;
            } else if (isalpha(temp.back())){    
                temp += x;  
            } else if (isdigit(temp.back())){
                numbers.push_back(temp);
                temp = "";
                temp += x;
            } else {      
                symbols.push_back(temp);
                temp = "";
                temp += x;
            }
        }
        if ((pos = symbols_list.find(x)) != string::npos ){//char - symbol?
            if (temp.empty()){
                temp += x;
            } else if (isalpha(temp.back())){    
                letters.push_back(temp);
                temp = "";
                temp += x; 
            } else if (isdigit(temp.back())){
                numbers.push_back(temp);
                temp = "";
                temp += x;
            } else {     
                temp += x;
            }
        }
        }

     //Adding temp to the associated vector after the main loop
        if (!temp.empty()){
            if (isalpha(temp.back())){
                letters.push_back(temp);
                temp = "";
            } else if (isdigit(temp.back())){
                numbers.push_back(temp);
                temp = "";
            } else symbols.push_back(temp);
        }

    cout << "Groups of letters: " << letters.size() << ": ";
    for (auto x: letters){
        cout << x << " ";
    };
    cout << endl;
    cout << "Groups of symbols: " << symbols.size() << ": ";
    for (auto x: symbols){
        cout << x << " ";
    };
    cout << endl;
    cout << "Groups of numbers: " << numbers.size() << ": ";
    for (auto x: numbers){
        cout << x << " ";
    };
    cout << endl;
    //replacing the symbols if needed:
    if (letters.size() > symbols.size()){
        cout << "There are more groups of letters than groups of symbols in this line" << endl;
        cout << "Initial string: " << str << endl;
        for (auto z : symbols){
            str.replace(str.find(z), z.size(), "123");
        }
        cout << "Resulting string: " << str << endl;
    }
    }

int main() {
    string i = "asd;=-0pa82)(*ui1oo";
    CharsCount(i);        
    return 0;
}


206
4
задан 21 февраля 2018 в 09:02 Источник Поделиться
Комментарии
3 ответа

Заголовки и пространств имен

Нам не хватает <cctype> (для std::isalpha() и семьи).

Приведя все имена из пространства имен является проблематичным; namespace std в частности, так. Он может незаметно изменить смысл вашей программы , когда вы не ожидаете его. Привыкайте использовать префикс пространства имен (std намеренно очень короткий), или импортировать просто имена, вам нужно в мельчайших разумных пределах.

Интерфейс функции

Избежать необходимости ссылаться на (изменяемое) значение. Предпочитают принимает ссылку на константный строковый (const std::string& str). Поскольку мы хотим, чтобы значение, мы можем изменить, мы должны принимать строковое значение:

void CharsCount(std::string str) {

Это изменение позволяет называть это более удобно main()без необходимости создания имени переменной:

CharsCount("asd;=-0pa82)(*ui1oo");

Нам не нужны symbols_list

Как мы работаем по умолчанию ("с") язык, мы можем использовать std::ispunct для проверки символов в нашем списке:

    if (std::ispunct(x)) {

Структура

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

struct groups_result {
std::vector<std::string> letters, symbols, numbers;
std::string final_string;
};

groups_result process_string(const std::string& str);
void print_result(const groups_result& groups);

Хотя это похоже больше работы, это может позволить нам писать самопроверки тесты сравнение groups_result объекты. Мы также можем выбрать где печатать вывода, если мы пишем

void print_result(std::ostream& os, const groups_result& groups);

Повторение кода

Сейчас наш основной цикл имеет три блока, что все по общей схеме:


  • если temp пуста, добавьте к нему безоговорочно

  • если текущим символом является того же типа, как и предыдущий, добавьте ее temp

  • в противном случае, добавьте temp в соответствующий вектор и начать новую строку

Мы можете избежать всех ссылок на temp.back() вспоминая последний символ (в виде простого char). Мы можем сделать даже лучше - нам просто нужно знать, какую функцию использовать для "это же тестовый класс", и какой вектор мы будем добавлять результат.

Это может быть немного сложнее, потому что мы теперь представляем некоторые косвенной адресации (указатели same_class и current_vector), но она значительно уменьшает повторение и сделать его проще для добавления новых типов символов:

groups_result process_string(const std::string& str)
{
groups_result groups{str, str};
std::string temp = "";

int (*same_class)(int) = nullptr;
std::vector<std::string> *current_vector = nullptr;

for (const auto& x: str) {
const unsigned char ux = x; // we need this for <cctype> functions
// is x in the same group as previous character?
if (!same_class || !same_class(ux)) {
if (current_vector) {
current_vector->emplace_back(std::move(temp));
}
// what's the new class?
if (std::isalpha(ux)) {
same_class = std::isalpha;
current_vector = &groups.letters;
} else if (std::isdigit(ux)) {
same_class = std::isdigit;
current_vector = &groups.numbers;
} else if (std::ispunct(ux)) {
same_class = std::ispunct;
current_vector = &groups.symbols;
} else {
// ignored characters
same_class = nullptr;
current_vector = nullptr;
}
// start a new group
temp.clear();
}
temp.push_back(x);
}

// Add last group to its vector
if (current_vector && !temp.empty()) {
current_vector->emplace_back(std::move(temp));
}

// Replace the symbols if needed
if (groups.letters.size() > groups.symbols.size()) {
for (auto z: groups.symbols) {
groups.final_string.replace(str.find(z), z.size(), "123");
}
}

return groups;
}

Если бы у нас было много классов, то мы можем получить еще более тяжелом, используя массив пар (в "одном классе" функция и вектор-указатель) заменить if/else цепи есть, но только для трех функций, это, наверное, не стоит:

    const std::pair<int(*)(int),std::vector<std::string>*> classes[] = {
{ std::isalpha, &groups.letters },
{ std::isdigit, &groups.numbers },
{ std::ispunct, &groups.symbols },
{ nullptr, nullptr }
};

for (const auto& x: str) {
const unsigned char ux = x; // we need this for <cctype> functions
// is x in the same group as previous character?
if (!same_class || !same_class(ux)) {
if (current_vector) {
current_vector->emplace_back(std::move(temp));
}
// what's the new class?
for (auto char_class: classes) {
if (!(same_class = char_class.first) || same_class(ux)) {
current_vector = char_class.second;
break;
}
}
// start a new group
temp.clear();
}
temp.push_back(x);
}

Повторение в типографии

Мы можем сделать полезную функцию для упрощения печати:

static void print_group(std::ostream& os, const char *name, const std::vector<std::string>& values)
{
os << "Groups of " << name << ": " << values.size() << ":";
for (auto const& x: values) {
os << " " << x;
}
os << '\n';
}

void print_result(std::ostream& os, const groups_result& groups)
{
print_group(os, "letters", groups.letters);
print_group(os, "symbols", groups.symbols);
print_group(os, "numbers", groups.numbers);

if (groups.letters.size() > groups.symbols.size()) {
os << "There are more groups of letters than groups of symbols in this line" << '\n';
os << "Initial string: " << groups.initial_string << '\n';
os << "Resulting string: " << groups.final_string << '\n';
}
}

Я также использовал '\n' а не std::endl в конце строки, так как нам не нужно промывать выход до завершения программы.

Сделать тестовую программу более гибкой

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

int main(int argc, char **argv)
{
for (int i = 1; i < argc; ++i) {
print_result(std::cout, process_string(argv[i]));
std::cout << std::endl;
}
}

На этот раз я не использовал std::endlтак мы получаем выход как каждая строка обрабатывается.


Собираем все вместе

#include <algorithm>
#include <ostream>
#include <string>
#include <vector>

struct groups_result {
std::string initial_string = {}, final_string = {};
std::vector<std::string> letters = {}, symbols = {}, numbers = {};
};

groups_result process_string(const std::string& str)
{
groups_result groups{str, str};
std::string temp = "";

int (*same_class)(int) = nullptr;
std::vector<std::string> *current_vector = nullptr;

const std::pair<int(*)(int),std::vector<std::string>*> classes[] = {
{ std::isalpha, &groups.letters },
{ std::isdigit, &groups.numbers },
{ std::ispunct, &groups.symbols },
{ nullptr, nullptr }
};

for (const auto& x: str) {
const unsigned char ux = x; // we need this for <cctype> functions
// is x in the same group as previous character?
if (!same_class || !same_class(ux)) {
if (current_vector) {
current_vector->emplace_back(std::move(temp));
}
// what's the new class?
for (auto char_class: classes) {
if (!(same_class = char_class.first) || same_class(ux)) {
current_vector = char_class.second;
break;
}
}
// start a new group
temp.clear();
}
temp.push_back(x);
}

// Add last group to its vector
if (current_vector && !temp.empty()) {
current_vector->emplace_back(std::move(temp));
}

// Replace the symbols if needed
if (groups.letters.size() > groups.symbols.size()) {
for (auto z: groups.symbols) {
groups.final_string.replace(str.find(z), z.size(), "123");
}
}

return groups;
}

static void print_group(std::ostream& os, const char *name, const std::vector<std::string>& values)
{
os << "Groups of " << name << ": " << values.size() << ":";
for (auto const& x: values) {
os << " " << x;
}
os << '\n';
}

void print_result(std::ostream& os, const groups_result& groups)
{
print_group(os, "letters", groups.letters);
print_group(os, "symbols", groups.symbols);
print_group(os, "numbers", groups.numbers);

if (groups.letters.size() > groups.symbols.size()) {
os << "There are more groups of letters than groups of symbols in this line" << '\n';
os << "Initial string: " << groups.initial_string << '\n';
os << "Resulting string: " << groups.final_string << '\n';
}
}

// Test program
#include <iostream>
int main(int argc, char **argv)
{
for (int i = 1; i < argc; ++i) {
print_result(std::cout, process_string(argv[i]));
std::cout << std::endl;
}
}

1
ответ дан 21 февраля 2018 в 05:02 Источник Поделиться

Код можно сделать более читабельным и несколько вычислений могут выполняться параллельно для улучшения работы.
Например: str.replace() можно избежать, остальные строки могут быть сформированы параллельно считая групп, которые будут значительно улучшить производительность.

Шаги по установлению контактов с такими проблемами:
1. Рисовать детерминированных конечных автоматов (DFA).
2. Преобразовать в ДКА код.

Вот моя реализация,

void process(string str){

int i = 0;
string otherStr = ""; // symbol groups replaced by "123"

int alphaCount = 0; // total alpha group
string alphaStr = ""; // all alpha groups seperated by space

int digitCount = 0; // total digit groups
string digitStr = ""; // digit groups separated by space

int symbolCount = 0; // total symbol group
string symbolStr = ""; // symbol group separeted by space

while(i < str.size()){

if(isalpha(str[i])){
for(; i<str.size() && isalpha(str[i]); i++){
otherStr += str[i];
alphaStr += str[i];
}

alphaCount++;
alphaStr += " ";
if(i<str.size())
--i;
}
else if(isdigit(str[i])){
for(; i<str.size() && isdigit(str[i]); i++){
otherStr += str[i];
digitStr += str[i];
}

digitCount++;
digitStr += " ";
if(i<str.size())
--i;
}
else if(isSymbol(str[i])){ //isSymbol() -> implement it.
otherStr += "123"
for(; i<str.size() && isSymbol(str[i]); i++){
symbolStr += str[i];
}

symbolCount++;
symbolStr += " ";
if(i<str.size())
--i;
}
else
i++;
}

/********************results**************************/

// Here

/*****************************************************/

}

Время выполнения: \$О(П)\$

2
ответ дан 21 февраля 2018 в 12:02 Источник Поделиться

Ок, я перечислю несколько вещей:

Строки Конструктора По Умолчанию

string temp = "";

В = "" довольно излишним, учитывая, что конструктор по умолчанию string делает. Кроме того, есть соответствующая функция std::string::clear для того, чтобы очистить строку.


Вертикальный Интервал

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


Лишние Пробелы

if ( isdigit(x) )

Я вижу, вы использовать пробелы внутри скобок, но не в другие условия. Будьте последовательны!


Вмятие

    if (temp.empty()){
temp += x;
} else if (isdigit(temp.back())){ //last char in temporary string - digit?
temp += x; //add that digit to the temp string
} else if (isalpha(temp.back())){ //last char - digit?
letters.push_back(temp); //save temp string in a digit vector
temp = "";
temp += x;
} else { //last digit is a symbol?
symbols.push_back(temp); //save temp to the symbols vector
temp = "";
temp += x;
}

В данной части Кодекса, нижние две условные предложения выглядят так, будто они являются частью вложенного оператора if. Вы должны быть последовательным с абзацного отступа, не только в этой части кода (функции отступ ужасно иногда). Кроме того, ваши комментарии за последние else if несовместимы с ваш фактический код.


Размер против длины строки

z.size()

Это просто я, будучи педантичным, но для строки, вы принимаете его длина, а не его размер. Возможно использовать std::string::length!

1
ответ дан 21 февраля 2018 в 12:02 Источник Поделиться