Я ввожу файл символов, и каждое слово получает свое место в векторе. Затем мне нужно отслеживать каждое слово и выяснять, сколько раз каждое уникальное слово появлялось так, что:
Есть три дерева деревья деревья
должен вывести:
Там 1
1
три 1
деревья 3
Мне было интересно, как использовать вектор строк, чтобы сохранить тракт каждого слова.
Я сделал бы вектор строк с каждой строкой, имеющей вектор единственного целого?
Не забивайте гвоздь отверткой. std::vector
, не особенно полезен для этой задачи в ее самой основной форме: простой расчет частоты. Для произвольного ввода из стандартного ввода лучше всего использовать ассоциативный контейнер, где ключ — это строка ввода, а значение — накопленная частота.
Расчет неупорядоченной частоты
Неупорядоченный класс отображения, std::unordered_map
, ввод на std::string
и отображение на частотомер для этой строки, может использоваться для отслеживания базовой частоты. Например:
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
int main()
{
std::unordered_map<std::string, unsigned> m;
std::string word;
while (std::cin >> word)
++m[word]; // increment the count for this word
for (auto const& pr : m)
std::cout << pr.first << ':' << pr.second << '\n';
}
Лексикографическая упорядоченная частота
Примечание: нет конкретных порядок использовать ассоциативный контейнер std::unordered_map
(отсюда и название). Если вы хотите лексикографический порядок, вы можете просто использовать обычный std::map
. такие как:
#include <iostream>
#include <vector>
#include <string>
#include <map>
int main()
{
std::map<std::string, unsigned> m;
std::string word;
while (std::cin >> word)
++m[word];
for (auto const& pr : m)
std::cout << pr.first << ':' << pr.second << '\n';
}
Расчет частоты удержания положения
Поддержание того, где во входном потоке появляется слово при расчете счетчика частоты, также возможно, и занимает немного больше кода. Выберите неупорядоченный или упорядоченный ассоциативный контейнер, как мы делали раньше, но вместо сопоставления с unsigned
мы сопоставляем с std::vector<unsigned>
, где мы накапливаем счетчик слов при потреблении входных слов. Общий размер каждого вектора все еще сохраняет счетчик частоты, но сам вектор сохраняет позицию во входном потоке, в котором появляется соответствующее слово. Например:
#include <iostream>
#include <vector>
#include <string>
#include <map>
int main()
{
std::map<std::string, std::vector<unsigned int>> m;
std::string word;
unsigned ctr = 0;
while (std::cin >> word)
m[word].push_back(++ctr);
for (auto const& pr : m)
{
std::cout << pr.first << ':' << pr.second.size() << " { ";
for (auto pos : pr.second)
std::cout << pos << ' ';
std::cout << "}\n";
}
}
Это произведет вывод формы:
word : frequency { n1 n2 n3... }
где word
это отличное слово, frequency
общая частота во входном потоке, и n1,n2,n3,...
являются позициями (начиная с 1), где слово появилось во время обработки.
Надеюсь, один из этих методов полезен.
Вы можете использовать мультимножество классов из c ++, которое будет отслеживать, сколько раз вы добавляли каждое слово в набор. Также имейте в виду, что вы можете читать полные слова из потоков в c ++, и он будет автоматически пропускать любые пробелы.
Я буду читать из stdin для этого примера (обратите внимание, я не скомпилировал это, просто чтобы показать идею).
#include <set>
using namespace std;
int main(){
string word;
multiset<string> ocurrences;
while(cin >> word){
ocurrences.insert(word);
}
for(string w : ocurrences){ // Iterate over all words in the set
cout<<w<<" "<<counts.count(w)<<" ";
}
}
Как уже упоминалось в комментариях, если вы хотите напечатать слова в порядке первого вхождения, просто сохраните vector<string>
и добавьте каждое прочитанное слово, если его еще нет в наборе, а затем выполните итерацию по этому вектору вместо набора.
#include <set>
using namespace std;
int main(){
string word;
vector<string> words;
multiset<string> ocurrences;
while(cin >> word){
if(ocurrences.count(word) == 0) //Is this the first time we see this word?
words.push_back(word);
ocurrences.insert(word);
}
for(string w : words){ //Iterate over the words in the order
//they appeared in the input.
cout<<w<<" "<<ocurrences.count(w)<<" ";
}
}
Другое дело, что хотя мультимножество лучше подходит для решения этой конкретной проблемы, то, о чем вы спрашиваете в своем вопросе, называется картой, структурой данных, которая связывает ключ со значением (возможно, разных типов). C ++ уже имеет реализацию карты. В этом случае вам понадобится map<string, int>
связать каждое слово со временем, когда оно встречается.
Вот способ, которым вы можете сделать это, накапливая свой словарь по потоку слов и используя структурированные привязки C ++ 17:
int main()
{
std::istringstream words( "There are three trees trees trees" );
auto dic = std::accumulate(
std::istream_iterator< std::string >( words ) ,
std::istream_iterator< std::string >( ) ,
std::unordered_map< std::string , int >( ) ,
[]( auto && map , auto && word ) -> decltype( auto )
{
auto [ it , success ] = map.try_emplace(
std::forward< decltype( word ) >( word ) , 0 );
++ it->second;
return std::forward< decltype( map ) >( map );
} );
for ( const auto & [ key , value ] : dic )
{
std::cout << key << ": " << value << std::endl;
}
}
Жить в Колиру (хотя с некоторыми предупреждениями)
> trees: 3
> three: 1
> There: 1
> are: 1