Answers are given and one is accepted. I would like to give an additional answer showing the standard approach for this task.
There is often the requirement to first count things and then to get back their rank or some topmost value or other information.
One of the most common solution is to use a so called associative container for that, and, here specifically, a std::map or even better a std::unordered_map. This, because we need a key value, in the above described way a letter and an associted value, here the count for this letter. The key is unique. There cannot be more than one of the same letter in it. This would of course not make any sense.
Associative containers are very efficient by accessing their elements by their key value.
OK, there are 2 of them. The std::map and the std::unordered_map. One uses a tree to store the key in a sorted manner and the other use fast hashing algorithms to access the key values. Since we are later not interested in sorted keys, but in sorted count of occurence, we can choose the std::unordred_map. As a futher benefit, this will use fast the hashing algorithms mentioned to access a key.
The maps have an additional huge advantage. The have an index operator [], that will look very fast for a key value. If found, it will return a reference to the value associated with the key. If not found, it will create a key and initialize its value with the default (0 in our case). And then counting of any key is as simple as map[key]++.
But then, later, we here often hear: But it must be sorted by the count. That does of course not work, because the count my have duplicate values, and the map can only contain unique key values. So, impossible.
The solution is to use a second associative container a std::multiset which can have more of the same keys and a custome sort operator, where we can sort according to the value. In this we store the not a key and a value as 2 elements, but a std::pair with both values. And we sort by the 2nd part of the pair.
We cannot use a std::multi:set in the first place, because we need the unique key (in this case the letter).
The above described approach gives us extreme flexibility and ease of use. We can basically count anything with this algorithm
It could for example look the the below compact code:
#include <iostream>
#include <string>
#include <utility>
#include <set>
#include <unordered_map>
#include <type_traits>
#include <cctype>
// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair<char, unsigned int>;
// Standard approach for counter
using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;
// Sorted values will be stored in a multiset
struct Comp { bool operator ()(const Pair& p1, const Pair& p2) const { return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second; } };
using Rank = std::multiset<Pair, Comp>;
// ------------------------------------------------------------
// --------------------------------------------------------------------------------------
// Compact function to calculate the frequency of charcters and then get their rank
Rank getRank(std::string& text) {
// Definition of our counter
Counter counter{};
// Iterate over all charcters in text and count their frequency
for (const char c : text) if (std::isalpha(c)) counter[char(std::tolower(c))]++;
// Return ranks,sorted by frequency and then sorted by character
return { counter.begin(), counter.end() };
}
// --------------------------------------------------------------------------------------
// Test, driver code
int main() {
// Get a string from the user
if (std::string text{}; std::getline(std::cin, text))
// Calculate rank and show result
for (const auto& [letter, count] : getRank(text))
std::cout << letter << " = " << count << '\n';
}
Please see the minimal statements used. Very elegant.
But often we do see that arrays are use as an associted container. They have also an index (a key) and a value. Disadvantage may be a tine space overhead for unsued keys. Additionally the will only work for something wit a know magnitude. For example for 26 letters. Other countries alphabets may have more or less letters. Then this kind of solution would be not that flexible. Anyway it is also often used and OK.
So, your solution maybe a littel bit more complex, but will of course still work.
Let me give you an additional example for getting the topmost value of any container. Here you will see, how flexible such a solution can be.
I am sorry, but it is a little bit advanced. . .
#include <iostream>
#include <utility>
#include <unordered_map>
#include <queue>
#include <vector>
#include <iterator>
#include <type_traits>
#include <string>
// Helper for type trait We want to identify an iterable container ----------------------------------------------------
template <typename Container>
auto isIterableHelper(int) -> decltype (
std::begin(std::declval<Container&>()) != std::end(std::declval<Container&>()), // begin/end and operator !=
++std::declval<decltype(std::begin(std::declval<Container&>()))&>(), // operator ++
void(*std::begin(std::declval<Container&>())), // operator*
void(), // Handle potential operator ,
std::true_type{});
template <typename T>
std::false_type isIterableHelper(...);
// The type trait -----------------------------------------------------------------------------------------------------
template <typename Container>
using is_iterable = decltype(isIterableHelper<Container>(0));
// Some Alias names for later easier reading --------------------------------------------------------------------------
template <typename Container>
using ValueType = std::decay_t<decltype(*std::begin(std::declval<Container&>()))>;
template <typename Container>
using Pair = std::pair<ValueType<Container>, size_t>;
template <typename Container>
using Counter = std::unordered_map<ValueType<Container>, size_t>;
template <typename Container>
using UnderlyingContainer = std::vector<Pair<Container>>;
// Predicate Functor
template <class Container> struct LessForSecondOfPair {
bool operator () (const Pair<Container>& p1, const Pair<Container>& p2) { return p1.second < p2.second; }
};
template <typename Container>
using MaxHeap = std::priority_queue<Pair<Container>, UnderlyingContainer<Container>, LessForSecondOfPair<Container>>;
// Function to get most frequent used number in any Container ---------------------------------------------------------
template <class Container>
auto topFrequent(const Container& data) {
if constexpr (is_iterable<Container>::value) {
// Count all occurences of data
Counter<Container> counter{};
for (const auto& d : data) counter[d]++;
// Build a Max-Heap
MaxHeap<Container> maxHeap(counter.begin(), counter.end());
// Return most frequent number
return maxHeap.top().first;
}
else
return data;
}
// Test
int main() {
std::vector testVector{ 1,2,2,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,6,7 };
std::cout << "Most frequent is: " << topFrequent(testVector) << "\n";
double cStyleArray[] = { 1.1, 2.2, 2.2, 3.3, 3.3, 3.3 };
std::cout << "Most frequent is: " << topFrequent(cStyleArray) << "\n";
std::string s{ "abbcccddddeeeeeffffffggggggg" };
std::cout << "Most frequent is: " << topFrequent(s) << "\n";
double value = 12.34;
std::cout << "Most frequent is: " << topFrequent(value) << "\n";
return 0;
}