Skip to content

Commit c827ef0

Browse files
committed
Add update_theta_sketch::get_result() to trim to k in one pass
Returns a compact_theta_sketch bounded to the nominal size k (2^lg_k), matching the at-most-k guarantee of theta_union::get_result(). It does the nth_element/erase cutback directly on the output vector compact() already allocates, avoiding the throwaway 2k rehash of trim()+compact(). Co-authored-by: Isaac
1 parent de8553b commit c827ef0

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

‎theta/include/theta_sketch.hpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,13 @@ class update_theta_sketch_alloc: public theta_sketch_alloc<Allocator> {
336336
*/
337337
compact_theta_sketch_alloc<Allocator> compact(bool ordered = true) const;
338338

339+
/**
340+
* Produces a compact sketch trimmed to the nominal size k in a single pass.
341+
* Like trim() followed by compact(), but without rebuilding the hash table. Result is unordered.
342+
* @return compact sketch with at most k retained entries
343+
*/
344+
compact_theta_sketch_alloc<Allocator> get_result() const;
345+
339346
virtual iterator begin();
340347
virtual iterator end();
341348
virtual const_iterator begin() const;
@@ -518,6 +525,7 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc<Allocator> {
518525
template<typename E, typename EK, typename P, typename S, typename CS, typename A> friend class theta_union_base;
519526
template<typename E, typename EK, typename P, typename S, typename CS, typename A> friend class theta_intersection_base;
520527
template<typename E, typename EK, typename CS, typename A> friend class theta_set_difference_base;
528+
template<typename A> friend class update_theta_sketch_alloc;
521529
compact_theta_sketch_alloc(bool is_empty, bool is_ordered, uint16_t seed_hash, uint64_t theta, std::vector<uint64_t, Allocator>&& entries);
522530
};
523531

‎theta/include/theta_sketch_impl.hpp‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <sstream>
2424
#include <vector>
2525
#include <stdexcept>
26+
#include <algorithm>
2627

2728
#include "binomial_bounds.hpp"
2829
#include "theta_helpers.hpp"
@@ -243,6 +244,25 @@ compact_theta_sketch_alloc<A> update_theta_sketch_alloc<A>::compact(bool ordered
243244
return compact_theta_sketch_alloc<A>(*this, ordered);
244245
}
245246

247+
template<typename A>
248+
compact_theta_sketch_alloc<A> update_theta_sketch_alloc<A>::get_result() const {
249+
std::vector<uint64_t, A> entries(table_.allocator_);
250+
if (is_empty()) {
251+
return compact_theta_sketch_alloc<A>(true, true, get_seed_hash(), get_theta64(), std::move(entries));
252+
}
253+
entries.reserve(get_num_retained());
254+
std::copy(begin(), end(), std::back_inserter(entries));
255+
uint64_t theta = table_.theta_;
256+
const uint32_t nominal_num = 1 << table_.lg_nom_size_;
257+
if (entries.size() > nominal_num) {
258+
std::nth_element(entries.begin(), entries.begin() + nominal_num, entries.end());
259+
theta = entries[nominal_num];
260+
entries.erase(entries.begin() + nominal_num, entries.end());
261+
}
262+
const bool ordered = entries.size() <= 1;
263+
return compact_theta_sketch_alloc<A>(false, ordered, get_seed_hash(), theta, std::move(entries));
264+
}
265+
246266
template<typename A>
247267
void update_theta_sketch_alloc<A>::print_specifics(std::ostringstream& os) const {
248268
os << " lg nominal size : " << static_cast<int>(table_.lg_nom_size_) << std::endl;

‎theta/test/theta_sketch_test.cpp‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <sstream>
2323
#include <vector>
2424
#include <stdexcept>
25+
#include <algorithm>
2526

2627
#include <catch2/catch.hpp>
2728
#include <theta_sketch.hpp>
@@ -167,6 +168,49 @@ TEST_CASE("theta sketch: estimation", "[theta_sketch]") {
167168
REQUIRE(compact_sketch.get_upper_bound(1) > n);
168169
}
169170

171+
TEST_CASE("theta sketch: get_result trims to k in one pass", "[theta_sketch]") {
172+
update_theta_sketch update_sketch = update_theta_sketch::builder().build();
173+
const int n = 8000;
174+
for (int i = 0; i < n; i++) update_sketch.update(i);
175+
const uint32_t k = 1 << theta_constants::DEFAULT_LG_K;
176+
REQUIRE(update_sketch.get_num_retained() > k); // over-provisioned before trimming
177+
178+
compact_theta_sketch result = update_sketch.get_result();
179+
REQUIRE_FALSE(result.is_empty());
180+
REQUIRE(result.is_estimation_mode());
181+
REQUIRE(result.get_num_retained() == k); // trimmed to nominal size
182+
REQUIRE_FALSE(result.is_ordered()); // unordered: no sort performed
183+
184+
// fused get_result() must match trim() + compact()
185+
update_theta_sketch trimmed = update_sketch;
186+
trimmed.trim();
187+
compact_theta_sketch expected = trimmed.compact(false);
188+
REQUIRE(result.get_theta64() == expected.get_theta64());
189+
REQUIRE(result.get_num_retained() == expected.get_num_retained());
190+
REQUIRE(result.get_estimate() == expected.get_estimate());
191+
192+
// same set of retained hashes (order-independent)
193+
std::vector<uint64_t> a(result.begin(), result.end());
194+
std::vector<uint64_t> b(expected.begin(), expected.end());
195+
std::sort(a.begin(), a.end());
196+
std::sort(b.begin(), b.end());
197+
REQUIRE(a == b);
198+
}
199+
200+
TEST_CASE("theta sketch: get_result on empty and below-k sketches", "[theta_sketch]") {
201+
compact_theta_sketch empty_result = update_theta_sketch::builder().build().get_result();
202+
REQUIRE(empty_result.is_empty());
203+
REQUIRE(empty_result.get_num_retained() == 0);
204+
205+
update_theta_sketch small = update_theta_sketch::builder().build();
206+
for (int i = 0; i < 100; i++) small.update(i);
207+
REQUIRE_FALSE(small.is_estimation_mode());
208+
compact_theta_sketch small_result = small.get_result();
209+
REQUIRE_FALSE(small_result.is_estimation_mode());
210+
REQUIRE(small_result.get_num_retained() == 100); // below k: nothing trimmed
211+
REQUIRE(small_result.get_estimate() == Approx(100.0));
212+
}
213+
170214
TEST_CASE("theta sketch: deserialize compact v1 empty from java", "[theta_sketch]") {
171215
std::ifstream is;
172216
is.exceptions(std::ios::failbit | std::ios::badbit);

0 commit comments

Comments
 (0)