Skip to content

Commit 54b4e4b

Browse files
raphaeldeliobsbodden
authored andcommitted
feat: implementing ops for top K
1 parent f431f09 commit 54b4e4b

File tree

4 files changed

+264
-0
lines changed

4 files changed

+264
-0
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/ops/RedisModulesOperations.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ public CuckooFilterOperations<K> opsForCuckoFilter() {
3535
public TopKOperations<K> opsForTopK() {
3636
return new TopKOperationsImpl<>(client);
3737
}
38+
39+
public TDigestOperations<K> opsForTDigest() {
40+
return new TDigestOperationsImpl<>(client);
41+
}
3842
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,88 @@
11
package com.redis.om.spring.ops.pds;
22

3+
import java.util.List;
4+
import java.util.Map;
5+
36
public interface TopKOperations<K> {
7+
/**
8+
* Create a new TopK filter.
9+
*
10+
* @param key The key of the filter
11+
* @param topk Number of top items to keep
12+
* @return Status string reply
13+
*/
14+
String createFilter(K key, long topk);
15+
16+
/**
17+
* Create a new TopK filter with additional parameters.
18+
*
19+
* @param key The key of the filter
20+
* @param topk Number of top items to keep
21+
* @param width Number of counters kept in each array
22+
* @param depth Number of arrays
23+
* @param decay The probability of reducing a counter in an occupied bucket
24+
* @return Status string reply
25+
*/
26+
String createFilter(K key, long topk, long width, long depth, double decay);
27+
28+
/**
29+
* Add one or more items to the filter.
30+
*
31+
* @param key The key of the filter
32+
* @param items Items to add to the filter
33+
* @return List of items dropped from the filter
34+
*/
35+
List<String> add(K key, String... items);
36+
37+
/**
38+
* Increase the score of an item by increment.
39+
*
40+
* @param key The key of the filter
41+
* @param item Item to increment
42+
* @param increment Increment by this much
43+
* @return Item dropped from the filter, or null if no item was dropped
44+
*/
45+
String incrementBy(K key, String item, long increment);
46+
47+
/**
48+
* Increase the score of multiple items by their increments.
49+
*
50+
* @param key The key of the filter
51+
* @param itemIncrementMap Map of item to increment
52+
* @return List of items dropped from the filter
53+
*/
54+
List<String> incrementBy(K key, Map<String, Long> itemIncrementMap);
55+
56+
/**
57+
* Check if items exist in the filter.
58+
*
59+
* @param key The key of the filter
60+
* @param items Items to check
61+
* @return List of boolean values indicating if items exist in the filter
62+
*/
63+
List<Boolean> query(K key, String... items);
64+
65+
/**
66+
* Return the top k items in the filter.
67+
*
68+
* @param key The key of the filter
69+
* @return List of items
70+
*/
71+
List<String> list(K key);
72+
73+
/**
74+
* Return the top k items with their respective counts.
75+
*
76+
* @param key The key of the filter
77+
* @return Map of items to their counts
78+
*/
79+
Map<String, Long> listWithCount(K key);
480

81+
/**
82+
* Get information about the filter.
83+
*
84+
* @param key The key of the filter
85+
* @return Map of information about the filter
86+
*/
87+
Map<String, Object> info(K key);
588
}

redis-om-spring/src/main/java/com/redis/om/spring/ops/pds/TopKOperationsImpl.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,58 @@
22

33
import com.redis.om.spring.client.RedisModulesClient;
44

5+
import java.util.List;
6+
import java.util.Map;
7+
58
public class TopKOperationsImpl<K> implements TopKOperations<K> {
69
final RedisModulesClient client;
710

811
public TopKOperationsImpl(RedisModulesClient client) {
912
this.client = client;
1013
}
14+
15+
@Override
16+
public String createFilter(K key, long topk) {
17+
return client.clientForTopK().topkReserve(key.toString(), topk);
18+
}
19+
20+
@Override
21+
public String createFilter(K key, long topk, long width, long depth, double decay) {
22+
return client.clientForTopK().topkReserve(key.toString(), topk, width, depth, decay);
23+
}
24+
25+
@Override
26+
public List<String> add(K key, String... items) {
27+
return client.clientForTopK().topkAdd(key.toString(), items);
28+
}
29+
30+
@Override
31+
public String incrementBy(K key, String item, long increment) {
32+
return client.clientForTopK().topkIncrBy(key.toString(), item, increment);
33+
}
34+
35+
@Override
36+
public List<String> incrementBy(K key, Map<String, Long> itemIncrementMap) {
37+
return client.clientForTopK().topkIncrBy(key.toString(), itemIncrementMap);
38+
}
39+
40+
@Override
41+
public List<Boolean> query(K key, String... items) {
42+
return client.clientForTopK().topkQuery(key.toString(), items);
43+
}
44+
45+
@Override
46+
public List<String> list(K key) {
47+
return client.clientForTopK().topkList(key.toString());
48+
}
49+
50+
@Override
51+
public Map<String, Long> listWithCount(K key) {
52+
return client.clientForTopK().topkListWithCount(key.toString());
53+
}
54+
55+
@Override
56+
public Map<String, Object> info(K key) {
57+
return client.clientForTopK().topkInfo(key.toString());
58+
}
1159
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.redis.om.spring.ops.pds;
2+
3+
import com.redis.om.spring.AbstractBaseDocumentTest;
4+
import com.redis.om.spring.ops.RedisModulesOperations;
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import redis.clients.jedis.exceptions.JedisDataException;
9+
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.junit.jupiter.api.Assertions.*;
16+
17+
class OpsForTopKTest extends AbstractBaseDocumentTest {
18+
@Autowired
19+
RedisModulesOperations<String> modulesOperations;
20+
21+
TopKOperations<String> topK;
22+
23+
@BeforeEach
24+
void beforeEach() {
25+
topK = modulesOperations.opsForTopK();
26+
}
27+
28+
@Test
29+
void testBasicOperations() {
30+
// Create a TopK filter with a capacity of 3
31+
String result = topK.createFilter("topkTest", 3);
32+
assertEquals("OK", result);
33+
34+
// Add three items
35+
List<String> dropped = topK.add("topkTest", "item1", "item2", "item3");
36+
assertEquals(3, dropped.size(), "Should return a list with the same size of the added items");
37+
dropped.forEach(it -> assertNull(it, "No item should be dropped initially"));
38+
39+
// Add one more item to exceed the capacity
40+
dropped = topK.add("topkTest", "item4");
41+
assertEquals(1, dropped.size(), "Should return a list with the same size of the added items");
42+
assertEquals("item1", dropped.get(0), "The first item should be dropped");
43+
44+
// Query items
45+
List<Boolean> exists = topK.query("topkTest", "item1", "item2", "item3", "item4");
46+
// item1 should've been dropped, so it should not exist. All the others should exist.
47+
assertFalse(exists.get(0), "item1 should not exist");
48+
exists.stream().skip(1).forEach(it -> assertTrue(it, "All other items should exist"));
49+
50+
// List items
51+
List<String> topItems = topK.list("topkTest");
52+
assertFalse(topItems.isEmpty(), "Should get the three items");
53+
assertThat(topItems.size()).isEqualTo(3);
54+
55+
// Get counts
56+
Map<String, Long> counts = topK.listWithCount("topkTest");
57+
assertFalse(counts.isEmpty(), "Should get counts for the three items");
58+
assertEquals(1L, counts.get("item2"), "item2 should have a count of 1");
59+
assertEquals(1L, counts.get("item3"), "item3 should have a count of 1");
60+
assertEquals(1L, counts.get("item4"), "item4 should have a count of 1");
61+
62+
// Clean up after the test
63+
template.delete("topkTest");
64+
}
65+
66+
@Test
67+
void testIncrementBy() {
68+
// Create a TopK filter
69+
topK.createFilter("topkIncrTest", 3);
70+
71+
// Increment a single item
72+
String dropped = topK.incrementBy("topkIncrTest", "item1", 5);
73+
assertNull(dropped, "No item should be dropped initially");
74+
75+
// Increment multiple items
76+
Map<String, Long> itemIncrMap = new HashMap<>();
77+
itemIncrMap.put("item2", 3L);
78+
itemIncrMap.put("item3", 7L);
79+
80+
List<String> droppedList = topK.incrementBy("topkIncrTest", itemIncrMap);
81+
assertEquals(2, droppedList.size(), "Should return a list with the same size of the added items");
82+
assertNull(droppedList.get(0), "No item should be dropped");
83+
84+
// Check that the items exist with correct counts
85+
Map<String, Long> counts = topK.listWithCount("topkIncrTest");
86+
assertFalse(counts.isEmpty());
87+
88+
// Verify counts are as expected (may vary due to probabilistic nature)
89+
assertEquals(5L, counts.get("item1"), "item1 should have a count of 5");
90+
assertEquals(3L, counts.get("item2"), "item2 should have a count of 3");
91+
assertEquals(7L, counts.get("item3"), "item3 should have a count of 7");
92+
93+
// Clean up after the test
94+
template.delete("topkIncrTest");
95+
}
96+
97+
@Test
98+
void testInfo() {
99+
// Create a TopK filter with custom parameters
100+
String status = topK.createFilter("topkInfoTest", 5, 10, 7, 0.9);
101+
assertEquals("OK", status, "Filter should be created successfully");
102+
103+
// Get filter info
104+
Map<String, Object> info = topK.info("topkInfoTest");
105+
106+
// Verify expected keys in info map
107+
assertNotNull(info);
108+
assertFalse(info.isEmpty());
109+
110+
// Verify specific filter parameters
111+
assertEquals(5L, info.get("k"));
112+
assertEquals(10L, info.get("width"));
113+
assertEquals(7L, info.get("depth"));
114+
assertEquals(0.9, Double.parseDouble((String) info.get("decay")));
115+
116+
// Clean up after the test
117+
template.delete("topkInfoTest");
118+
}
119+
120+
@Test
121+
void testNonExistingKey() {
122+
// Attempt to get info for a non-existing key
123+
JedisDataException exception = assertThrows(JedisDataException.class,
124+
() -> topK.info("nonExistingKey"));
125+
126+
// Verify error message
127+
assertEquals("TopK: key does not exist", exception.getMessage());
128+
}
129+
}

0 commit comments

Comments
 (0)