Skip to content

Commit 6f18dbd

Browse files
committed
add: cpp test of top_k data structure.
1 parent 9b9209d commit 6f18dbd

3 files changed

Lines changed: 179 additions & 42 deletions

File tree

src/types/topk.cc

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ static uint32_t MurmurHash2(const void *key, int len, uint32_t seed) {
7676
len -= 4;
7777
}
7878

79-
// Handle the last few bytes of the input array
79+
// Handle the last few bytes of the input heap
8080

8181
switch (len) {
8282
case 3:
@@ -219,65 +219,65 @@ static uint32_t MurmurHash2(const void *key, int len, uint32_t seed) {
219219
}
220220

221221
/* ---------------------------------------------------------------------- */
222-
void BlockSplitTopK::swapHeapBucket(HeapBucket *a, HeapBucket* b) {
223-
HeapBucket tmp = *a;
224-
a->count = b->count;
225-
a->fp = b->fp;
226-
a->itemlen = b->itemlen;
227-
a->item = b->item;
228-
229-
b->count = tmp.count;
230-
b->fp = tmp.fp;
231-
b->itemlen = tmp.itemlen;
232-
b->item = tmp.item;
233-
}
234-
235222
void BlockSplitTopK::heapifyDown(int start) {
236-
size_t parent = start;
223+
size_t child = start;
237224

238225
// check whether larger than children
239-
if (heap_size < 2 || (heap_size - 2) / 2 < parent) {
226+
if (heap_size < 2 || (heap_size - 2) / 2 < child) {
240227
return;
241228
}
242229

243-
while (parent < heap_size) {
244-
size_t left_child = 2 * parent + 1;
245-
size_t right_child = left_child + 1;
230+
child = 2 * child + 1;
231+
if ((child + 1) < heap_size && (heap[child].count > heap[child + 1].count)) {
232+
++child;
233+
}
234+
if (heap[child].count > heap[start].count) {
235+
return;
236+
}
246237

247-
// check whether left child is larger than parent
248-
if (left_child < heap_size && heap[left_child].count < heap[parent].count) {
249-
parent = left_child;
250-
}
251-
// check whether right child is larger than parent
252-
if (right_child < heap_size && heap[right_child].count < heap[parent].count) {
253-
parent = right_child;
254-
}
255-
if (parent == left_child || parent == right_child) {
256-
swapHeapBucket(&heap[(parent-1)/2], &heap[parent]);
257-
} else {
238+
HeapBucket top;
239+
memcpy(&top, &heap[start], sizeof(HeapBucket));
240+
do {
241+
memcpy(&heap[start], &heap[child], sizeof(HeapBucket));
242+
start = child;
243+
244+
if ((heap_size - 2) / 2 < child) {
258245
break;
259246
}
260-
}
247+
child = 2 * child + 1;
248+
249+
if ((child + 1) < heap_size && (heap[child].count > heap[child + 1].count)) {
250+
++child;
251+
}
252+
} while (heap[child].count < top.count);
253+
memcpy(&heap[start], &top, sizeof(HeapBucket));
261254
}
262255

263256
void BlockSplitTopK::heapifyUp(int start) {
264-
size_t child = start;
257+
size_t parent = start;
265258

266259
// check whether smaller than parent
267-
if (heap_size < 2 || child == 0) {
260+
if (heap_size < 2 || parent == 0) {
268261
return;
269262
}
270263

271-
while (child > 0) {
272-
size_t parent = (child - 1) / 2;
264+
parent = (parent - 1) / 2;
265+
if (heap[parent].count > heap[start].count) {
266+
return;
267+
}
273268

274-
if (parent >= 0 && heap[child].count < heap[parent].count) {
275-
swapHeapBucket(&heap[parent], &heap[child]);
276-
child = parent;
277-
} else {
269+
HeapBucket bottom;
270+
memcpy(&bottom, &heap[start], sizeof(HeapBucket));
271+
do {
272+
memcpy(&heap[start], &heap[parent], sizeof(HeapBucket));
273+
start = parent;
274+
275+
if (start == 0) {
278276
break;
279277
}
280-
}
278+
parent = (parent - 1) / 2;
279+
} while (heap[parent].count > bottom.count);
280+
memcpy(&heap[start], &bottom, sizeof(HeapBucket));
281281
}
282282

283283
int BlockSplitTopK::checkExistInHeap(const std::string &item) {
@@ -310,8 +310,10 @@ void BlockSplitTopK::Add(const std::string &item, uint32_t increment) {
310310
if (buckets[loc].count == 0) {
311311
buckets[loc].fp = fp;
312312
buckets[loc].count = increment;
313+
maxCount = std::max(maxCount, buckets[loc].count);
313314
} else if (buckets[loc].fp == fp && location != -1) {
314315
buckets[loc].count += increment;
316+
maxCount = std::max(maxCount, buckets[loc].count);
315317
} else {
316318
// decay
317319
uint32_t local_incr = increment;
@@ -330,12 +332,12 @@ void BlockSplitTopK::Add(const std::string &item, uint32_t increment) {
330332
if (buckets[loc].count == 0) {
331333
buckets[loc].fp = fp;
332334
buckets[loc].count = 1;
335+
maxCount = std::max(maxCount, buckets[loc].count);
333336
break;
334337
}
335338
}
336339
}
337340
}
338-
maxCount = std::max(maxCount, buckets[loc].count);
339341
}
340342

341343
if (k == heap_size) {

src/types/topk.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ class BlockSplitTopK {
7272
void heapifyUp(int start);
7373
int checkExistInHeap(const std::string &item);
7474
int cmpHeapBucketCount(const HeapBucket &a, const HeapBucket &b);
75-
void swapHeapBucket(HeapBucket *a, HeapBucket *b);
7675

7776
uint32_t k;
7877
uint32_t width;

tests/cppunit/types/topk_test.cc

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*
19+
*/
20+
21+
#include <gtest/gtest.h>
22+
23+
#include <memory>
24+
25+
#include "test_base.h"
26+
#include "types/redis_topk.h"
27+
28+
static constexpr uint32_t k = 5;
29+
static constexpr uint32_t width = 7;
30+
static constexpr uint32_t depth = 8;
31+
static constexpr double decay = 0.9;
32+
33+
class RedisTopKTest : public TestBase {
34+
protected:
35+
explicit RedisTopKTest() : TestBase() {
36+
top_k_ = std::make_unique<redis::TopK>(storage_.get(), "topk_ns");
37+
}
38+
~RedisTopKTest() override = default;
39+
40+
void SetUp() override {
41+
key_ = "test_topk->key";
42+
top_k_->Reserve(*ctx_, key_, k, width, depth, decay);
43+
}
44+
45+
void TearDown() override {}
46+
47+
std::unique_ptr<redis::TopK> top_k_;
48+
};
49+
50+
TEST_F(RedisTopKTest, TestTopKInfo) {
51+
// test exist key
52+
redis::TopKInfo info1;
53+
top_k_->Info(*ctx_, key_, &info1);
54+
ASSERT_EQ(info1.k, k);
55+
ASSERT_EQ(info1.width, width);
56+
ASSERT_EQ(info1.depth, depth);
57+
ASSERT_EQ(info1.decay, decay);
58+
59+
// test not exist key
60+
redis::TopKInfo info2;
61+
auto s = top_k_->Info(*ctx_, "not_exist_key", &info2);
62+
ASSERT_FALSE(s.ok());
63+
}
64+
65+
TEST_F(RedisTopKTest, TestTopKAddAndQuery) {
66+
// test not exist key
67+
std::string no_exist_key = "no_exist_key";
68+
auto s = top_k_->Add(*ctx_, no_exist_key, "1");
69+
ASSERT_FALSE(s.ok());
70+
71+
bool exist;
72+
s = top_k_->Query(*ctx_, no_exist_key, "1", &exist);
73+
ASSERT_FALSE(s.ok());
74+
75+
std::vector<std::string> list;
76+
s = top_k_->List(*ctx_, no_exist_key, list);
77+
ASSERT_FALSE(s.ok());
78+
79+
// test exist key
80+
std::vector<std::string> values1 = {"1", "2", "3", "4", "5"};
81+
std::vector<std::string> values2 = {"6", "7", "8", "9", "10"};
82+
std::unordered_set<std::string> values_set1(values1.begin(), values1.end());
83+
std::unordered_set<std::string> values_set2(values2.begin(), values2.end());
84+
85+
// found not exist values1
86+
for (size_t i = 0; i < values1.size(); ++i) {
87+
bool found = true;
88+
top_k_->Query(*ctx_, key_, values1[i], &found);
89+
ASSERT_FALSE(found);
90+
}
91+
// add values1, and query values1.
92+
for (size_t i = 0; i < values1.size(); ++i) {
93+
top_k_->Add(*ctx_, key_, values1[i]);
94+
bool found = false;
95+
top_k_->Query(*ctx_, key_, values1[i], &found);
96+
ASSERT_TRUE(found);
97+
}
98+
for (size_t i = 0; i < values1.size(); ++i) {
99+
bool found = false;
100+
top_k_->Query(*ctx_, key_, values1[i], &found);
101+
ASSERT_TRUE(found);
102+
}
103+
104+
// found topk list.
105+
std::vector<std::string> top_k_list;
106+
top_k_->List(*ctx_, key_, top_k_list);
107+
ASSERT_EQ(top_k_list.size(), values1.size());
108+
for (size_t i = 0; i < k; ++i) {
109+
ASSERT_TRUE(values_set1.find(top_k_list[i]) != values_set1.end());
110+
}
111+
112+
// heap is full, need remove values1.
113+
for (size_t i = 0; i < values2.size(); ++i) {
114+
bool found = false;
115+
// due to decay, topk is possiable to remove values1.
116+
while (!found) {
117+
top_k_->Add(*ctx_, key_, values2[i]);
118+
top_k_->Query(*ctx_, key_, values2[i], &found);
119+
}
120+
top_k_->Add(*ctx_, key_, values2[i]);
121+
}
122+
123+
// values1 is removed.
124+
for (size_t i = 0; i < values1.size(); ++i) {
125+
bool found = true;
126+
top_k_->Query(*ctx_, key_, values1[i], &found);
127+
ASSERT_FALSE(found);
128+
}
129+
130+
// found topk list.
131+
top_k_list.clear();
132+
top_k_->List(*ctx_, key_, top_k_list);
133+
for (size_t i = 0; i < top_k_list.size(); ++i) {
134+
ASSERT_TRUE(values_set2.find(top_k_list[i]) != values_set2.end());
135+
}
136+
}

0 commit comments

Comments
 (0)