20#ifndef COUNT_MIN_IMPL_HPP_
21#define COUNT_MIN_IMPL_HPP_
28#include "MurmurHash3.h"
29#include "count_min.hpp"
30#include "memory_operations.hpp"
34template<
typename W,
typename A>
37_num_hashes(num_hashes),
38_num_buckets(num_buckets),
39_sketch_array((static_cast<uint64_t>(num_hashes) * num_buckets < 1<<30) ? static_cast<size_t>(num_hashes) * num_buckets : 0, 0, _allocator),
42 if (num_buckets < 3) {
43 throw std::invalid_argument(
"Using fewer than 3 buckets incurs relative error greater than 1.");
48 if (
static_cast<uint64_t
>(num_buckets) * num_hashes >= 1 << 30) {
49 throw std::invalid_argument(
"These parameters generate a sketch that exceeds 2^30 elements."
50 "Try reducing either the number of buckets or the number of hash functions.");
53 std::default_random_engine rng(_seed);
54 std::uniform_int_distribution<uint64_t> extra_hash_seeds(0, std::numeric_limits<uint64_t>::max());
55 hash_seeds.reserve(num_hashes);
57 for (uint64_t i=0; i < num_hashes; ++i) {
58 hash_seeds.push_back(extra_hash_seeds(rng) + _seed);
62template<
typename W,
typename A>
67template<
typename W,
typename A>
72template<
typename W,
typename A>
77template<
typename W,
typename A>
79 return exp(1.0) /
static_cast<double>(_num_buckets);
82template<
typename W,
typename A>
87template<
typename W,
typename A>
93 if (relative_error < 0.) {
94 throw std::invalid_argument(
"Relative error must be at least 0.");
96 return static_cast<uint32_t
>(ceil(exp(1.0) / relative_error));
99template<
typename W,
typename A>
106 if (confidence < 0. || confidence > 1.0) {
107 throw std::invalid_argument(
"Confidence must be between 0 and 1.0 (inclusive).");
109 return std::min<uint8_t>(ceil(log(1.0 / (1.0 - confidence))), UINT8_MAX);
112template<
typename W,
typename A>
114void count_min_sketch<W,A>::foreach_hash_location(
const void* item,
size_t size, F callback)
const {
129 uint64_t bucket_index;
131 uint64_t hash_seed_index = 0;
132 for (
const auto &it: hash_seeds) {
134 MurmurHash3_x64_128(item, size, it, hashes);
135 uint64_t hash = hashes.h1;
136 bucket_index = hash % _num_buckets;
137 callback((hash_seed_index * _num_buckets) + bucket_index);
138 hash_seed_index += 1;
142template<
typename W,
typename A>
145template<
typename W,
typename A>
148template<
typename W,
typename A>
150 if (item.empty()) {
return 0; }
154template<
typename W,
typename A>
159 W estimate = std::numeric_limits<W>::max();
160 foreach_hash_location(item, size, [
this, &estimate](uint64_t h) {
161 estimate = std::min(estimate, _sketch_array[h]);
166template<
typename W,
typename A>
168 update(&item,
sizeof(item), weight);
171template<
typename W,
typename A>
173 update(&item,
sizeof(item), weight);
176template<
typename W,
typename A>
178 if (item.empty()) {
return; }
179 update(item.c_str(), item.length(), weight);
182template<
typename W,
typename A>
188 _total_weight += weight >= 0 ? weight : -weight;
189 foreach_hash_location(item, size, [
this, weight](uint64_t h) {
190 _sketch_array[h] += weight;
194template<
typename W,
typename A>
197template<
typename W,
typename A>
200template<
typename W,
typename A>
202 if (item.empty()) {
return 0; }
206template<
typename W,
typename A>
211template<
typename W,
typename A>
214template<
typename W,
typename A>
217template<
typename W,
typename A>
219 if (item.empty()) {
return 0; }
223template<
typename W,
typename A>
228template<
typename W,
typename A>
233 if (
this == &other_sketch) {
throw std::invalid_argument(
"Cannot merge a sketch with itself." ); }
235 bool acceptable_config =
239 if (!acceptable_config) {
throw std::invalid_argument(
"Incompatible sketch configuration." ); }
242 auto it = _sketch_array.begin();
243 auto other_it = other_sketch.
begin();
244 while (it != _sketch_array.end()) {
253template<
typename W,
typename A>
255 return _sketch_array.begin();
258template<
typename W,
typename A>
260return _sketch_array.end();
263template<
typename W,
typename A>
266 os.write(
static_cast<const char*
>(data), size);
270template<
typename Sink,
typename T>
271static inline void write_count_min_value(Sink& sink,
size_t& bytes_written,
const T& value) {
272 sink(&value,
sizeof(value));
273 bytes_written +=
sizeof(value);
276template<
typename W,
typename A>
277template<
typename Sink>
279 size_t bytes_written = 0;
283 const uint8_t preamble_longs = PREAMBLE_LONGS_SHORT;
284 const uint8_t ser_ver = SERIAL_VERSION_1;
285 const uint8_t family_id = FAMILY_ID;
286 const uint8_t flags_byte = (
is_empty() ? 1 << flags::IS_EMPTY : 0);
287 const uint32_t unused32 = NULL_32;
288 write_count_min_value(sink, bytes_written, preamble_longs);
289 write_count_min_value(sink, bytes_written, ser_ver);
290 write_count_min_value(sink, bytes_written, family_id);
291 write_count_min_value(sink, bytes_written, flags_byte);
292 write_count_min_value(sink, bytes_written, unused32);
295 const uint32_t nbuckets = _num_buckets;
296 const uint8_t nhashes = _num_hashes;
297 const uint16_t seed_hash(compute_seed_hash(_seed));
298 const uint8_t unused8 = NULL_8;
299 write_count_min_value(sink, bytes_written, nbuckets);
300 write_count_min_value(sink, bytes_written, nhashes);
301 write_count_min_value(sink, bytes_written, seed_hash);
302 write_count_min_value(sink, bytes_written, unused8);
303 if (
is_empty()) {
return bytes_written; }
306 const W t_weight = _total_weight;
307 write_count_min_value(sink, bytes_written, t_weight);
310 const size_t sketch_array_bytes =
sizeof(W) * _sketch_array.size();
311 if (sketch_array_bytes > 0) {
312 sink(_sketch_array.data(), sketch_array_bytes);
313 bytes_written += sketch_array_bytes;
316 return bytes_written;
319template<
typename W,
typename A>
323 const auto preamble_longs = read<uint8_t>(is);
324 const auto serial_version = read<uint8_t>(is);
325 const auto family_id = read<uint8_t>(is);
326 const auto flags_byte = read<uint8_t>(is);
329 check_header_validity(preamble_longs, serial_version, family_id, flags_byte);
332 const auto nbuckets = read<uint32_t>(is);
333 const auto nhashes = read<uint8_t>(is);
334 const auto seed_hash = read<uint16_t>(is);
337 if (seed_hash != compute_seed_hash(seed)) {
338 throw std::invalid_argument(
"Incompatible seed hashes: " + std::to_string(seed_hash) +
", "
339 + std::to_string(compute_seed_hash(seed)));
341 count_min_sketch c(nhashes, nbuckets, seed, allocator);
342 const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
343 if (is_empty == 1) {
return c; }
346 const auto weight = read<W>(is);
347 c._total_weight += weight;
348 read(is, c._sketch_array.data(),
sizeof(W) * c._sketch_array.size());
353template<
typename W,
typename A>
356 const size_t preamble_longs = PREAMBLE_LONGS_SHORT;
360 return (preamble_longs *
sizeof(uint64_t)) + (
is_empty() ? 0 :
sizeof(W) * (1 + _num_buckets * _num_hashes));
363template<
typename W,
typename A>
366 uint8_t *ptr = bytes.data() + header_size_bytes;
368 ptr += copy_to_mem(data, ptr, size);
374template<
typename W,
typename A>
376 ensure_minimum_memory(size, PREAMBLE_LONGS_SHORT *
sizeof(uint64_t));
378 const char* ptr =
static_cast<const char*
>(bytes);
381 uint8_t preamble_longs;
382 ptr += copy_from_mem(ptr, preamble_longs);
383 uint8_t serial_version;
384 ptr += copy_from_mem(ptr, serial_version);
386 ptr += copy_from_mem(ptr, family_id);
388 ptr += copy_from_mem(ptr, flags_byte);
389 ptr +=
sizeof(uint32_t);
391 check_header_validity(preamble_longs, serial_version, family_id, flags_byte);
397 ptr += copy_from_mem(ptr, nbuckets);
398 ptr += copy_from_mem(ptr, nhashes);
399 ptr += copy_from_mem(ptr, seed_hash);
400 ptr +=
sizeof(uint8_t);
402 if (seed_hash != compute_seed_hash(seed)) {
403 throw std::invalid_argument(
"Incompatible seed hashes: " + std::to_string(seed_hash) +
", "
404 + std::to_string(compute_seed_hash(seed)));
406 count_min_sketch c(nhashes, nbuckets, seed, allocator);
407 const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
408 if (is_empty) {
return c; }
411 ensure_minimum_memory(size, PREAMBLE_LONGS_SHORT *
sizeof(uint64_t) +
sizeof(W) * (1 + c._sketch_array.size()));
415 ptr += copy_from_mem(ptr, weight);
416 c._total_weight += weight;
419 for (
size_t i = 0; i<c._num_buckets*c._num_hashes; ++i) {
420 ptr += copy_from_mem(ptr, c._sketch_array[i]);
425template<
typename W,
typename A>
427 return _total_weight == 0;
430template<
typename W,
typename A>
433 uint64_t num_nonzero = 0;
434 for (
const auto entry: _sketch_array) {
435 if (entry !=
static_cast<W
>(0.0)) { ++num_nonzero; }
440 std::ostringstream os;
441 os <<
"### Count Min sketch summary:" << std::endl;
442 os <<
" num hashes : " <<
static_cast<uint32_t
>(_num_hashes) << std::endl;
443 os <<
" num buckets : " << _num_buckets << std::endl;
444 os <<
" capacity bins : " << _sketch_array.size() << std::endl;
445 os <<
" filled bins : " << num_nonzero << std::endl;
446 os <<
" pct filled : " << std::setprecision(3) << (num_nonzero * 100.0) / _sketch_array.size() <<
"%" << std::endl;
447 os <<
"### End sketch summary" << std::endl;
449 return string<A>(os.str().c_str(), _allocator);
452template<
typename W,
typename A>
453void count_min_sketch<W,A>::check_header_validity(uint8_t preamble_longs, uint8_t serial_version, uint8_t family_id, uint8_t flags_byte) {
454 const bool empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
456 const uint8_t sw = (empty ? 1 : 0) + (2 * serial_version) + (4 * family_id) + (32 * (preamble_longs & 0x3F));
468 std::ostringstream os;
469 os <<
"Possible sketch corruption. Inconsistent state: "
470 <<
"preamble_longs = " <<
static_cast<uint32_t
>(preamble_longs)
471 <<
", empty = " << (empty ?
"true" :
"false")
472 <<
", serialization_version = " <<
static_cast<uint32_t
>(serial_version);
473 throw std::invalid_argument(os.str());
C++ implementation of the CountMin sketch data structure of Cormode and Muthukrishnan.
Definition count_min.hpp:39
static count_min_sketch deserialize(std::istream &is, uint64_t seed=DEFAULT_SEED, const Allocator &allocator=Allocator())
This method deserializes a sketch from a given stream.
void serialize(std::ostream &os) const
This method serializes the sketch into a given stream in a binary form.
Definition count_min_impl.hpp:264
const_iterator end() const
Iterator pointing to the past-the-end item in the sketch.
Definition count_min_impl.hpp:259
double get_relative_error() const
Definition count_min_impl.hpp:78
uint32_t get_num_buckets() const
Definition count_min_impl.hpp:68
size_t serialize_to(Sink &&sink) const
This method serializes the sketch by passing binary fragments to a callback.
Definition count_min_impl.hpp:278
bool is_empty() const
Returns true if this sketch is empty.
Definition count_min_impl.hpp:426
W get_total_weight() const
Definition count_min_impl.hpp:83
uint8_t get_num_hashes() const
Definition count_min_impl.hpp:63
W get_upper_bound(const void *item, size_t size) const
Query the sketch for the upper bound of a given item.
Definition count_min_impl.hpp:207
W get_lower_bound(const void *item, size_t size) const
Query the sketch for the lower bound of a given item.
Definition count_min_impl.hpp:224
W get_estimate(uint64_t item) const
Suggests the number of buckets required to achieve the given relative error.
Definition count_min_impl.hpp:143
count_min_sketch(uint8_t num_hashes, uint32_t num_buckets, uint64_t seed=DEFAULT_SEED, const Allocator &allocator=Allocator())
Creates an instance of the sketch given parameters _num_hashes, _num_buckets and hash seed,...
Definition count_min_impl.hpp:35
uint64_t get_seed() const
Definition count_min_impl.hpp:73
void merge(const count_min_sketch &other_sketch)
Merges another count_min_sketch into this count_min_sketch.
Definition count_min_impl.hpp:229
void update(const void *item, size_t size, W weight)
Update this sketch with given data of any type.
Definition count_min_impl.hpp:183
string< Allocator > to_string() const
Returns a string describing the sketch.
Definition count_min_impl.hpp:431
size_t get_serialized_size_bytes() const
Computes size needed to serialize the current state of the sketch.
Definition count_min_impl.hpp:354
const_iterator begin() const
Iterator pointing to the first item in the sketch.
Definition count_min_impl.hpp:254
DataSketches namespace.
Definition binomial_bounds.hpp:38