Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Zsumscore patch #1222

Closed
wants to merge 2 commits into from

1 participant

@ripcurld00d

Introduction

My patch to this feature request #1044

Syntax: ZSUMSCORE key min max

What it does is computing the sum of scores of members, which their scores are between min and max values, in the sorted set key.

Therefore, to compute the sum of scores of all the members in a specific sorted set, run:

ZSUMSCORE sorted_set_name -inf +inf

Notes

  • I have had the notion of adding a new variable, which holds the sum, to the skip-list & zip-list structures. By doing so, I probably achieve better performance (complexity O(1)). However, I guess that it is harder to implement and to test.

  • When running the ZSUMSCORE double stress test at the unit test, I get inaccurate results. Nevertheless, after replicating it manually, it seems fine. Even though, please note that when the score is with a floating point, the sum is accurate up to the 15th number after the point (or at least that is what I get when running the unit test).

  • Would like to hear you comments/ideas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 27, 2013
  1. @ripcurld00d

    Add ZSUMSCORE command

    ripcurld00d authored
  2. @ripcurld00d

    Fix indentation

    ripcurld00d authored
This page is out of date. Refresh to see the latest.
View
5 src/help.h
@@ -749,6 +749,11 @@ struct commandHelp {
"Get the score associated with the given member in a sorted set",
4,
"1.2.0" },
+ { "ZSUMSCORE",
+ "key min max",
+ "Get the sum of the scores of the sorted set members, which their scores are between the given values",
+ 4,
+ "2.8.0" },
{ "ZUNIONSTORE",
"destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
"Add multiple sorted sets and store the resulting sorted set in a new key",
View
1  src/redis.c
@@ -178,6 +178,7 @@ struct redisCommand redisCommandTable[] = {
{"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
{"zcard",zcardCommand,2,"r",0,NULL,1,1,1,0,0},
{"zscore",zscoreCommand,3,"r",0,NULL,1,1,1,0,0},
+ {"zsumscore",zsumscoreCommand,4,"r",0,NULL,1,1,1,0,0},
{"zrank",zrankCommand,3,"r",0,NULL,1,1,1,0,0},
{"zrevrank",zrevrankCommand,3,"r",0,NULL,1,1,1,0,0},
{"hset",hsetCommand,4,"wm",0,NULL,1,1,1,0,0},
View
1  src/redis.h
@@ -1459,6 +1459,7 @@ void zrevrangeCommand(redisClient *c);
void zcardCommand(redisClient *c);
void zremCommand(redisClient *c);
void zscoreCommand(redisClient *c);
+void zsumscoreCommand(redisClient *c);
void zremrangebyscoreCommand(redisClient *c);
void multiCommand(redisClient *c);
void execCommand(redisClient *c);
View
51 src/t_zset.c
@@ -2137,6 +2137,57 @@ void zscoreCommand(redisClient *c) {
}
}
+void zsumscoreCommand(redisClient *c) {
+ robj *key = c->argv[1];
+ robj *zobj;
+ zrangespec range;
+ double sum = 0.0;
+
+ /* Parse the range arguments */
+ if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
+ addReplyError(c,"min or max is not a float");
+ return;
+ }
+
+ if ((zobj = lookupKeyReadOrReply(c, key, shared.nokeyerr)) == NULL ||
+ checkType(c,zobj,REDIS_ZSET)) return;
+
+ if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
+ unsigned char *zl = zobj->ptr;
+ unsigned char *eptr, *sptr;
+ double score;
+
+ eptr = zzlFirstInRange(zl, range);
+ sptr = (eptr == NULL) ? NULL : ziplistNext(zl, eptr);
+ for (; eptr != NULL; zzlNext(zl, &eptr, &sptr)) {
+ score = zzlGetScore(sptr);
+ if (!zslValueLteMax(score, &range))
+ break;
+ sum += score;
+ }
+
+ } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
+ zset *zs = zobj->ptr;
+ zskiplistNode *zn;
+ double score;
+
+ for (zn = zslFirstInRange(zs->zsl, range); zn != NULL;
+ zn = zn->level[0].forward) {
+ score = zn->score;
+ if (!zslValueLteMax(score, &range))
+ break;
+ sum += score;
+ }
+ } else {
+ redisPanic("Unknown sorted set encoding");
+ }
+
+ if (isnan(sum))
+ addReplyError(c, "sum is not a float");
+ else
+ addReplyDouble(c, sum);
+}
+
void zrankGenericCommand(redisClient *c, int reverse) {
robj *key = c->argv[1];
robj *ele = c->argv[2];
View
57 tests/unit/type/zset.tcl
@@ -506,6 +506,35 @@ start_server {tags {"zset"}} {
}
}
}
+
+ test "ZSUMSCORE key doesn't exist" {
+ assert_error "*no*key*" {r zsumscore nosrtset -inf +inf}
+ }
+
+ test "ZSUMSCORE wrong type" {
+ r sadd my_set_1 "hello world"
+ assert_error "*WRONGTYPE*" {r zsumscore my_set_1 -inf +inf}
+ r del my_set_1
+ }
+
+ test "ZSUMSCORE wrong number of arguments" {
+ r zadd my_set_2 0 m1
+ assert_error "*wrong number of arguments*" {r zsumscore my_set_2 0}
+ assert_error "*wrong number of arguments*" {r zsumscore my_set_2 0 1 3}
+ r del my_set_2
+ }
+
+ test "ZSUMSCORE sum of the range check - $encoding" {
+ r zadd my_set_3 50 m1
+ assert_equal 50 [r zsumscore my_set_3 -inf +inf]
+ assert_equal 0 [r zsumscore my_set_3 -inf (50]
+ assert_equal 0 [r zsumscore my_set_3 (50 +inf]
+ }
+
+ test "ZSUMSCORE basic" {
+ r zadd my_set_4 1 m0 0.2 m1 0.03 m2 0.004 m3
+ assert_equal 1.234 [r zsumscore my_set_4 -inf +inf]
+ }
}
basics ziplist
@@ -752,8 +781,34 @@ start_server {tags {"zset"}} {
}
assert_equal {} $err
}
- }
+ test "ZSUMSCORE of $elements integers - $encoding" {
+ r del my_zset_$encoding
+ set tcl_sum 0
+ for {set i 0} {$i < $elements} {incr i} {
+ set score [expr int(rand()*10)]
+ r zadd my_zset_$encoding $score $i
+ set tcl_sum [expr $tcl_sum + $score]
+ }
+ assert_encoding $encoding my_zset_$encoding
+ assert_equal $tcl_sum [r zsumscore my_zset_$encoding -inf +inf]
+ }
+
+ test "ZSUMSCORE of $elements doubles - $encoding" {
+ r del my_zset_$encoding
+ r del my_zset_ref_$encoding
+ set tcl_sum 0
+ for {set i 0} {$i < $elements} {incr i} {
+ set score [expr rand()]
+ r zadd my_zset_$encoding $score m_$i
+ set tcl_sum [expr $score + $tcl_sum]
+ }
+ assert_encoding $encoding my_zset_$encoding
+ # this is the best approximation i get when running the unit test
+ assert_equal [format "%.14g" [r zsumscore my_zset_$encoding -inf +inf]] [format "%.14g" $tcl_sum]
+ }
+ }
+
tags {"slow"} {
stressers ziplist
stressers skiplist
Something went wrong with that request. Please try again.