Skip to content

Commit 6b2e542

Browse files
Merge pull request #281 from apache/kll_converting_constructor
KLL converting constructor
2 parents 763f924 + a4d8a46 commit 6b2e542

3 files changed

Lines changed: 174 additions & 18 deletions

File tree

kll/include/kll_sketch.hpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ class kll_sketch {
182182
kll_sketch& operator=(const kll_sketch& other);
183183
kll_sketch& operator=(kll_sketch&& other);
184184

185+
/*
186+
* Type converting constructor.
187+
* @param other sketch of a different type
188+
* @param allocator instance of an Allocator
189+
*/
190+
template<typename TT, typename CC, typename AA>
191+
explicit kll_sketch(const kll_sketch<TT, CC, AA> & other, const A& allocator = A());
192+
185193
/**
186194
* Updates this sketch with the given data item.
187195
* @param value an item from a stream of items
@@ -208,6 +216,13 @@ class kll_sketch {
208216
*/
209217
uint16_t get_k() const;
210218

219+
/**
220+
* Returns min_k, which is an internal parameter for error estimation
221+
* after merging sketches with different k.
222+
* @return parameter min_k
223+
*/
224+
uint16_t get_min_k() const;
225+
211226
/**
212227
* Returns the length of the input stream.
213228
* @return stream length
@@ -220,6 +235,13 @@ class kll_sketch {
220235
*/
221236
uint32_t get_num_retained() const;
222237

238+
/**
239+
* Returns the current capacity in items allocated by the sketch.
240+
* For internal use.
241+
* @return the capacity
242+
*/
243+
uint32_t get_capacity() const;
244+
223245
/**
224246
* Returns true if this sketch is in estimation mode.
225247
* @return estimation mode flag
@@ -390,7 +412,7 @@ class kll_sketch {
390412
/**
391413
* Computes size needed to serialize the current state of the sketch.
392414
* This version is for fixed-size arithmetic types (integral and floating point).
393-
* @param instance of a SerDe
415+
* @param serde instance of a SerDe
394416
* @return size in bytes needed to serialize this sketch
395417
*/
396418
template<typename TT = T, typename SerDe = S, typename std::enable_if<std::is_arithmetic<TT>::value, int>::type = 0>
@@ -399,7 +421,7 @@ class kll_sketch {
399421
/**
400422
* Computes size needed to serialize the current state of the sketch.
401423
* This version is for all other types and can be expensive since every item needs to be looked at.
402-
* @param instance of a SerDe
424+
* @param serde instance of a SerDe
403425
* @return size in bytes needed to serialize this sketch
404426
*/
405427
template<typename TT = T, typename SerDe = S, typename std::enable_if<!std::is_arithmetic<TT>::value, int>::type = 0>
@@ -459,7 +481,7 @@ class kll_sketch {
459481
/**
460482
* This method deserializes a sketch from a given stream.
461483
* @param is input stream
462-
* @param instance of an Allocator
484+
* @param allocator instance of an Allocator
463485
* @return an instance of a sketch
464486
*
465487
* Deprecated, to be removed in the next major version
@@ -469,8 +491,8 @@ class kll_sketch {
469491
/**
470492
* This method deserializes a sketch from a given stream.
471493
* @param is input stream
472-
* @param instance of a SerDe
473-
* @param instance of an Allocator
494+
* @param serde instance of a SerDe
495+
* @param allocator instance of an Allocator
474496
* @return an instance of a sketch
475497
*/
476498
template<typename SerDe = S>
@@ -480,7 +502,7 @@ class kll_sketch {
480502
* This method deserializes a sketch from a given array of bytes.
481503
* @param bytes pointer to the array of bytes
482504
* @param size the size of the array
483-
* @param instance of an Allocator
505+
* @param allocator instance of an Allocator
484506
* @return an instance of a sketch
485507
*
486508
* Deprecated, to be removed in the next major version
@@ -491,8 +513,8 @@ class kll_sketch {
491513
* This method deserializes a sketch from a given array of bytes.
492514
* @param bytes pointer to the array of bytes
493515
* @param size the size of the array
494-
* @param instance of a SerDe
495-
* @param instance of an Allocator
516+
* @param serde instance of a SerDe
517+
* @param allocator instance of an Allocator
496518
* @return an instance of a sketch
497519
*/
498520
template<typename SerDe = S>
@@ -606,6 +628,8 @@ class kll_sketch {
606628
static void check_serial_version(uint8_t serial_version);
607629
static void check_family_id(uint8_t family_id);
608630

631+
void check_sorting() const;
632+
609633
// implementations for floating point types
610634
template<typename TT = T, typename std::enable_if<std::is_floating_point<TT>::value, int>::type = 0>
611635
static const TT& get_invalid_value() {

kll/include/kll_sketch_impl.hpp

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <stdexcept>
2727

2828
#include "conditional_forward.hpp"
29+
#include "count_zeros.hpp"
2930
#include "memory_operations.hpp"
3031
#include "kll_helper.hpp"
3132

@@ -69,7 +70,7 @@ max_value_(nullptr),
6970
is_level_zero_sorted_(other.is_level_zero_sorted_)
7071
{
7172
items_ = allocator_.allocate(items_size_);
72-
std::copy(other.items_ + levels_[0], other.items_ + levels_[num_levels_], items_ + levels_[0]);
73+
for (auto i = levels_[0]; i < levels_[num_levels_]; ++i) new (&items_[i]) T(other.items_[i]);
7374
if (other.min_value_ != nullptr) min_value_ = new (allocator_.allocate(1)) T(*other.min_value_);
7475
if (other.max_value_ != nullptr) max_value_ = new (allocator_.allocate(1)) T(*other.max_value_);
7576
}
@@ -147,6 +148,52 @@ kll_sketch<T, C, S, A>::~kll_sketch() {
147148
}
148149
}
149150

151+
template<typename T, typename C, typename S, typename A>
152+
template<typename TT, typename CC, typename AA>
153+
kll_sketch<T, C, S, A>::kll_sketch(const kll_sketch<TT, CC, AA>& other, const A& allocator):
154+
allocator_(allocator),
155+
k_(other.get_k()),
156+
m_(DEFAULT_M),
157+
min_k_(other.get_min_k()),
158+
n_(other.get_n()),
159+
num_levels_(1),
160+
levels_(2, 0, allocator),
161+
items_(nullptr),
162+
items_size_(other.get_capacity()),
163+
min_value_(nullptr),
164+
max_value_(nullptr),
165+
is_level_zero_sorted_(false)
166+
{
167+
static_assert(
168+
std::is_constructible<T, TT>::value,
169+
"Type converting constructor requires new type to be constructible from existing type"
170+
);
171+
items_ = allocator_.allocate(items_size_);
172+
levels_[0] = items_size_ - other.get_num_retained();
173+
levels_[1] = items_size_;
174+
175+
if (!other.is_empty()) {
176+
min_value_ = new (allocator_.allocate(1)) T(other.get_min_value());
177+
max_value_ = new (allocator_.allocate(1)) T(other.get_max_value());
178+
size_t index = levels_[0];
179+
for (auto pair: other) {
180+
new (&items_[index]) T(pair.first);
181+
const uint8_t level = count_trailing_zeros_in_u64(pair.second);
182+
if (level == num_levels_) {
183+
++num_levels_;
184+
levels_.resize(num_levels_ + 1);
185+
levels_[level] = index;
186+
levels_[num_levels_] = items_size_;
187+
} else if (level < num_levels_ - 1 || level > num_levels_) {
188+
throw std::invalid_argument("corrupt source sketch");
189+
}
190+
++index;
191+
}
192+
}
193+
check_sorting();
194+
assert_correct_total_weight();
195+
}
196+
150197
template<typename T, typename C, typename S, typename A>
151198
template<typename FwdT>
152199
void kll_sketch<T, C, S, A>::update(FwdT&& value) {
@@ -210,6 +257,11 @@ uint16_t kll_sketch<T, C, S, A>::get_k() const {
210257
return k_;
211258
}
212259

260+
template<typename T, typename C, typename S, typename A>
261+
uint16_t kll_sketch<T, C, S, A>::get_min_k() const {
262+
return min_k_;
263+
}
264+
213265
template<typename T, typename C, typename S, typename A>
214266
uint64_t kll_sketch<T, C, S, A>::get_n() const {
215267
return n_;
@@ -220,6 +272,11 @@ uint32_t kll_sketch<T, C, S, A>::get_num_retained() const {
220272
return levels_[num_levels_] - levels_[0];
221273
}
222274

275+
template<typename T, typename C, typename S, typename A>
276+
uint32_t kll_sketch<T, C, S, A>::get_capacity() const {
277+
return items_size_;
278+
}
279+
223280
template<typename T, typename C, typename S, typename A>
224281
bool kll_sketch<T, C, S, A>::is_estimation_mode() const {
225282
return num_levels_ > 1;
@@ -780,17 +837,27 @@ void kll_sketch<T, C, S, A>::sort_level_zero() {
780837
}
781838
}
782839

840+
template<typename T, typename C, typename S, typename A>
841+
void kll_sketch<T, C, S, A>::check_sorting() const {
842+
// not checking level 0
843+
for (uint8_t level = 1; level < num_levels_; ++level) {
844+
const auto from = items_ + levels_[level];
845+
const auto to = items_ + levels_[level + 1];
846+
if (!std::is_sorted(from, to, C())) {
847+
throw std::logic_error("levels must be sorted");
848+
}
849+
}
850+
}
851+
783852
template<typename T, typename C, typename S, typename A>
784853
template<bool inclusive>
785854
quantile_sketch_sorted_view<T, C, A> kll_sketch<T, C, S, A>::get_sorted_view(bool cumulative) const {
786855
const_cast<kll_sketch*>(this)->sort_level_zero(); // allow this side effect
787856
quantile_sketch_sorted_view<T, C, A> view(get_num_retained(), allocator_);
788-
uint8_t level = 0;
789-
while (level < num_levels_) {
857+
for (uint8_t level = 0; level < num_levels_; ++level) {
790858
const auto from = items_ + levels_[level];
791859
const auto to = items_ + levels_[level + 1]; // exclusive
792860
view.add(from, to, 1 << level);
793-
++level;
794861
}
795862
if (cumulative) view.template convert_to_cummulative<inclusive>();
796863
return view;

kll/test/kll_sketch_test.cpp

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ static std::string testBinaryInputPath = "test/";
3939
#endif
4040

4141
// typical usage would be just kll_sketch<float> or kll_sketch<std::string>, but here we use test_allocator
42-
typedef kll_sketch<float, std::less<float>, serde<float>, test_allocator<float>> kll_float_sketch;
42+
using kll_float_sketch = kll_sketch<float, std::less<float>, serde<float>, test_allocator<float>>;
4343
// let std::string use the default allocator for simplicity, otherwise we need to define "less" and "serde"
44-
typedef kll_sketch<std::string, std::less<std::string>, serde<std::string>, test_allocator<std::string>> kll_string_sketch;
44+
using kll_string_sketch = kll_sketch<std::string, std::less<std::string>, serde<std::string>, test_allocator<std::string>>;
4545

4646
TEST_CASE("kll sketch", "[kll_sketch]") {
4747

@@ -75,7 +75,7 @@ TEST_CASE("kll sketch", "[kll_sketch]") {
7575
(void) it; // to suppress "unused" warning
7676
FAIL("should be no iterations over an empty sketch");
7777
}
78-
}
78+
}
7979

8080
SECTION("get bad quantile") {
8181
kll_float_sketch sketch(200, 0);
@@ -835,10 +835,75 @@ TEST_CASE("kll sketch", "[kll_sketch]") {
835835
REQUIRE((*it).second == 3);
836836
}
837837
}
838-
// cleanup
839-
if (test_allocator_total_bytes != 0) {
840-
REQUIRE(test_allocator_total_bytes == 0);
838+
839+
SECTION("type conversion: empty") {
840+
kll_sketch<double> kll_double;
841+
kll_sketch<float> kll_float(kll_double);
842+
REQUIRE(kll_float.is_empty());
843+
REQUIRE(kll_float.get_k() == kll_double.get_k());
844+
REQUIRE(kll_float.get_n() == 0);
845+
REQUIRE(kll_float.get_num_retained() == 0);
846+
}
847+
848+
SECTION("type conversion: over k") {
849+
kll_sketch<double> kll_double;
850+
for (int i = 0; i < 1000; ++i) kll_double.update(static_cast<double>(i));
851+
kll_sketch<float> kll_float(kll_double);
852+
REQUIRE(!kll_float.is_empty());
853+
REQUIRE(kll_float.get_k() == kll_double.get_k());
854+
REQUIRE(kll_float.get_n() == kll_double.get_n());
855+
REQUIRE(kll_float.get_num_retained() == kll_double.get_num_retained());
856+
857+
auto sv_float = kll_float.get_sorted_view(false);
858+
auto sv_double = kll_double.get_sorted_view(false);
859+
auto sv_float_it = sv_float.begin();
860+
auto sv_double_it = sv_double.begin();
861+
while (sv_float_it != sv_float.end()) {
862+
REQUIRE(sv_double_it != sv_double.end());
863+
auto float_pair = *sv_float_it;
864+
auto double_pair = *sv_double_it;
865+
REQUIRE(float_pair.first == Approx(double_pair.first).margin(0.01));
866+
REQUIRE(float_pair.second == double_pair.second);
867+
++sv_float_it;
868+
++sv_double_it;
869+
}
870+
REQUIRE(sv_double_it == sv_double.end());
871+
}
872+
873+
class A {
874+
int val;
875+
public:
876+
A(int val): val(val) {}
877+
int get_val() const { return val; }
878+
};
879+
880+
struct less_A {
881+
bool operator()(const A& a1, const A& a2) const { return a1.get_val() < a2.get_val(); }
882+
};
883+
884+
class B {
885+
int val;
886+
public:
887+
explicit B(const A& a): val(a.get_val()) {}
888+
int get_val() const { return val; }
889+
};
890+
891+
struct less_B {
892+
bool operator()(const B& b1, const B& b2) const { return b1.get_val() < b2.get_val(); }
893+
};
894+
895+
SECTION("type conversion: custom types") {
896+
kll_sketch<A, less_A> sa;
897+
sa.update(1);
898+
sa.update(2);
899+
sa.update(3);
900+
901+
kll_sketch<B, less_B> sb(sa);
902+
REQUIRE(sb.get_n() == 3);
841903
}
904+
905+
// cleanup
906+
REQUIRE(test_allocator_total_bytes == 0);
842907
}
843908

844909
} /* namespace datasketches */

0 commit comments

Comments
 (0)