That long line of using
is problematic. First, it's far too long (it's not even sorted, which might explain why std::string
appears twice). Secondly, it looks like you've been told to avoid using namespace std;
but have heard only half the reason it's a bad idea. Those many using
s tend to obscure which identifiers in your code are from the library and which are your own. I really recommend you drop that habit and start referring to things by their full names most of the time.
size_t
and uint16_t
are never defined. I'm guessing you meant std::size_t
and std::uint16_t
(the latter from <cstdint>
header). It's not clear why you need an exactly-16-bit type for this simple counting - what's wrong with std::uint_fast16_t
? Or even a plain unsigned int
? Other identifiers not defined include exit
(presumably std::exit
from <cstdlib>
) and EXIT_SUCCESS
(also from <cstdlib>
).
The zuint
structure doesn't seem to provide any benefit over using the integer type directly.
The tmp
array doesn't need to be scoped to the whole function - it can be temporary, as the name suggests, within the read loop.
No need to write two separate literal strings to std::cerr
- combine them into a single string:
std::cerr << "File not opened. 'data.bin' probably does not exist.\n";
Perhaps consider std::perror
(from <cstdio>
) as an alternative error reporting function which provides more information.
The loop is flawed (testing for eof
before reading is a known anti-pattern). When istream::read()
fails, we haven't populated tmp
and shouldn't be adding an entry. Even when it fails, we're not defensive against malformed data (missing final null character). It's better to read into an oversize buffer and supply the null even if it's absent in the input.
Consider using std::unordered_map
instead of std::map
when we don't care about the order of its elements. That will likely be more performant when there are many different names in the file.
We can save writing long types in full, by taking advantage of auto
.
The comparator we pass to std::sort
can avoid copying strings, by accepting references to pairs.
We don't need to return success value at the end of main()
- the main function is magic, and simply running off the end implies a return value of 0.
Modified code
#include <algorithm>
#include <cerrno>
#include <cstdlib>
#include <cstring> // for strerror()
#include <fstream>
#include <iostream>
#include <unordered_map>
#include <vector>
constexpr std::size_t name_size = 20;
const char* file_name = "data.bin";
int main()
{
std::ifstream file(file_name, std::ios::in | std::ios::binary);
if (!file) {
std::cerr << file_name << ": " << std::strerror(errno) << '\n';
return EXIT_FAILURE;
}
std::unordered_map<std::string, unsigned int> points;
{
char name[name_size + 1];
name[name_size] = '\0';
while (file.read(name, name_size)) {
++points[name];
}
}
if (!file.eof()) {
std::cerr << file_name << ": " << std::strerror(errno) << '\n';
return EXIT_FAILURE;
}
std::vector<std::pair<std::string, unsigned int>>
pointscpy(points.begin(), points.end());
std::sort(pointscpy.begin(), pointscpy.end(),
[](auto const& v1, auto const& v2) {
return v1.second > v2.second;
});
int i = 0;
for (auto const& p: pointscpy) {
std::cout << ++i << ". " << p.first << ": " << p.second << '\n';
}
}
Future directions
Instead of hard-coding the file name in the program, accept it as a run-time argument, and/or accept input on the standard input stream so that it can be used as a filter.