Intro
(This post is the continuation of Get histogram of bytes in any set of files in C++14.)
This time I have added some ASCII art for visualizing the histogram. Also, if the program is invoked without command line arguments, it will expect the data from std::cin
.
Code
#include <algorithm>
#include <array>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <ios>
#include <istream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using std::array;
using std::cin;
using std::cout;
using std::ifstream;
using std::istream;
using std::string;
using std::stringstream;
using std::vector;
static constexpr size_t TERMINAL_WIDTH = 80;
class ByteHistogram {
private:
static constexpr size_t HISTOGRAM_WIDTH_ = 0x100;
typedef array<size_t, HISTOGRAM_WIDTH_> Histogram;
Histogram histogram_;
friend std::ostream& operator<<(std::ostream& os,
const ByteHistogram& hist);
public:
ByteHistogram() {
std::fill(histogram_.begin(),
histogram_.end(),
0);
}
void count(unsigned char ch) {
histogram_[ch]++;
}
Histogram::const_iterator cbegin() const {
return histogram_.cbegin();
}
Histogram::const_iterator cend() const {
return histogram_.cend();
}
} byteHistogram; // Instantiate a global object.
static size_t computeMaximumCount(const ByteHistogram& histogram) {
size_t maximumCount = 0;
for (auto it = histogram.cbegin(); it != histogram.cend(); it++) {
maximumCount = std::max(maximumCount, *it);
}
return maximumCount;
}
static size_t computeCounterStringLength(const size_t maximumCount) {
stringstream ss;
ss << maximumCount;
return ss.str().length();
}
static string computeBar(const size_t count,
const size_t maximumCount,
const size_t maximumCountLength) {
const size_t availableSpace = TERMINAL_WIDTH - 7 - maximumCountLength;
const size_t barLength = (size_t)
availableSpace * (((float) count)
/ ((float) maximumCount));
stringstream ss;
for (size_t i = 0; i < barLength; i++) {
ss << '*';
}
return ss.str();
}
std::ostream& operator<<(std::ostream& os,
const ByteHistogram& hist) {
const size_t maximumCount = computeMaximumCount(byteHistogram);
const size_t countStringLength = computeCounterStringLength(maximumCount);
stringstream ss;
ss << "0x%02x: %" << countStringLength << "d %s\n";
const string formatString = ss.str();
const char* cFormatString = formatString.c_str();
for (size_t i = 0; i != ByteHistogram::HISTOGRAM_WIDTH_; i++) {
const size_t count = hist.histogram_[i];
const string bar = computeBar(count,
maximumCount,
countStringLength);
std::printf(cFormatString,
static_cast<unsigned char>(i),
count,
bar.c_str());
}
return os;
}
static void processCin();
static void processFile(const string& fileName);
static vector<string> getFileNames(const int argc, char* argv[]);
int main(int argc, char* argv[])
{
if (argc == 1) {
processCin();
return EXIT_SUCCESS;
}
vector<string> fileNames = getFileNames(argc, argv);
for (auto it = fileNames.cbegin(); it != fileNames.cend(); it++) {
processFile(*it);
}
cout << byteHistogram;
return EXIT_SUCCESS;
}
static vector<string> getFileNames(const int argc,
char* argv[]) {
vector<string> fileNames;
for (size_t i = 1; i < argc; i++) {
fileNames.push_back(string(argv[i]));
}
return fileNames;
}
static void processCin() {
while (!cin.eof()) {
const unsigned char ch = cin.get();
if (cin.fail() || cin.bad()) {
break;
}
byteHistogram.count(ch);
}
cout << byteHistogram;
}
static void throwOnFailedFile(const string& fileName) {
std::ios_base::failure exception("Could not process file \"" + fileName + "\".");
throw exception;
}
static void processFile(const string& fileName) {
ifstream stream(fileName, std::ios::binary);
while (!stream.eof()) {
unsigned char ch;
ch = static_cast<unsigned char>(stream.get());
if (!stream.good() || stream.fail()) {
break;
}
byteHistogram.count(ch);
}
stream.close();
}
Typical output
C:\Users\rodio\OneDrive\Desktop>type SkipList.java | ByteHistogram.cpp.exe
0x00: 0
0x01: 0
0x02: 0
0x03: 0
0x04: 0
0x05: 0
0x06: 0
0x07: 0
0x08: 0
0x09: 1
0x0a: 233 ****
0x0b: 0
0x0c: 0
0x0d: 0
0x0e: 0
0x0f: 0
0x10: 0
0x11: 0
0x12: 0
0x13: 0
0x14: 0
0x15: 0
0x16: 0
0x17: 0
0x18: 0
0x19: 0
0x1a: 0
0x1b: 0
0x1c: 0
0x1d: 0
0x1e: 0
0x1f: 0
0x20: 3313 *********************************************************************
0x21: 6
0x22: 14
0x23: 0
0x24: 0
0x25: 0
0x26: 0
0x27: 46
0x28: 63 *
0x29: 63 *
0x2a: 4
0x2b: 53 *
0x2c: 11
0x2d: 16
0x2e: 63 *
0x2f: 8
0x30: 8
0x31: 22
0x32: 4
0x33: 2
0x34: 3
0x35: 3
0x36: 0
0x37: 0
0x38: 0
0x39: 0
0x3a: 8
0x3b: 103 **
0x3c: 27
0x3d: 74 *
0x3e: 27
0x3f: 3
0x40: 3
0x41: 5
0x42: 4
0x43: 38
0x44: 0
0x45: 0
0x46: 0
0x47: 0
0x48: 4
0x49: 59 *
0x4a: 0
0x4b: 24
0x4c: 12
0x4d: 49 *
0x4e: 12
0x4f: 8
0x50: 3
0x51: 0
0x52: 19
0x53: 20
0x54: 13
0x55: 0
0x56: 1
0x57: 2
0x58: 0
0x59: 0
0x5a: 0
0x5b: 57 *
0x5c: 11
0x5d: 57 *
0x5e: 0
0x5f: 0
0x60: 0
0x61: 211 ****
0x62: 21
0x63: 75 *
0x64: 140 **
0x65: 296 ******
0x66: 15
0x67: 61 *
0x68: 110 **
0x69: 187 ***
0x6a: 10
0x6b: 14
0x6c: 123 **
0x6d: 16
0x6e: 242 *****
0x6f: 152 ***
0x70: 56 *
0x71: 2
0x72: 242 *****
0x73: 86 *
0x74: 323 ******
0x75: 44
0x76: 46
0x77: 43
0x78: 151 ***
0x79: 26
0x7a: 1
0x7b: 33
0x7c: 5
0x7d: 33
0x7e: 0
0x7f: 0
0x80: 0
0x81: 0
0x82: 0
0x83: 0
0x84: 0
0x85: 0
0x86: 0
0x87: 0
0x88: 0
0x89: 0
0x8a: 0
0x8b: 0
0x8c: 0
0x8d: 0
0x8e: 0
0x8f: 0
0x90: 0
0x91: 0
0x92: 0
0x93: 0
0x94: 0
0x95: 0
0x96: 0
0x97: 0
0x98: 0
0x99: 0
0x9a: 0
0x9b: 0
0x9c: 0
0x9d: 0
0x9e: 0
0x9f: 0
0xa0: 0
0xa1: 0
0xa2: 0
0xa3: 0
0xa4: 0
0xa5: 0
0xa6: 0
0xa7: 0
0xa8: 0
0xa9: 0
0xaa: 0
0xab: 0
0xac: 0
0xad: 0
0xae: 0
0xaf: 0
0xb0: 0
0xb1: 0
0xb2: 0
0xb3: 0
0xb4: 0
0xb5: 0
0xb6: 0
0xb7: 0
0xb8: 0
0xb9: 0
0xba: 0
0xbb: 0
0xbc: 0
0xbd: 0
0xbe: 0
0xbf: 0
0xc0: 0
0xc1: 0
0xc2: 0
0xc3: 0
0xc4: 0
0xc5: 0
0xc6: 0
0xc7: 0
0xc8: 0
0xc9: 0
0xca: 0
0xcb: 0
0xcc: 0
0xcd: 0
0xce: 0
0xcf: 0
0xd0: 0
0xd1: 0
0xd2: 0
0xd3: 0
0xd4: 0
0xd5: 0
0xd6: 0
0xd7: 0
0xd8: 0
0xd9: 0
0xda: 0
0xdb: 0
0xdc: 0
0xdd: 0
0xde: 0
0xdf: 0
0xe0: 0
0xe1: 0
0xe2: 0
0xe3: 0
0xe4: 0
0xe5: 0
0xe6: 0
0xe7: 0
0xe8: 0
0xe9: 0
0xea: 0
0xeb: 0
0xec: 0
0xed: 0
0xee: 0
0xef: 0
0xf0: 0
0xf1: 0
0xf2: 0
0xf3: 0
0xf4: 0
0xf5: 0
0xf6: 0
0xf7: 0
0xf8: 0
0xf9: 0
0xfa: 0
0xfb: 0
0xfc: 0
0xfd: 0
0xfe: 0
0xff: 0
Critique request
Am I getting anywhere? Please tell me whatever comes to mind.
std::max_element
as indicated in the answer, why writefor (auto it = histogram.cbegin(); it != histogram.cend(); it++) { maximumCount = std::max(maximumCount, *it); }
when you can use a range-based for loop?for (const auto& x : histogram) { maximumCount = std::max(maximumCount, x); }
\$\endgroup\$0x4d
(49) shows up as a single splat, but0x27
(46) is blank... The user is left to guess the value of what a splat represents... And, it seems that anything less than 47 (or 49) instances equates to zero instances... Kinda yechy... Suggest that0
means zero, but1-48
be reported with a single splat... Instances are present in low quantities... Also, might consider adding actual ASCII characters if/when possible so user doesn't have to consult ASCII table to lookup hexadecimal codes (like Mark Watney had to...) \$\endgroup\$