#ifndef COUNT_MIN_IMPL_HPP_
#define COUNT_MIN_IMPL_HPP_
#include <algorithm>
#include <iomanip>
#include <random>
#include <sstream>
#include "MurmurHash3.h"
#include "count_min.hpp"
#include "memory_operations.hpp"
namespace datasketches {
template<typename W, typename A>
count_min_sketch<W,A>::count_min_sketch(uint8_t num_hashes, uint32_t num_buckets, uint64_t seed, const A& allocator):
_allocator(allocator),
_num_hashes(num_hashes),
_num_buckets(num_buckets),
_sketch_array((num_hashes*num_buckets < 1<<30) ? num_hashes*num_buckets : 0, 0, _allocator),
_seed(seed),
_total_weight(0) {
if (num_buckets < 3) throw std::invalid_argument("Using fewer than 3 buckets incurs relative error greater than 1.");
if (num_buckets * num_hashes >= 1 << 30) {
throw std::invalid_argument("These parameters generate a sketch that exceeds 2^30 elements."
"Try reducing either the number of buckets or the number of hash functions.");
}
std::default_random_engine rng(_seed);
std::uniform_int_distribution<uint64_t> extra_hash_seeds(0, std::numeric_limits<uint64_t>::max());
hash_seeds.reserve(num_hashes);
for (uint64_t i=0; i < num_hashes; ++i) {
hash_seeds.push_back(extra_hash_seeds(rng) + _seed); }
}
template<typename W, typename A>
uint8_t count_min_sketch<W,A>::get_num_hashes() const {
return _num_hashes;
}
template<typename W, typename A>
uint32_t count_min_sketch<W,A>::get_num_buckets() const {
return _num_buckets;
}
template<typename W, typename A>
uint64_t count_min_sketch<W,A>::get_seed() const {
return _seed;
}
template<typename W, typename A>
double count_min_sketch<W,A>::get_relative_error() const {
return exp(1.0) / double(_num_buckets);
}
template<typename W, typename A>
W count_min_sketch<W,A>::get_total_weight() const {
return _total_weight;
}
template<typename W, typename A>
uint32_t count_min_sketch<W,A>::suggest_num_buckets(double relative_error) {
if (relative_error < 0.) {
throw std::invalid_argument("Relative error must be at least 0.");
}
return static_cast<uint32_t>(ceil(exp(1.0) / relative_error));
}
template<typename W, typename A>
uint8_t count_min_sketch<W,A>::suggest_num_hashes(double confidence) {
if (confidence < 0. || confidence > 1.0) {
throw std::invalid_argument("Confidence must be between 0 and 1.0 (inclusive).");
}
return std::min<uint8_t>(ceil(log(1.0 / (1.0 - confidence))), UINT8_MAX);
}
template<typename W, typename A>
std::vector<uint64_t> count_min_sketch<W,A>::get_hashes(const void* item, size_t size) const {
uint64_t bucket_index;
std::vector<uint64_t> sketch_update_locations;
sketch_update_locations.reserve(_num_hashes);
uint64_t hash_seed_index = 0;
for (const auto &it: hash_seeds) {
HashState hashes;
MurmurHash3_x64_128(item, size, it, hashes); uint64_t hash = hashes.h1;
bucket_index = hash % _num_buckets;
sketch_update_locations.push_back((hash_seed_index * _num_buckets) + bucket_index);
hash_seed_index += 1;
}
return sketch_update_locations;
}
template<typename W, typename A>
W count_min_sketch<W,A>::get_estimate(uint64_t item) const {return get_estimate(&item, sizeof(item));}
template<typename W, typename A>
W count_min_sketch<W,A>::get_estimate(int64_t item) const {return get_estimate(&item, sizeof(item));}
template<typename W, typename A>
W count_min_sketch<W,A>::get_estimate(const std::string& item) const {
if (item.empty()) return 0; return get_estimate(item.c_str(), item.length());
}
template<typename W, typename A>
W count_min_sketch<W,A>::get_estimate(const void* item, size_t size) const {
std::vector<uint64_t> hash_locations = get_hashes(item, size);
std::vector<W> estimates;
for (const auto h: hash_locations) {
estimates.push_back(_sketch_array[h]);
}
return *std::min_element(estimates.begin(), estimates.end());
}
template<typename W, typename A>
void count_min_sketch<W,A>::update(uint64_t item, W weight) {
update(&item, sizeof(item), weight);
}
template<typename W, typename A>
void count_min_sketch<W,A>::update(int64_t item, W weight) {
update(&item, sizeof(item), weight);
}
template<typename W, typename A>
void count_min_sketch<W,A>::update(const std::string& item, W weight) {
if (item.empty()) return;
update(item.c_str(), item.length(), weight);
}
template<typename W, typename A>
void count_min_sketch<W,A>::update(const void* item, size_t size, W weight) {
_total_weight += weight >= 0 ? weight : -weight;
std::vector<uint64_t> hash_locations = get_hashes(item, size);
for (const auto h: hash_locations) {
_sketch_array[h] += weight;
}
}
template<typename W, typename A>
W count_min_sketch<W,A>::get_upper_bound(uint64_t item) const {return get_upper_bound(&item, sizeof(item));}
template<typename W, typename A>
W count_min_sketch<W,A>::get_upper_bound(int64_t item) const {return get_upper_bound(&item, sizeof(item));}
template<typename W, typename A>
W count_min_sketch<W,A>::get_upper_bound(const std::string& item) const {
if (item.empty()) return 0; return get_upper_bound(item.c_str(), item.length());
}
template<typename W, typename A>
W count_min_sketch<W,A>::get_upper_bound(const void* item, size_t size) const {
return static_cast<W>(get_estimate(item, size) + get_relative_error() * get_total_weight());
}
template<typename W, typename A>
W count_min_sketch<W,A>::get_lower_bound(uint64_t item) const {return get_lower_bound(&item, sizeof(item));}
template<typename W, typename A>
W count_min_sketch<W,A>::get_lower_bound(int64_t item) const {return get_lower_bound(&item, sizeof(item));}
template<typename W, typename A>
W count_min_sketch<W,A>::get_lower_bound(const std::string& item) const {
if (item.empty()) return 0; return get_lower_bound(item.c_str(), item.length());
}
template<typename W, typename A>
W count_min_sketch<W,A>::get_lower_bound(const void* item, size_t size) const {
return get_estimate(item, size);
}
template<typename W, typename A>
void count_min_sketch<W,A>::merge(const count_min_sketch &other_sketch) {
if (this == &other_sketch) {
throw std::invalid_argument( "Cannot merge a sketch with itself." );
}
bool acceptable_config =
(get_num_hashes() == other_sketch.get_num_hashes()) &&
(get_num_buckets() == other_sketch.get_num_buckets()) &&
(get_seed() == other_sketch.get_seed());
if (!acceptable_config) {
throw std::invalid_argument( "Incompatible sketch configuration." );
}
auto it = _sketch_array.begin(); auto other_it = other_sketch.begin(); while (it != _sketch_array.end()) {
*it += *other_it;
++it;
++other_it;
}
_total_weight += other_sketch.get_total_weight();
}
template<typename W, typename A>
typename count_min_sketch<W,A>::const_iterator count_min_sketch<W,A>::begin() const {
return _sketch_array.begin();
}
template<typename W, typename A>
typename count_min_sketch<W,A>::const_iterator count_min_sketch<W,A>::end() const {
return _sketch_array.end();
}
template<typename W, typename A>
void count_min_sketch<W,A>::serialize(std::ostream& os) const {
const uint8_t preamble_longs = PREAMBLE_LONGS_SHORT;
const uint8_t ser_ver = SERIAL_VERSION_1;
const uint8_t family_id = FAMILY_ID;
const uint8_t flags_byte = (is_empty() ? 1 << flags::IS_EMPTY : 0);
const uint32_t unused32 = NULL_32;
write(os, preamble_longs);
write(os, ser_ver);
write(os, family_id);
write(os, flags_byte);
write(os, unused32);
const uint32_t nbuckets = _num_buckets;
const uint8_t nhashes = _num_hashes;
const uint16_t seed_hash(compute_seed_hash(_seed));
const uint8_t unused8 = NULL_8;
write(os, nbuckets);
write(os, nhashes);
write(os, seed_hash);
write(os, unused8);
if (is_empty()) return;
write(os, _total_weight);
auto it = _sketch_array.begin();
while (it != _sketch_array.end()) {
write(os, *it);
++it;
}
}
template<typename W, typename A>
auto count_min_sketch<W,A>::deserialize(std::istream& is, uint64_t seed, const A& allocator) -> count_min_sketch {
const auto preamble_longs = read<uint8_t>(is);
const auto serial_version = read<uint8_t>(is);
const auto family_id = read<uint8_t>(is);
const auto flags_byte = read<uint8_t>(is);
read<uint32_t>(is);
check_header_validity(preamble_longs, serial_version, family_id, flags_byte);
const auto nbuckets = read<uint32_t>(is);
const auto nhashes = read<uint8_t>(is);
const auto seed_hash = read<uint16_t>(is);
read<uint8_t>(is);
if (seed_hash != compute_seed_hash(seed)) {
throw std::invalid_argument("Incompatible seed hashes: " + std::to_string(seed_hash) + ", "
+ std::to_string(compute_seed_hash(seed)));
}
count_min_sketch c(nhashes, nbuckets, seed, allocator);
const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
if (is_empty == 1) return c;
const auto weight = read<W>(is);
c._total_weight += weight;
read(is, c._sketch_array.data(), sizeof(W) * c._sketch_array.size());
return c;
}
template<typename W, typename A>
size_t count_min_sketch<W,A>::get_serialized_size_bytes() const {
const size_t preamble_longs = PREAMBLE_LONGS_SHORT;
return (preamble_longs * sizeof(uint64_t)) + (is_empty() ? 0 : sizeof(W) * (1 + _num_buckets * _num_hashes));
}
template<typename W, typename A>
auto count_min_sketch<W,A>::serialize(unsigned header_size_bytes) const -> vector_bytes {
vector_bytes bytes(header_size_bytes + get_serialized_size_bytes(), 0, _allocator);
uint8_t *ptr = bytes.data() + header_size_bytes;
const uint8_t preamble_longs = PREAMBLE_LONGS_SHORT;
ptr += copy_to_mem(preamble_longs, ptr);
const uint8_t ser_ver = SERIAL_VERSION_1;
ptr += copy_to_mem(ser_ver, ptr);
const uint8_t family_id = FAMILY_ID;
ptr += copy_to_mem(family_id, ptr);
const uint8_t flags_byte = (is_empty() ? 1 << flags::IS_EMPTY : 0);
ptr += copy_to_mem(flags_byte, ptr);
const uint32_t unused32 = NULL_32;
ptr += copy_to_mem(unused32, ptr);
const uint32_t nbuckets = _num_buckets;
const uint8_t nhashes = _num_hashes;
const uint16_t seed_hash(compute_seed_hash(_seed));
const uint8_t null_characters_8 = NULL_8;
ptr += copy_to_mem(nbuckets, ptr);
ptr += copy_to_mem(nhashes, ptr);
ptr += copy_to_mem(seed_hash, ptr);
ptr += copy_to_mem(null_characters_8, ptr);
if (is_empty()) return bytes;
const W t_weight = _total_weight;
ptr += copy_to_mem(t_weight, ptr);
auto it = _sketch_array.begin();
while (it != _sketch_array.end()) {
ptr += copy_to_mem(*it, ptr);
++it;
}
return bytes;
}
template<typename W, typename A>
auto count_min_sketch<W,A>::deserialize(const void* bytes, size_t size, uint64_t seed, const A& allocator) -> count_min_sketch {
ensure_minimum_memory(size, PREAMBLE_LONGS_SHORT * sizeof(uint64_t));
const char* ptr = static_cast<const char*>(bytes);
uint8_t preamble_longs;
ptr += copy_from_mem(ptr, preamble_longs);
uint8_t serial_version;
ptr += copy_from_mem(ptr, serial_version);
uint8_t family_id;
ptr += copy_from_mem(ptr, family_id);
uint8_t flags_byte;
ptr += copy_from_mem(ptr, flags_byte);
ptr += sizeof(uint32_t);
check_header_validity(preamble_longs, serial_version, family_id, flags_byte);
uint32_t nbuckets;
uint8_t nhashes;
uint16_t seed_hash;
ptr += copy_from_mem(ptr, nbuckets);
ptr += copy_from_mem(ptr, nhashes);
ptr += copy_from_mem(ptr, seed_hash);
ptr += sizeof(uint8_t);
if (seed_hash != compute_seed_hash(seed)) {
throw std::invalid_argument("Incompatible seed hashes: " + std::to_string(seed_hash) + ", "
+ std::to_string(compute_seed_hash(seed)));
}
count_min_sketch c(nhashes, nbuckets, seed, allocator);
const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
if (is_empty) return c;
ensure_minimum_memory(size, sizeof(W) * (1 + nbuckets * nhashes));
W weight;
ptr += copy_from_mem(ptr, weight);
c._total_weight += weight;
for (size_t i = 0; i<c._num_buckets*c._num_hashes; ++i) {
ptr += copy_from_mem(ptr, c._sketch_array[i]);
}
return c;
}
template<typename W, typename A>
bool count_min_sketch<W,A>::is_empty() const {
return _total_weight == 0;
}
template<typename W, typename A>
string<A> count_min_sketch<W,A>::to_string() const {
uint64_t num_nonzero = 0;
for (const auto entry: _sketch_array) {
if (entry != static_cast<W>(0.0))
++num_nonzero;
}
std::ostringstream os;
os << "### Count Min sketch summary:" << std::endl;
os << " num hashes : " << static_cast<uint32_t>(_num_hashes) << std::endl;
os << " num buckets : " << _num_buckets << std::endl;
os << " capacity bins : " << _sketch_array.size() << std::endl;
os << " filled bins : " << num_nonzero << std::endl;
os << " pct filled : " << std::setprecision(3) << (num_nonzero * 100.0) / _sketch_array.size() << "%" << std::endl;
os << "### End sketch summary" << std::endl;
return string<A>(os.str().c_str(), _allocator);
}
template<typename W, typename A>
void count_min_sketch<W,A>::check_header_validity(uint8_t preamble_longs, uint8_t serial_version, uint8_t family_id, uint8_t flags_byte) {
const bool empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
const uint8_t sw = (empty ? 1 : 0) + (2 * serial_version) + (4 * family_id) + (32 * (preamble_longs & 0x3F));
bool valid = true;
switch (sw) { case 138 : break; case 139 : break; default : valid = false;
}
if (!valid) {
std::ostringstream os;
os << "Possible sketch corruption. Inconsistent state: "
<< "preamble_longs = " << static_cast<uint32_t>(preamble_longs)
<< ", empty = " << (empty ? "true" : "false")
<< ", serialization_version = " << static_cast<uint32_t>(serial_version);
throw std::invalid_argument(os.str());
}
}
}
#endif