datasketches 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/ext/datasketches/theta_wrapper.cpp +1 -1
  4. data/lib/datasketches/version.rb +1 -1
  5. data/vendor/datasketches-cpp/CMakeLists.txt +5 -3
  6. data/vendor/datasketches-cpp/CODE_OF_CONDUCT.md +1 -1
  7. data/vendor/datasketches-cpp/LICENSE +14 -0
  8. data/vendor/datasketches-cpp/README.md +67 -73
  9. data/vendor/datasketches-cpp/benchmarks/CMakeLists.txt +52 -0
  10. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch.cpp +153 -0
  11. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch_serialization.cpp +161 -0
  12. data/vendor/datasketches-cpp/common/CMakeLists.txt +1 -0
  13. data/vendor/datasketches-cpp/common/include/binomial_bounds.hpp +2 -2
  14. data/vendor/datasketches-cpp/common/include/fdlibm_log.hpp +101 -0
  15. data/vendor/datasketches-cpp/common/include/serde.hpp +6 -0
  16. data/vendor/datasketches-cpp/common/test/CMakeLists.txt +44 -2
  17. data/vendor/datasketches-cpp/common/test/binomial_bounds_test.cpp +279 -0
  18. data/vendor/datasketches-cpp/common/test/deserialize_hardening_test.cpp +188 -0
  19. data/vendor/datasketches-cpp/count/include/count_min.hpp +17 -4
  20. data/vendor/datasketches-cpp/count/include/count_min_impl.hpp +65 -83
  21. data/vendor/datasketches-cpp/count/test/count_min_test.cpp +63 -7
  22. data/vendor/datasketches-cpp/cpc/include/compression_data.hpp +2 -0
  23. data/vendor/datasketches-cpp/cpc/include/cpc_compressor_impl.hpp +16 -7
  24. data/vendor/datasketches-cpp/cpc/include/cpc_sketch.hpp +1 -0
  25. data/vendor/datasketches-cpp/cpc/include/cpc_sketch_impl.hpp +54 -36
  26. data/vendor/datasketches-cpp/cpc/include/cpc_union_impl.hpp +27 -27
  27. data/vendor/datasketches-cpp/cpc/include/cpc_util.hpp +7 -7
  28. data/vendor/datasketches-cpp/cpc/include/icon_estimator.hpp +5 -5
  29. data/vendor/datasketches-cpp/cpc/include/u32_table_impl.hpp +16 -16
  30. data/vendor/datasketches-cpp/cpc/test/cpc_sketch_test.cpp +35 -0
  31. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch.hpp +28 -3
  32. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch_impl.hpp +41 -25
  33. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map.hpp +3 -1
  34. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map_impl.hpp +10 -5
  35. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_serialize_for_java.cpp +24 -0
  36. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_test.cpp +116 -0
  37. data/vendor/datasketches-cpp/filters/include/bloom_filter.hpp +1 -1
  38. data/vendor/datasketches-cpp/filters/include/bloom_filter_impl.hpp +32 -12
  39. data/vendor/datasketches-cpp/filters/test/bloom_filter_test.cpp +28 -1
  40. data/vendor/datasketches-cpp/hll/include/CouponHashSet-internal.hpp +1 -2
  41. data/vendor/datasketches-cpp/hll/include/CouponList-internal.hpp +19 -5
  42. data/vendor/datasketches-cpp/hll/include/CubicInterpolation-internal.hpp +4 -4
  43. data/vendor/datasketches-cpp/hll/include/HarmonicNumbers-internal.hpp +2 -1
  44. data/vendor/datasketches-cpp/hll/include/Hll4Array-internal.hpp +4 -4
  45. data/vendor/datasketches-cpp/hll/include/Hll4Array.hpp +1 -1
  46. data/vendor/datasketches-cpp/hll/include/Hll6Array-internal.hpp +3 -3
  47. data/vendor/datasketches-cpp/hll/include/Hll6Array.hpp +1 -1
  48. data/vendor/datasketches-cpp/hll/include/Hll8Array-internal.hpp +6 -3
  49. data/vendor/datasketches-cpp/hll/include/Hll8Array.hpp +1 -1
  50. data/vendor/datasketches-cpp/hll/include/HllArray-internal.hpp +35 -29
  51. data/vendor/datasketches-cpp/hll/include/HllArray.hpp +1 -1
  52. data/vendor/datasketches-cpp/hll/include/HllSketch-internal.hpp +3 -5
  53. data/vendor/datasketches-cpp/hll/include/HllSketchImpl-internal.hpp +5 -11
  54. data/vendor/datasketches-cpp/hll/include/HllSketchImpl.hpp +2 -4
  55. data/vendor/datasketches-cpp/hll/include/HllSketchImplFactory.hpp +11 -10
  56. data/vendor/datasketches-cpp/hll/include/HllUnion-internal.hpp +17 -8
  57. data/vendor/datasketches-cpp/hll/include/HllUtil.hpp +12 -3
  58. data/vendor/datasketches-cpp/hll/include/coupon_iterator-internal.hpp +2 -2
  59. data/vendor/datasketches-cpp/hll/include/coupon_iterator.hpp +3 -0
  60. data/vendor/datasketches-cpp/hll/include/hll.hpp +11 -4
  61. data/vendor/datasketches-cpp/hll/include/hll.private.hpp +19 -0
  62. data/vendor/datasketches-cpp/hll/test/CMakeLists.txt +2 -0
  63. data/vendor/datasketches-cpp/hll/test/CouponListTest.cpp +64 -0
  64. data/vendor/datasketches-cpp/hll/test/HllFullSizeTest.cpp +137 -0
  65. data/vendor/datasketches-cpp/hll/test/HllKxqRebuildTest.cpp +150 -0
  66. data/vendor/datasketches-cpp/hll/test/HllSketchTest.cpp +3 -3
  67. data/vendor/datasketches-cpp/hll/test/HllUnionTest.cpp +71 -1
  68. data/vendor/datasketches-cpp/kll/include/kll_helper_impl.hpp +10 -10
  69. data/vendor/datasketches-cpp/kll/include/kll_sketch.hpp +10 -1
  70. data/vendor/datasketches-cpp/kll/include/kll_sketch_impl.hpp +33 -24
  71. data/vendor/datasketches-cpp/kll/test/kll_sketch_deserialize_from_java_test.cpp +24 -0
  72. data/vendor/datasketches-cpp/kll/test/kll_sketch_serialize_for_java.cpp +10 -0
  73. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch.hpp +11 -2
  74. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch_impl.hpp +24 -15
  75. data/vendor/datasketches-cpp/req/include/req_sketch.hpp +9 -0
  76. data/vendor/datasketches-cpp/req/include/req_sketch_impl.hpp +24 -15
  77. data/vendor/datasketches-cpp/req/test/req_sketch_deserialize_from_java_test.cpp +46 -0
  78. data/vendor/datasketches-cpp/req/test/req_sketch_serialize_for_java.cpp +20 -0
  79. data/vendor/datasketches-cpp/req/test/req_sketch_test.cpp +70 -0
  80. data/vendor/datasketches-cpp/sampling/include/ebpps_sample_impl.hpp +17 -8
  81. data/vendor/datasketches-cpp/sampling/include/ebpps_sketch.hpp +13 -0
  82. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch.hpp +10 -1
  83. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch_impl.hpp +6 -7
  84. data/vendor/datasketches-cpp/sampling/include/var_opt_union.hpp +5 -1
  85. data/vendor/datasketches-cpp/sampling/include/var_opt_union_impl.hpp +2 -1
  86. data/vendor/datasketches-cpp/sampling/test/ebpps_allocation_test.cpp +1 -1
  87. data/vendor/datasketches-cpp/sampling/test/ebpps_sketch_test.cpp +2 -2
  88. data/vendor/datasketches-cpp/sampling/test/var_opt_allocation_test.cpp +1 -1
  89. data/vendor/datasketches-cpp/sampling/test/var_opt_sketch_test.cpp +10 -4
  90. data/vendor/datasketches-cpp/sampling/test/var_opt_union_test.cpp +12 -0
  91. data/vendor/datasketches-cpp/tdigest/include/tdigest.hpp +38 -2
  92. data/vendor/datasketches-cpp/tdigest/include/tdigest_impl.hpp +168 -9
  93. data/vendor/datasketches-cpp/tdigest/test/CMakeLists.txt +1 -0
  94. data/vendor/datasketches-cpp/tdigest/test/tdigest_iterator_test.cpp +274 -0
  95. data/vendor/datasketches-cpp/tdigest/test/tdigest_test.cpp +275 -0
  96. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser.hpp +2 -0
  97. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser_impl.hpp +24 -3
  98. data/vendor/datasketches-cpp/theta/include/theta_constants.hpp +4 -2
  99. data/vendor/datasketches-cpp/theta/include/theta_helpers.hpp +32 -0
  100. data/vendor/datasketches-cpp/theta/include/theta_set_difference_base_impl.hpp +4 -2
  101. data/vendor/datasketches-cpp/theta/include/theta_sketch.hpp +22 -4
  102. data/vendor/datasketches-cpp/theta/include/theta_sketch_impl.hpp +60 -38
  103. data/vendor/datasketches-cpp/theta/include/theta_union_base_impl.hpp +2 -6
  104. data/vendor/datasketches-cpp/theta/include/theta_update_sketch_base_impl.hpp +2 -2
  105. data/vendor/datasketches-cpp/theta/test/bit_packing_test.cpp +50 -0
  106. data/vendor/datasketches-cpp/theta/test/theta_a_not_b_test.cpp +22 -0
  107. data/vendor/datasketches-cpp/theta/test/theta_sketch_test.cpp +315 -0
  108. data/vendor/datasketches-cpp/tools/rat-check.sh +68 -0
  109. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch.hpp +35 -4
  110. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch_impl.hpp +2 -2
  111. data/vendor/datasketches-cpp/tuple/include/tuple_sketch.hpp +41 -0
  112. data/vendor/datasketches-cpp/tuple/include/tuple_sketch_impl.hpp +5 -4
  113. data/vendor/datasketches-cpp/tuple/test/tuple_sketch_test.cpp +59 -0
  114. data/vendor/datasketches-cpp/version.cfg.in +1 -1
  115. metadata +12 -2
@@ -21,6 +21,8 @@
21
21
  #define COUNT_MIN_HPP_
22
22
 
23
23
  #include <iterator>
24
+ #include <type_traits>
25
+ #include <vector>
24
26
  #include "common_defs.hpp"
25
27
 
26
28
  namespace datasketches {
@@ -36,6 +38,7 @@ template <typename W,
36
38
  typename Allocator = std::allocator<W>>
37
39
  class count_min_sketch{
38
40
  static_assert(std::is_arithmetic<W>::value, "Arithmetic type expected");
41
+ static_assert(!std::is_same<W, bool>::value, "Boolean weight type is not supported");
39
42
  public:
40
43
  using allocator_type = Allocator;
41
44
  using const_iterator = typename std::vector<W, Allocator>::const_iterator;
@@ -326,6 +329,15 @@ public:
326
329
  */
327
330
  void serialize(std::ostream& os) const;
328
331
 
332
+ /**
333
+ * This method serializes the sketch by passing binary fragments to a callback.
334
+ * The callback must be callable as write_bytes(const void* data, size_t size).
335
+ * The data pointer is valid only until the callback returns.
336
+ * @return size in bytes written to the callback
337
+ */
338
+ template<typename Sink>
339
+ size_t serialize_to(Sink&& sink) const;
340
+
329
341
  // This is a convenience alias for users
330
342
  // The type returned by the following serialize method
331
343
  using vector_bytes = std::vector<uint8_t, typename std::allocator_traits<Allocator>::template rebind_alloc<uint8_t>>;
@@ -389,12 +401,13 @@ private:
389
401
  static void check_header_validity(uint8_t preamble_longs, uint8_t serial_version, uint8_t family_id, uint8_t flags_byte);
390
402
 
391
403
  /*
392
- * Obtain the hash values when inserting an item into the sketch.
393
- * @param item pointer to the data item to be inserted into the sketch.
404
+ * Compute the hash locations for an input item
405
+ * @param item pointer to the data item to be inserted into or queried from the sketch.
394
406
  * @param size of the data in bytes
395
- * @return vector of uint64_t which each represent the index to which `value' must update in the sketch
407
+ * @param callback function to invoke for each sketch array location
396
408
  */
397
- std::vector<uint64_t> get_hashes(const void* item, size_t size) const;
409
+ template<typename F>
410
+ void foreach_hash_location(const void* item, size_t size, F callback) const;
398
411
 
399
412
  };
400
413
 
@@ -36,14 +36,16 @@ count_min_sketch<W,A>::count_min_sketch(uint8_t num_hashes, uint32_t num_buckets
36
36
  _allocator(allocator),
37
37
  _num_hashes(num_hashes),
38
38
  _num_buckets(num_buckets),
39
- _sketch_array((num_hashes*num_buckets < 1<<30) ? num_hashes*num_buckets : 0, 0, _allocator),
39
+ _sketch_array((static_cast<uint64_t>(num_hashes) * num_buckets < 1<<30) ? static_cast<size_t>(num_hashes) * num_buckets : 0, 0, _allocator),
40
40
  _seed(seed),
41
41
  _total_weight(0) {
42
- if (num_buckets < 3) throw std::invalid_argument("Using fewer than 3 buckets incurs relative error greater than 1.");
42
+ if (num_buckets < 3) {
43
+ throw std::invalid_argument("Using fewer than 3 buckets incurs relative error greater than 1.");
44
+ }
43
45
 
44
46
  // This check is to ensure later compatibility with a Java implementation whose maximum size can only
45
47
  // be 2^31-1. We check only against 2^30 for simplicity.
46
- if (num_buckets * num_hashes >= 1 << 30) {
48
+ if (static_cast<uint64_t>(num_buckets) * num_hashes >= 1 << 30) {
47
49
  throw std::invalid_argument("These parameters generate a sketch that exceeds 2^30 elements."
48
50
  "Try reducing either the number of buckets or the number of hash functions.");
49
51
  }
@@ -74,7 +76,7 @@ uint64_t count_min_sketch<W,A>::get_seed() const {
74
76
 
75
77
  template<typename W, typename A>
76
78
  double count_min_sketch<W,A>::get_relative_error() const {
77
- return exp(1.0) / double(_num_buckets);
79
+ return exp(1.0) / static_cast<double>(_num_buckets);
78
80
  }
79
81
 
80
82
  template<typename W, typename A>
@@ -108,9 +110,10 @@ uint8_t count_min_sketch<W,A>::suggest_num_hashes(double confidence) {
108
110
  }
109
111
 
110
112
  template<typename W, typename A>
111
- std::vector<uint64_t> count_min_sketch<W,A>::get_hashes(const void* item, size_t size) const {
113
+ template<typename F>
114
+ void count_min_sketch<W,A>::foreach_hash_location(const void* item, size_t size, F callback) const {
112
115
  /*
113
- * Returns the hash locations for the input item using the original hashing
116
+ * Computes the hash locations for the input item using the original hashing
114
117
  * scheme from [1].
115
118
  * Generate _num_hashes separate hashes from calls to murmurmhash.
116
119
  * This could be optimized by keeping both of the 64bit parts of the hash
@@ -124,8 +127,6 @@ std::vector<uint64_t> count_min_sketch<W,A>::get_hashes(const void* item, size_t
124
127
  * https://www.eecs.harvard.edu/~michaelm/postscripts/tr-02-05.pdf
125
128
  */
126
129
  uint64_t bucket_index;
127
- std::vector<uint64_t> sketch_update_locations;
128
- sketch_update_locations.reserve(_num_hashes);
129
130
 
130
131
  uint64_t hash_seed_index = 0;
131
132
  for (const auto &it: hash_seeds) {
@@ -133,10 +134,9 @@ std::vector<uint64_t> count_min_sketch<W,A>::get_hashes(const void* item, size_t
133
134
  MurmurHash3_x64_128(item, size, it, hashes); // ? BEWARE OVERFLOW.
134
135
  uint64_t hash = hashes.h1;
135
136
  bucket_index = hash % _num_buckets;
136
- sketch_update_locations.push_back((hash_seed_index * _num_buckets) + bucket_index);
137
+ callback((hash_seed_index * _num_buckets) + bucket_index);
137
138
  hash_seed_index += 1;
138
139
  }
139
- return sketch_update_locations;
140
140
  }
141
141
 
142
142
  template<typename W, typename A>
@@ -147,7 +147,7 @@ W count_min_sketch<W,A>::get_estimate(int64_t item) const {return get_estimate(&
147
147
 
148
148
  template<typename W, typename A>
149
149
  W count_min_sketch<W,A>::get_estimate(const std::string& item) const {
150
- if (item.empty()) return 0; // Empty strings are not inserted into the sketch.
150
+ if (item.empty()) { return 0; } // Empty strings are not inserted into the sketch.
151
151
  return get_estimate(item.c_str(), item.length());
152
152
  }
153
153
 
@@ -156,12 +156,11 @@ W count_min_sketch<W,A>::get_estimate(const void* item, size_t size) const {
156
156
  /*
157
157
  * Returns the estimated frequency of the item
158
158
  */
159
- std::vector<uint64_t> hash_locations = get_hashes(item, size);
160
- std::vector<W> estimates;
161
- for (const auto h: hash_locations) {
162
- estimates.push_back(_sketch_array[h]);
163
- }
164
- return *std::min_element(estimates.begin(), estimates.end());
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]);
162
+ });
163
+ return estimate;
165
164
  }
166
165
 
167
166
  template<typename W, typename A>
@@ -176,7 +175,7 @@ void count_min_sketch<W,A>::update(int64_t item, W weight) {
176
175
 
177
176
  template<typename W, typename A>
178
177
  void count_min_sketch<W,A>::update(const std::string& item, W weight) {
179
- if (item.empty()) return;
178
+ if (item.empty()) { return; }
180
179
  update(item.c_str(), item.length(), weight);
181
180
  }
182
181
 
@@ -187,10 +186,9 @@ void count_min_sketch<W,A>::update(const void* item, size_t size, W weight) {
187
186
  * locations by the weight.
188
187
  */
189
188
  _total_weight += weight >= 0 ? weight : -weight;
190
- std::vector<uint64_t> hash_locations = get_hashes(item, size);
191
- for (const auto h: hash_locations) {
189
+ foreach_hash_location(item, size, [this, weight](uint64_t h) {
192
190
  _sketch_array[h] += weight;
193
- }
191
+ });
194
192
  }
195
193
 
196
194
  template<typename W, typename A>
@@ -201,7 +199,7 @@ W count_min_sketch<W,A>::get_upper_bound(int64_t item) const {return get_upper_b
201
199
 
202
200
  template<typename W, typename A>
203
201
  W count_min_sketch<W,A>::get_upper_bound(const std::string& item) const {
204
- if (item.empty()) return 0; // Empty strings are not inserted into the sketch.
202
+ if (item.empty()) { return 0; } // Empty strings are not inserted into the sketch.
205
203
  return get_upper_bound(item.c_str(), item.length());
206
204
  }
207
205
 
@@ -218,7 +216,7 @@ W count_min_sketch<W,A>::get_lower_bound(int64_t item) const {return get_lower_b
218
216
 
219
217
  template<typename W, typename A>
220
218
  W count_min_sketch<W,A>::get_lower_bound(const std::string& item) const {
221
- if (item.empty()) return 0; // Empty strings are not inserted into the sketch.
219
+ if (item.empty()) { return 0; } // Empty strings are not inserted into the sketch.
222
220
  return get_lower_bound(item.c_str(), item.length());
223
221
  }
224
222
 
@@ -232,17 +230,13 @@ void count_min_sketch<W,A>::merge(const count_min_sketch &other_sketch) {
232
230
  /*
233
231
  * Merges this sketch into other_sketch sketch by elementwise summing of buckets
234
232
  */
235
- if (this == &other_sketch) {
236
- throw std::invalid_argument( "Cannot merge a sketch with itself." );
237
- }
233
+ if (this == &other_sketch) { throw std::invalid_argument( "Cannot merge a sketch with itself." ); }
238
234
 
239
235
  bool acceptable_config =
240
236
  (get_num_hashes() == other_sketch.get_num_hashes()) &&
241
237
  (get_num_buckets() == other_sketch.get_num_buckets()) &&
242
238
  (get_seed() == other_sketch.get_seed());
243
- if (!acceptable_config) {
244
- throw std::invalid_argument( "Incompatible sketch configuration." );
245
- }
239
+ if (!acceptable_config) { throw std::invalid_argument( "Incompatible sketch configuration." ); }
246
240
 
247
241
  // Merge step - iterate over the other vector and add the weights to this sketch
248
242
  auto it = _sketch_array.begin(); // This is a std::vector iterator.
@@ -268,6 +262,22 @@ return _sketch_array.end();
268
262
 
269
263
  template<typename W, typename A>
270
264
  void count_min_sketch<W,A>::serialize(std::ostream& os) const {
265
+ serialize_to([&os](const void* data, size_t size) {
266
+ os.write(static_cast<const char*>(data), size);
267
+ });
268
+ }
269
+
270
+ template<typename Sink, typename T>
271
+ static 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);
274
+ }
275
+
276
+ template<typename W, typename A>
277
+ template<typename Sink>
278
+ size_t count_min_sketch<W,A>::serialize_to(Sink&& sink) const {
279
+ size_t bytes_written = 0;
280
+
271
281
  // Long 0
272
282
  //const uint8_t preamble_longs = is_empty() ? PREAMBLE_LONGS_SHORT : PREAMBLE_LONGS_FULL;
273
283
  const uint8_t preamble_longs = PREAMBLE_LONGS_SHORT;
@@ -275,32 +285,35 @@ void count_min_sketch<W,A>::serialize(std::ostream& os) const {
275
285
  const uint8_t family_id = FAMILY_ID;
276
286
  const uint8_t flags_byte = (is_empty() ? 1 << flags::IS_EMPTY : 0);
277
287
  const uint32_t unused32 = NULL_32;
278
- write(os, preamble_longs);
279
- write(os, ser_ver);
280
- write(os, family_id);
281
- write(os, flags_byte);
282
- write(os, unused32);
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);
283
293
 
284
294
  // Long 1
285
295
  const uint32_t nbuckets = _num_buckets;
286
296
  const uint8_t nhashes = _num_hashes;
287
297
  const uint16_t seed_hash(compute_seed_hash(_seed));
288
298
  const uint8_t unused8 = NULL_8;
289
- write(os, nbuckets);
290
- write(os, nhashes);
291
- write(os, seed_hash);
292
- write(os, unused8);
293
- if (is_empty()) return; // sketch is empty, no need to write further bytes.
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; } // sketch is empty, no need to write further bytes.
294
304
 
295
305
  // Long 2
296
- write(os, _total_weight);
306
+ const W t_weight = _total_weight;
307
+ write_count_min_value(sink, bytes_written, t_weight);
297
308
 
298
309
  // Long 3 onwards: remaining bytes are consumed by writing the weight and the array values.
299
- auto it = _sketch_array.begin();
300
- while (it != _sketch_array.end()) {
301
- write(os, *it);
302
- ++it;
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;
303
314
  }
315
+
316
+ return bytes_written;
304
317
  }
305
318
 
306
319
  template<typename W, typename A>
@@ -327,7 +340,7 @@ auto count_min_sketch<W,A>::deserialize(std::istream& is, uint64_t seed, const A
327
340
  }
328
341
  count_min_sketch c(nhashes, nbuckets, seed, allocator);
329
342
  const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
330
- if (is_empty == 1) return c; // sketch is empty, no need to read further.
343
+ if (is_empty == 1) { return c; } // sketch is empty, no need to read further.
331
344
 
332
345
  // Set the sketch weight and read in the sketch values
333
346
  const auto weight = read<W>(is);
@@ -351,40 +364,9 @@ template<typename W, typename A>
351
364
  auto count_min_sketch<W,A>::serialize(unsigned header_size_bytes) const -> vector_bytes {
352
365
  vector_bytes bytes(header_size_bytes + get_serialized_size_bytes(), 0, _allocator);
353
366
  uint8_t *ptr = bytes.data() + header_size_bytes;
354
-
355
- // Long 0
356
- const uint8_t preamble_longs = PREAMBLE_LONGS_SHORT;
357
- ptr += copy_to_mem(preamble_longs, ptr);
358
- const uint8_t ser_ver = SERIAL_VERSION_1;
359
- ptr += copy_to_mem(ser_ver, ptr);
360
- const uint8_t family_id = FAMILY_ID;
361
- ptr += copy_to_mem(family_id, ptr);
362
- const uint8_t flags_byte = (is_empty() ? 1 << flags::IS_EMPTY : 0);
363
- ptr += copy_to_mem(flags_byte, ptr);
364
- const uint32_t unused32 = NULL_32;
365
- ptr += copy_to_mem(unused32, ptr);
366
-
367
- // Long 1
368
- const uint32_t nbuckets = _num_buckets;
369
- const uint8_t nhashes = _num_hashes;
370
- const uint16_t seed_hash(compute_seed_hash(_seed));
371
- const uint8_t null_characters_8 = NULL_8;
372
- ptr += copy_to_mem(nbuckets, ptr);
373
- ptr += copy_to_mem(nhashes, ptr);
374
- ptr += copy_to_mem(seed_hash, ptr);
375
- ptr += copy_to_mem(null_characters_8, ptr);
376
- if (is_empty()) return bytes; // sketch is empty, no need to write further bytes.
377
-
378
- // Long 2
379
- const W t_weight = _total_weight;
380
- ptr += copy_to_mem(t_weight, ptr);
381
-
382
- // Long 3 onwards: remaining bytes are consumed by writing the weight and the array values.
383
- auto it = _sketch_array.begin();
384
- while (it != _sketch_array.end()) {
385
- ptr += copy_to_mem(*it, ptr);
386
- ++it;
387
- }
367
+ serialize_to([&ptr](const void* data, size_t size) {
368
+ ptr += copy_to_mem(data, ptr, size);
369
+ });
388
370
 
389
371
  return bytes;
390
372
  }
@@ -423,9 +405,10 @@ auto count_min_sketch<W,A>::deserialize(const void* bytes, size_t size, uint64_t
423
405
  }
424
406
  count_min_sketch c(nhashes, nbuckets, seed, allocator);
425
407
  const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY)) > 0;
426
- if (is_empty) return c; // sketch is empty, no need to read further.
408
+ if (is_empty) { return c; } // sketch is empty, no need to read further.
427
409
 
428
- ensure_minimum_memory(size, sizeof(W) * (1 + nbuckets * nhashes));
410
+ // preamble (already read) + total weight + table; nbuckets * nhashes < 2^30 was checked by the constructor
411
+ ensure_minimum_memory(size, PREAMBLE_LONGS_SHORT * sizeof(uint64_t) + sizeof(W) * (1 + c._sketch_array.size()));
429
412
 
430
413
  // Long 2 is the weight.
431
414
  W weight;
@@ -449,8 +432,7 @@ string<A> count_min_sketch<W,A>::to_string() const {
449
432
  // count the number of used entries in the sketch
450
433
  uint64_t num_nonzero = 0;
451
434
  for (const auto entry: _sketch_array) {
452
- if (entry != static_cast<W>(0.0))
453
- ++num_nonzero;
435
+ if (entry != static_cast<W>(0.0)) { ++num_nonzero; }
454
436
  }
455
437
 
456
438
  // Using a temporary stream for implementation here does not comply with AllocatorAwareContainer requirements.
@@ -55,7 +55,7 @@ TEST_CASE("CM init") {
55
55
  TEST_CASE("CM parameter suggestions", "[error parameters]") {
56
56
 
57
57
  // Bucket suggestions
58
- REQUIRE_THROWS(count_min_sketch<uint64_t>::suggest_num_buckets(-1.0), "Confidence must be between 0 and 1.0 (inclusive)." );
58
+ REQUIRE_THROWS_WITH(count_min_sketch<uint64_t>::suggest_num_buckets(-1.0), "Relative error must be at least 0.");
59
59
  REQUIRE(count_min_sketch<uint64_t>::suggest_num_buckets(0.2) == 14);
60
60
  REQUIRE(count_min_sketch<uint64_t>::suggest_num_buckets(0.1) == 28);
61
61
  REQUIRE(count_min_sketch<uint64_t>::suggest_num_buckets(0.05) == 55);
@@ -69,8 +69,8 @@ TEST_CASE("CM parameter suggestions", "[error parameters]") {
69
69
  REQUIRE(count_min_sketch<uint64_t>(n_hashes, 272).get_relative_error() <= 0.01);
70
70
 
71
71
  // Hash suggestions
72
- REQUIRE_THROWS(count_min_sketch<uint64_t>::suggest_num_hashes(10.0), "Confidence must be between 0 and 1.0 (inclusive)." );
73
- REQUIRE_THROWS(count_min_sketch<uint64_t>::suggest_num_hashes(-1.0), "Confidence must be between 0 and 1.0 (inclusive)." );
72
+ REQUIRE_THROWS_WITH(count_min_sketch<uint64_t>::suggest_num_hashes(10.0), "Confidence must be between 0 and 1.0 (inclusive)." );
73
+ REQUIRE_THROWS_WITH(count_min_sketch<uint64_t>::suggest_num_hashes(-1.0), "Confidence must be between 0 and 1.0 (inclusive)." );
74
74
  REQUIRE(count_min_sketch<uint64_t>::suggest_num_hashes(0.682689492) == 2); // 1 STDDEV
75
75
  REQUIRE(count_min_sketch<uint64_t>::suggest_num_hashes(0.954499736) == 4); // 2 STDDEV
76
76
  REQUIRE(count_min_sketch<uint64_t>::suggest_num_hashes(0.997300204) == 6); // 3 STDDEV
@@ -161,9 +161,9 @@ TEST_CASE("CM merge - reject", "[reject cases]") {
161
161
  std::vector<count_min_sketch<uint64_t>> sketches = {s1, s2, s3};
162
162
 
163
163
  // Fail cases
164
- REQUIRE_THROWS(s.merge(s), "Cannot merge a sketch with itself." );
164
+ REQUIRE_THROWS_WITH(s.merge(s), "Cannot merge a sketch with itself." );
165
165
  for (count_min_sketch<uint64_t> sk : sketches) {
166
- REQUIRE_THROWS(s.merge(sk), "Incompatible sketch config." );
166
+ REQUIRE_THROWS_WITH(s.merge(sk), "Incompatible sketch configuration." );
167
167
  }
168
168
  }
169
169
 
@@ -215,7 +215,7 @@ TEST_CASE("CountMin sketch: serialize-deserialize empty", "[cm_sketch]") {
215
215
  for (auto di: d) {
216
216
  REQUIRE(di == 0);
217
217
  }
218
- std::ofstream os("count_min-empty.bin");
218
+ std::ofstream os("count_min_empty_cpp.sk", std::ios::binary);
219
219
  c.serialize(os);
220
220
  }
221
221
 
@@ -243,7 +243,7 @@ TEST_CASE("CountMin sketch: serialize-deserialize non-empty", "[cm_sketch]") {
243
243
  ++d_it;
244
244
  }
245
245
 
246
- std::ofstream os("count_min-non-empty.bin");
246
+ std::ofstream os("count_min_non_empty_cpp.sk", std::ios::binary);
247
247
  c.serialize(os);
248
248
  }
249
249
 
@@ -300,4 +300,60 @@ TEST_CASE("CountMin sketch: bytes serialize-deserialize non-empty", "[cm_sketch]
300
300
 
301
301
  }
302
302
 
303
+ TEST_CASE("CountMin sketch: sink serialize-deserialize round trip", "[cm_sketch]") {
304
+ auto check_sink_serialize = [](const count_min_sketch<uint64_t>& c) {
305
+ std::vector<uint8_t> sink_bytes;
306
+ sink_bytes.reserve(c.get_serialized_size_bytes());
307
+
308
+ const size_t bytes_written = c.serialize_to([&sink_bytes](const void* data, size_t size) {
309
+ const auto* begin = static_cast<const uint8_t*>(data);
310
+ sink_bytes.insert(sink_bytes.end(), begin, begin + size);
311
+ });
312
+
313
+ REQUIRE(bytes_written == c.get_serialized_size_bytes());
314
+ REQUIRE(sink_bytes.size() == bytes_written);
315
+
316
+ auto d = count_min_sketch<uint64_t>::deserialize(sink_bytes.data(), sink_bytes.size(), c.get_seed());
317
+ REQUIRE(c.get_num_hashes() == d.get_num_hashes());
318
+ REQUIRE(c.get_num_buckets() == d.get_num_buckets());
319
+ REQUIRE(c.get_seed() == d.get_seed());
320
+ REQUIRE(c.get_total_weight() == d.get_total_weight());
321
+
322
+ auto c_it = c.begin();
323
+ auto d_it = d.begin();
324
+ while (c_it != c.end()) {
325
+ REQUIRE(*c_it == *d_it);
326
+ ++c_it;
327
+ ++d_it;
328
+ }
329
+ };
330
+
331
+ count_min_sketch<uint64_t> empty(3, 32);
332
+ check_sink_serialize(empty);
333
+
334
+ count_min_sketch<uint64_t> non_empty(5, 64);
335
+ for (uint64_t i=0; i < 10; ++i) non_empty.update(i, 10 * i * i);
336
+ check_sink_serialize(non_empty);
337
+ }
338
+
339
+ TEST_CASE("CountMin sketch: bytes deserialize truncated non-empty", "[cm_sketch]") {
340
+ count_min_sketch<uint64_t> c(5, 64);
341
+ for (uint64_t i = 0; i < 10; ++i) c.update(i, 10 * i * i);
342
+ auto bytes = c.serialize();
343
+ for (size_t trim = 1; trim <= 24; ++trim) {
344
+ REQUIRE_THROWS_AS(count_min_sketch<uint64_t>::deserialize(bytes.data(), bytes.size() - trim), std::out_of_range);
345
+ }
346
+ }
347
+
348
+ TEST_CASE("CountMin sketch: deserialize rejects overflowing dimensions", "[cm_sketch]") {
349
+ // num_buckets * num_hashes wraps to 0 in 32-bit arithmetic
350
+ count_min_sketch<uint64_t> c(2, 64);
351
+ c.update(uint64_t(1));
352
+ auto bytes = c.serialize();
353
+ const uint32_t num_buckets = 1U << 31;
354
+ std::memcpy(bytes.data() + 8, &num_buckets, sizeof(num_buckets));
355
+ REQUIRE_THROWS_AS(count_min_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
356
+ REQUIRE_THROWS_AS(count_min_sketch<uint64_t>(2, num_buckets), std::invalid_argument);
357
+ }
358
+
303
359
  } /* namespace datasketches */
@@ -22,6 +22,8 @@
22
22
  #ifndef CPC_COMPRESSION_DATA_HPP_
23
23
  #define CPC_COMPRESSION_DATA_HPP_
24
24
 
25
+ #include <cstdint>
26
+
25
27
  namespace datasketches {
26
28
 
27
29
  /*
@@ -157,7 +157,7 @@ void cpc_compressor<A>::compress(const cpc_sketch_alloc<A>& source, compressed_s
157
157
  break;
158
158
  case cpc_sketch_alloc<A>::flavor::PINNED:
159
159
  compress_pinned_flavor(source, result);
160
- if (result.window_data.size() == 0) throw std::logic_error("window is not expected");
160
+ if (result.window_data.size() == 0) throw std::logic_error("window is expected");
161
161
  break;
162
162
  case cpc_sketch_alloc<A>::flavor::SLIDING:
163
163
  compress_sliding_flavor(source, result);
@@ -354,6 +354,7 @@ void cpc_compressor<A>::uncompress_sliding_flavor(const compressed_state<A>& sou
354
354
  const uint32_t row_col = pairs[i];
355
355
  const uint32_t row = row_col >> 6;
356
356
  uint8_t col = row_col & 63;
357
+ if (col >= 56) throw std::out_of_range("col out of range");
357
358
  // first undo the permutation
358
359
  col = permutation[col];
359
360
  // then undo the rotation: old = (new + (offset+8)) mod 64
@@ -390,6 +391,9 @@ auto cpc_compressor<A>::uncompress_surprising_values(const uint32_t* data, uint3
390
391
  vector_u32 pairs(num_pairs, 0, allocator);
391
392
  const uint8_t num_base_bits = golomb_choose_number_of_base_bits(k + num_pairs, num_pairs);
392
393
  low_level_uncompress_pairs(pairs.data(), num_pairs, num_base_bits, data, data_words);
394
+ for (uint32_t i = 0; i < num_pairs; i++) {
395
+ if ((pairs[i] >> 6) >= k) throw std::out_of_range("row index out of range");
396
+ }
393
397
  return pairs;
394
398
  }
395
399
 
@@ -472,8 +476,10 @@ static inline void maybe_flush_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, uint32
472
476
  }
473
477
  }
474
478
 
475
- static inline void maybe_fill_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, const uint32_t* wordarr, uint32_t& wordindex, uint8_t minbits) {
479
+ static inline void maybe_fill_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, const uint32_t* wordarr, uint32_t& wordindex,
480
+ uint32_t numwords, uint8_t minbits) {
476
481
  if (bufbits < minbits) {
482
+ if (wordindex >= numwords) throw std::out_of_range("compressed data over-run");
477
483
  bitbuf |= static_cast<uint64_t>(wordarr[wordindex++]) << bufbits;
478
484
  bufbits += 32;
479
485
  }
@@ -530,7 +536,7 @@ void cpc_compressor<A>::low_level_uncompress_bytes(
530
536
  if (compressed_words == nullptr) throw std::logic_error("compressed_words == NULL");
531
537
 
532
538
  for (uint32_t byte_index = 0; byte_index < num_bytes_to_decode; byte_index++) {
533
- maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, 12); // ensure 12 bits in bit buffer
539
+ maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, 12); // ensure 12 bits in bit buffer
534
540
 
535
541
  const size_t peek12 = bitbuf & 0xfff; // These 12 bits will include an entire Huffman codeword.
536
542
  const uint16_t lookup = decoding_table[peek12];
@@ -547,6 +553,7 @@ void cpc_compressor<A>::low_level_uncompress_bytes(
547
553
 
548
554
  static inline uint64_t read_unary(
549
555
  const uint32_t* compressed_words,
556
+ uint32_t num_compressed_words,
550
557
  uint32_t& next_word_index,
551
558
  uint64_t& bitbuf,
552
559
  uint8_t& bufbits
@@ -646,7 +653,7 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
646
653
  // y_delta_lo (basebits)
647
654
 
648
655
  for (uint32_t pair_index = 0; pair_index < num_pairs_to_decode; pair_index++) {
649
- maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, 12); // ensure 12 bits in bit buffer
656
+ maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, 12); // ensure 12 bits in bit buffer
650
657
  const size_t peek12 = bitbuf & 0xfff;
651
658
  const uint16_t lookup = length_limited_unary_decoding_table65[peek12];
652
659
  const uint8_t code_word_length = lookup >> 8;
@@ -654,9 +661,9 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
654
661
  bitbuf >>= code_word_length;
655
662
  bufbits -= code_word_length;
656
663
 
657
- const uint64_t golomb_hi = read_unary(compressed_words, word_index, bitbuf, bufbits);
664
+ const uint64_t golomb_hi = read_unary(compressed_words, num_compressed_words, word_index, bitbuf, bufbits);
658
665
 
659
- maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_base_bits); // ensure num_base_bits in bit buffer
666
+ maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, num_base_bits); // ensure num_base_bits in bit buffer
660
667
  const uint64_t golomb_lo = bitbuf & golomb_lo_mask;
661
668
  bitbuf >>= num_base_bits;
662
669
  bufbits -= num_base_bits;
@@ -666,6 +673,7 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
666
673
  if (y_delta > 0) predicted_col_index = 0;
667
674
  const uint32_t row_index = static_cast<uint32_t>(predicted_row_index + y_delta);
668
675
  const uint8_t col_index = predicted_col_index + x_delta;
676
+ if (col_index > 63) throw std::out_of_range("column index out of range");
669
677
  const uint32_t row_col = (row_index << 6) | col_index;
670
678
  pair_array[pair_index] = row_col;
671
679
  predicted_row_index = row_index;
@@ -676,6 +684,7 @@ void cpc_compressor<A>::low_level_uncompress_pairs(
676
684
 
677
685
  uint64_t read_unary(
678
686
  const uint32_t* compressed_words,
687
+ uint32_t num_compressed_words,
679
688
  uint32_t& next_word_index,
680
689
  uint64_t& bitbuf,
681
690
  uint8_t& bufbits
@@ -683,7 +692,7 @@ uint64_t read_unary(
683
692
  if (compressed_words == nullptr) throw std::logic_error("compressed_words == NULL");
684
693
  size_t subtotal = 0;
685
694
  while (true) {
686
- maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, next_word_index, 8); // ensure 8 bits in bit buffer
695
+ maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, next_word_index, num_compressed_words, 8); // ensure 8 bits in bit buffer
687
696
 
688
697
  const uint8_t peek8 = bitbuf & 0xff; // These 8 bits include either all or part of the Unary codeword
689
698
  const uint8_t trailing_zeros = byte_trailing_zeros_table[peek8];
@@ -315,6 +315,7 @@ private:
315
315
  inline size_t copy_hip_to_mem(void* dst) const;
316
316
 
317
317
  static void check_lg_k(uint8_t lg_k);
318
+ static void check_num_coupons(uint8_t lg_k, uint32_t num_coupons, uint32_t num_pairs);
318
319
 
319
320
  friend cpc_compressor<A>;
320
321
  friend cpc_union_alloc<A>;