Подсчитать количество букв в строке из векторного C

#c

#c

Вопрос:

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

Например, входные данные «Привет» «привет» «Стек» «Стек» будут выводить

ответ: 2

c: 2

e: 2

h: 2

k: 2

l: 4

o: 2

s: 2

t: 2

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

 #include <iostream>
#include <vector>
#include <numeric>
using namespace std;

int numA(string s){
    int count = 0;
    for(int i = 0; i < s.size(); i  ){
        if(s[i] == 'a' || s[i] == 'A'){
            count  ;
        }
    }
    return count;
}

int numB(string s){
    int count = 0;
    for(int i = 0; i < s.size(); i  ){
        if(s[i] == 'b' || s[i] == 'B'){
            count  ;
        }
    }
    return count;
}

int main(){

    vector<string> words;
    string str;

    while(cin >> str){
        cin >> str;
        words.push_back(str);
    }
    str = accumulate(begin(words), end(words), str);


    cout << str << endl;
    cout << "a: " << numA(str) << endl;
    cout << "b: " << numB(str) << endl;
}
  

Прямо сейчас я тестирую только количество букв A, a, B и b, но по какой-то причине иногда он выдает правильное число, но в других случаях он выводит несколько меньше, чем предполагалось, или иногда вообще ничего, даже если явно естьв нем есть a или b.

У меня такое чувство, что это как-то связано с накоплением, но я не уверен в обратном. Если кто-нибудь может объяснить мне, что может пойти не так, я был бы очень признателен за помощь. Спасибо!

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

1. Что в вашем учебнике по C говорится о том, что делает третий параметр для этой конкретной перегрузки std::accumulate , и почему вы думаете, что вам это нужно?

2. Вы уверены, что нет другого способа решить эту проблему… запись каждой функции num_a, num_A, num_b, num_B, num_c, num_C и т. Д. Будет работать, Но, конечно, есть лучший способ? чтобы вы могли отслеживать все буквы, которые вы видели, и увеличивать значение?

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

4. @jackw11111 я уверен, что есть, но я понятия не имею, каким будет этот путь.

5. к сожалению:/ . я все же пытаюсь взглянуть на светлую сторону ситуации. поскольку он на самом деле не дает нам достаточных ресурсов, я прибегаю к этому веб-сайту и Google в целом, чтобы он научил меня новым и часто более простым способам решения проблем. есть ли у вас какие-либо рекомендации по (дешевым, смехотворным) учебникам?

Ответ №1:

Есть несколько проблем, которые вы упускаете. Основной из них заключается в том, что всякий раз, когда вам нужно знать частоту, в пределах которой встречается любое количество объектов, вам просто нужен частотный массив, содержащий количество элементов (по одному для каждого объекта в диапазоне, который вам нужно измерить), причем каждый элемент изначально равен нулю. Когда вы просто смотрите на частоту символов, все, что нужно, — это простой массив из 26 int (по одному для каждого символа в алфавите). Значение ASCII позволяет просто преобразовать символ в индекс массива.

Если у вас более сложный тип, который не обеспечивает простой способ сопоставления value => index , то вы можете использовать что-то, что позволяет хранить это количество пар, например a std::unordered_set или подобное.

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

В целом, вы могли бы сделать что-то похожее на:

 #include <iostream>
#include <string>
#include <vector>
#include <cctype>

#define NCHAR 26

int main () {
    
    std::string s {};                       /* string */
    std::vector<std::string> vs {};         /* vector of strings */
    int lower[NCHAR] = {0};                 /* frequency array - initialized all zero */
    
    while (std::cin >> s)                   /* read each word */
        vs.push_back(s);                    /* add to vector */
    
    if (vs.size() == 0)                     /* validate at least 1 word stored */
        return 1;
    
    for (const autoamp; w : vs)                /* for each word in vector of strings */
        for (const autoamp; c : w)             /* for each char in word */
            if (isalpha(c))                 /* if [a-zA-Z] */
                lower[tolower(c)-'a']  ;    /* convert tolower, increment index */
        
    for (int i = 0; i < NCHAR; i  )         /* loop over frequency array */
        if (lower[i])                       /* if element not zero, output result */
            std::cout << (char)(i 'a') << ": " << lower[i] << 'n';
}
  

Пример использования / вывода

Использование простого heredoc для передачи слов в вашем примере в программу приведет к следующему:

 $ ./bin/vectstr_frequency << 'eof'
> Hello
> hello
> Stack
> stAck
> eof
a: 2
c: 2
e: 2
h: 2
k: 2
l: 4
o: 2
s: 2
t: 2
  

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

(на основе ваших комментариев и для обеспечения того, чтобы ответы не были удалены при очистке комментариев Sam, следующая дополнительная информация предоставляется как часть ответа)

Объясняется частотный массив

Чтобы лучше понять, как заполняется частотный массив и как значения сопоставляются с индексами, а затем индексы сопоставляются обратно со значениями, возьмите char ch; для удержания текущего символа. Если ch == 'd'; затем ch - 'a' сопоставляет текущий символ 'd' с индексом 3 . Как? Таблица ASCII, 'd' - 'a' 100 - 97 которая есть 3 . (таким образом, ваше сопоставление просто вычитает значение для первого объекта в диапазоне из текущего значения, гарантируя, что первый объект в вашем диапазоне соответствует первому элементу вашего частотного массива)

При выводе результатов, например for (int i = 0; i < NCHAR; i ) , i представляет индекс частотного массива, который будет 0, 1, 2, ... таким образом отображаться в противоположном направлении, символ, представленный индексом, будет i 'a' . Например, когда индекс равен 3 , у вас есть 3 97 100 , который соответствует символу ASCII 'd' . Таким образом, вы вычитаете 'a' , чтобы сопоставить с индексом, и добавляете то же смещение 'a' (97) к индексу, чтобы сопоставить индекс обратно с символом (значением в вашем диапазоне).

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

Чтобы определить частоту символов ' ' для '/'

У вас есть 16 непрерывных значений в вашем диапазоне, символы ASCII от ' ' (32) до '/' (47). таким образом, вам нужен частотный массив с 16 элементами, например int punct[16] = {0}; , для измерения вхождений знаков препинания в этом диапазоне. Затем вы можете проверить if (' ' <= c amp;amp; c <= '/') { /* process the punctuation */ } , где вы бы сопоставили символ с индексом c - ' ' , а затем вы бы сопоставили индекс обратно с символом i ' ' . Работает одинаково для любого последовательного диапазона объектов.

Но обратите внимание, что в этом случае вы не можете использовать std::cin >> s для чтения каждой строки, вы должны использовать getline(std::cin, s) для чтения строки за раз. Почему? Ваш новый диапазон содержит пробелы и >> перестает считываться, когда встречает a space . Таким образом, используя std::cin >> s; , вы никогда не будете читать пробелы.

Изменив входные данные для использования getline() и добавив punct[] частотный массив, вы могли бы сделать:

 #include <iostream>
#include <string>
#include <vector>
#include <cctype>

#define NPUNC 16        /* if you need a constant, #define one (or more) */
#define NCHAR 26

int main () {
    
    std::string s {};                       /* string */
    std::vector<std::string> vs {};         /* vector of strings */
    int lower[NCHAR] = {0},                 /* frequency array - initialized all zero */
        punct[NPUNC] = {0};                 /* frequency array - for ' ' - '/' */
    
    while (getline (std::cin, s))           /* read each line */
        vs.push_back(s);                    /* add to vector */
    
    if (vs.size() == 0)                     /* validate at least 1 line stored */
        return 1;
    
    for (const autoamp; l : vs)                /* for each line in vector of strings */
        for (const autoamp; c : l)             /* for each char in line */
            if (isalpha(c))                 /* if [a-zA-Z] */
                lower[tolower(c)-'a']  ;    /* convert tolower, increment index */
            else if (' ' <= c amp;amp; c <= '/')  /* if ' ' through '/' */
                punct[c-' ']  ;             /* increment corresponding punct index */
    
    for (int i = 0; i < NPUNC; i  )         /* loop over punct frequency array */
        if (punct[i])                       /* if element not zero, output result */
            std::cout << (char)(i ' ') << ": " << punct[i] << 'n';
    
    std::cout.put('n');
    
    for (int i = 0; i < NCHAR; i  )         /* loop over frequency array */
        if (lower[i])                       /* if element not zero, output result */
            std::cout << (char)(i 'a') << ": " << lower[i] << 'n';
}
  

Пример использования / вывода

 $ echo 'My *#$/#%%!! Dog Has Fleas!!' | ./bin/vectstr_frequency punct
 : 4
!: 4
#: 2
$: 1
%: 2
*: 1
/: 1

a: 2
d: 1
e: 1
f: 1
g: 1
h: 1
l: 1
m: 1
o: 1
s: 2
y: 1
  

(примечание: если вы используете printf in bash вместо echo , то "%%" будет учитываться как один '%' — убедитесь, что вы понимаете, почему ( man 3 printf подойдет))

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

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

1. Для меня это сработало отлично! Большое вам спасибо!!!! Мне интересно, как это работает. То есть, по сути, вместо преобразования всех векторных входных данных в строку он помещает их в массив, а затем присваивает каждому символу его собственный индекс? Однако что вы подразумеваете под увеличением элемента?

2. Вектор содержит каждую из строк (слов). for Циклы на основе диапазона перебирают каждое слово (внешний цикл), а затем внутренний цикл перебирает каждый символ в текущем слове. «Увеличение элемента» означает это. Для алфавита сопоставляются индексы элементов массива частот 0 - 'a' , 1 - 'b' , 2 - 'c' , … и так далее. Таким образом, если текущий символ является 'a' элементом 0 , увеличивается (что означает, что 1 — 'a' был подсчитан). После того, как вы проделаете то же самое для всех символов в каждом слове, массив частот содержит частоту появления каждого символа.

3. Используйте char ch для удержания текущего символа. Если ch = 'd'; затем ch - 'a' сопоставляет текущий символ 'd' с индексом 3 . Как? Таблица ASCII , 'd' - 'a' == 100 - 97 == 3 . При выводе результатов, for (int i = 0; i < NCHAR; i ) , i будет 0, 1, 2, ... так, чтобы отобразить в противоположном направлении символ, представленный индексом, будет i 'a' . Например, когда индекс равен 3 , у вас есть 3 97 100 , который соответствует символу ASCII d . Таким образом, вы вычитаете 'a' , чтобы сопоставить с индексом, и добавляете 'a' , чтобы сопоставить обратно с символом.

4. о, хорошо. теперь скажите, что я хотел бы также включить специальные символы, такие как «пробел», через «/». не могли бы вы реализовать способ проверки тех, кто использует этот метод? я думаю, наряду с другими входными данными, такими как числа внутри строки и т.д. я знаю, что isalpha проверяет наличие букв, но существует ли аналогичный метод для специальных символов и чисел?

5. Хорошо, у вас есть 16 элементов в вашем диапазоне, символы ASCII от ' ' (32) до '/' (47). таким образом, вы просто создаете int punct[16] = {0}; в качестве своего частотного массива для измерения вхождений знаков препинания в этом диапазоне. Затем вы можете проверить if (' ' <= c amp;amp; c <= '/') { /* process the punctuation */ } , где вы бы сопоставили символ с индексом c - ' ' , а затем вы бы сопоставили индекс обратно с символом i ' ' . Работает для любого последовательного диапазона объектов :) . Запустите аннотированный массив частот , который добавляет выходные данные, чтобы помочь ускорить процесс.