diff --git a/tuple/include/array_tuple_intersection.hpp b/tuple/include/array_tuple_intersection.hpp index 5f6ef264..5598683e 100644 --- a/tuple/include/array_tuple_intersection.hpp +++ b/tuple/include/array_tuple_intersection.hpp @@ -48,6 +48,15 @@ class array_tuple_intersection: public tuple_intersection + void update(FwdSketch&& sketch); + /** * Produces a copy of the current state of the intersection. * If update() was not called, the state is the infinite "universe", diff --git a/tuple/include/array_tuple_intersection_impl.hpp b/tuple/include/array_tuple_intersection_impl.hpp index e9e8ab44..e7ad07f9 100644 --- a/tuple/include/array_tuple_intersection_impl.hpp +++ b/tuple/include/array_tuple_intersection_impl.hpp @@ -17,12 +17,26 @@ * under the License. */ +#include +#include + namespace datasketches { template array_tuple_intersection::array_tuple_intersection(uint64_t seed, const Policy& policy, const Allocator& allocator): Base(seed, policy, allocator) {} +template +template +void array_tuple_intersection::update(FwdSketch&& sketch) { + const uint8_t num_values = this->state_.get_policy().get_external_policy().get_num_values(); + if (sketch.get_num_values() != num_values) { + throw std::invalid_argument("number of values mismatch: intersection has " + std::to_string(num_values) + + ", sketch has " + std::to_string(sketch.get_num_values())); + } + Base::update(std::forward(sketch)); +} + template auto array_tuple_intersection::get_result(bool ordered) const -> CompactSketch { return CompactSketch(this->state_.get_policy().get_external_policy().get_num_values(), Base::get_result(ordered)); diff --git a/tuple/include/array_tuple_union.hpp b/tuple/include/array_tuple_union.hpp index 8f7f497e..d11a1bd1 100644 --- a/tuple/include/array_tuple_union.hpp +++ b/tuple/include/array_tuple_union.hpp @@ -60,6 +60,15 @@ class array_tuple_union: public tuple_union { class builder; + /** + * Update the union with a given sketch. + * The sketch must have the same number of values as the union policy. + * @param sketch to update the union with + * @throw std::invalid_argument if the number of values does not match + */ + template + void update(FwdSketch&& sketch); + CompactSketch get_result(bool ordered = true) const; private: diff --git a/tuple/include/array_tuple_union_impl.hpp b/tuple/include/array_tuple_union_impl.hpp index 9eca00d2..bd2a6759 100644 --- a/tuple/include/array_tuple_union_impl.hpp +++ b/tuple/include/array_tuple_union_impl.hpp @@ -17,6 +17,9 @@ * under the License. */ +#include +#include + namespace datasketches { template @@ -24,6 +27,17 @@ array_tuple_union::array_tuple_union(uint8_t lg_cur_si Base(lg_cur_size, lg_nom_size, rf, p, theta, seed, policy, allocator) {} +template +template +void array_tuple_union::update(FwdSketch&& sketch) { + const uint8_t num_values = this->state_.get_policy().get_external_policy().get_num_values(); + if (sketch.get_num_values() != num_values) { + throw std::invalid_argument("number of values mismatch: union has " + std::to_string(num_values) + + ", sketch has " + std::to_string(sketch.get_num_values())); + } + Base::update(std::forward(sketch)); +} + template auto array_tuple_union::get_result(bool ordered) const -> CompactSketch { return CompactSketch(this->state_.get_policy().get_external_policy().get_num_values(), Base::get_result(ordered)); diff --git a/tuple/test/array_of_doubles_sketch_test.cpp b/tuple/test/array_of_doubles_sketch_test.cpp index 2beef788..d756b281 100644 --- a/tuple/test/array_of_doubles_sketch_test.cpp +++ b/tuple/test/array_of_doubles_sketch_test.cpp @@ -173,6 +173,53 @@ TEST_CASE("aod intersection: half overlap", "[tuple_sketch]") { REQUIRE(result.get_estimate() == Approx(500).margin(0.01)); } +TEST_CASE("aod union: number of values", "[tuple_sketch]") { + auto sketch1 = update_array_of_doubles_sketch::builder(1).build(); + sketch1.update(1, std::vector{1}); + auto sketch3 = update_array_of_doubles_sketch::builder(3).build(); + sketch3.update(1, std::vector{1, 2, 3}); + sketch3.update(2, std::vector{4, 5, 6}); + + // matching number of values: entries for the same key are summed + auto u = array_of_doubles_union::builder(3).build(); + u.update(sketch3); + u.update(sketch3.compact()); + auto result = u.get_result(); + REQUIRE(result.get_num_values() == 3); + REQUIRE(result.get_num_retained() == 2); + for (const auto& entry: result) { + REQUIRE(entry.second.size() == 3); + } + + // mismatch is rejected whether or not keys overlap, and for update and compact sketches + REQUIRE_THROWS_AS(u.update(sketch1), std::invalid_argument); + REQUIRE_THROWS_AS(u.update(sketch1.compact()), std::invalid_argument); + auto u1 = array_of_doubles_union::builder(1).build(); + REQUIRE_THROWS_AS(u1.update(sketch3), std::invalid_argument); + auto empty3 = update_array_of_doubles_sketch::builder(3).build(); + REQUIRE_THROWS_AS(u1.update(empty3), std::invalid_argument); + + // a rejected update leaves the union usable + u1.update(sketch1); + REQUIRE(u1.get_result().get_num_retained() == 1); +} + +TEST_CASE("aod intersection: number of values", "[tuple_sketch]") { + auto sketch1 = update_array_of_doubles_sketch::builder(1).build(); + sketch1.update(1, std::vector{1}); + auto sketch3 = update_array_of_doubles_sketch::builder(3).build(); + sketch3.update(1, std::vector{1, 2, 3}); + + array_of_doubles_intersection intersection(DEFAULT_SEED, 3); + REQUIRE_THROWS_AS(intersection.update(sketch1), std::invalid_argument); + intersection.update(sketch3); + REQUIRE_THROWS_AS(intersection.update(sketch1.compact()), std::invalid_argument); + intersection.update(sketch3.compact()); + auto result = intersection.get_result(); + REQUIRE(result.get_num_values() == 3); + REQUIRE(result.get_num_retained() == 1); +} + TEST_CASE("aod a-not-b: half overlap", "[tuple_sketch]") { double a[1] = {1};