ZREVRANK 'fair' ranking in REDIS - redis

When I have a sorted set with scores, I'd like to have the right rank even when multiple items have the same score.
For instance, when there are 5 items with scores: 1, 2, 2, 2, 3, I'd like to have those three central items to have the same rank (1), while the highest score gets rank 0 (with ZREVRANGE), and the lowest gets rank 4.
I see that it's possible to query the amount of keys with the same score somewhat efficiently O(log(N)), but it looks like if I want to have the scores as I want them, I'd have to use zscan, which is O(N).
Edit: add complete example based on the accepted solution
Our dataset is a sorted set with scores. For example: a has score 1, b, c and d have score 2, and e has score 3:
127.0.0.1:6379> zadd aset 1 a
(integer) 1
127.0.0.1:6379> zadd aset 2 b
(integer) 1
127.0.0.1:6379> zadd aset 2 c
(integer) 1
127.0.0.1:6379> zadd aset 2 d
(integer) 1
127.0.0.1:6379> zadd aset 3 e
(integer) 1
ZREVRANK works for those items with a unique score:
127.0.0.1:6379> zrevrank aset a
(integer) 4
127.0.0.1:6379> zrevrank aset e
(integer) 0
But it fails for those items with the same score:
127.0.0.1:6379> zrevrank aset b
(integer) 3
127.0.0.1:6379> zrevrank aset c
(integer) 2
127.0.0.1:6379> zrevrank aset d
(integer) 1
To solve that, first get the score with ZSCORE:
127.0.0.1:6379> zscore aset c
"2"
The other items have the same score, of course:
127.0.0.1:6379> zscore aset b
"2"
127.0.0.1:6379> zscore aset d
"2"
To get their rank, just use ZCOUNT with the score:
127.0.0.1:6379> zcount aset (2 +inf
(integer) 1
This also works for those items that have a unique score:
127.0.0.1:6379> zcount aset (1 +inf
(integer) 4
127.0.0.1:6379> zcount aset (3 +inf
(integer) 0
Writing this as an atomic lua script is left as an exercise for the reader.

For a given item with score x, you can determine its rank in O(log(N)) time with ZCOUNT (X +inf.
Exactly how you make use of that will depend on the details of your implementation.

ZREVRANGEBYLEX could be used in this case. The time complexity in this case would be O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. Please look at ZRANGEBYLEX for syntax.
Lex family of sorted set commands allow you to specify lexicographical ordering for keys with same values.

Related

How to write the below given shorted set in Redis

How to write the below in Redis
"Create a sorted set myset1 with the following values ( 1, 'a', 2, 'b', 3 , 'c').Create a sorted set myset 2 with the following values (4, 'b', 1, 'c', 0, 'd').Find the intersection of myset1 and myset2 : - Write a command to find the intersection of myset1 and myset2 and store the intersection in out- Write a command to see the resulting intersection output Sorted set "out" : Find the union of myset1 and myset2 - Write a command to take the union of myset1 and myset2 and store the output in unout - Write a command to see the resulting intersection output Sorted set "unout" "
I wrote the code like below and it says 80% correct.
127.0.0.1:6379> zadd myset1 0 1 0 a 0 2 0 b 0 3 0 c (integer) 6 127.0.0.1:6379> zadd myset2 0 4 0 b 0 1 0 c 0 0 0 d (integer) 6 127.0.0.1:6379> zinterstore out 2 myset1 myset2 (integer) 3 127.0.0.1:6379> zrange out 0 -1 1) "1" 2) "b" 3) "c" 127.0.0.1:6379> zunionstore unout 2 myset1 myset2 (integer) 9 127.0.0.1:6379> zrange unout 0 -1 1) "0" 2) "1" 3) "2" 4) "3" 5) "4" 6) "a" 7) "b" 8) "c" 9) "d" 127.0.0.1:6379>
You can use ZINTERSTORE and ZUNIONSTORE commands to computer intersection and union of intersection of sorted sets.
ZINTERSTORE
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
Computes the intersection of numkeys sorted sets given by the
specified keys, and stores the result in destination. It is mandatory
to provide the number of input keys (numkeys) before passing the input
keys and the other (optional) arguments.
By default, the resulting score of an element is the sum of its scores
in the sorted sets where it exists. Because intersection requires an
element to be a member of every given sorted set, this results in the
score of every element in the resulting sorted set to be equal to the
number of input sorted sets.
ZUNIONSTORE
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
Computes the union of numkeys sorted sets given by the specified keys,
and stores the result in destination. It is mandatory to provide the
number of input keys (numkeys) before passing the input keys and the
other (optional) arguments.
By default, the resulting score of an element is the sum of its scores
in the sorted sets where it exists.
Edit:
It's not accepting since you're adding score as key as well
Yout ZADD commands should be
ZADD myset1 1, a 2 b 3 c
zadd myset2 4 b 1 c 0 d
Try this
ZADD myset1 1 a 2 b 3 c
ZADD myset2 4 b 1 c 0 d
ZINTERSTORE out 2 myset1 myset2
ZUNIONSTORE unout 2 myset1 myset2

redis how to get Sorted Set Member with Score?

I am new to Redis.
127.0.0.1:6379> zadd myset 1 'one'
(integer) 1
127.0.0.1:6379> zadd myset 2 'two'
(integer) 1
127.0.0.1:6379> zadd myset 3 'three' 4 'four'
(integer) 2
127.0.0.1:6379> zadd myset 10 'ten' 9 'nine'
(integer) 2
I tried ZRANGEBYSCORE, but it shows member only..
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf
1) "one"
2) "two"
3) "three"
4) "four"
5) "nine"
6) "ten"
But I want to get Score / Member pairs.
How can I get these pairs ?
As #Ersoy already commented:
you need to add WITHSCORES at the end, ZRANGEBYSCORE myset -inf +inf WITHSCORES
The reply is sent in a format as follows:
1) MEMBER_1
2) SCORE_1
3) MEMBER_2
4) SCORE_1
...
If you want the reply as a list/array of pair, like:
1) MEMBER_1, SCORE_1
2) MEMBER_2, SCORE_2
...
Any sane Redis client[1], even in your preferred language, is likely to convert it as such.
[1] not including redis-cli

From redis sorted set, retrieving the rank of the highest value that has score just less than given score

From a redis sorted set, how do I retrieve the rank of highest value that has a score just less than a given score?
For instance, imagine my sorted set is:
rank value score
1) 'a' -10
2) 'd' -4
3) 'c' 0
4) 'b' 2
5) 'e' 10
Specifically, If I'm given the score 12, I want to retrieve rank 5. If I'm given the score 1, I want to retrieve rank 3. If I'm given a score -11, I want to retrieve nothing.
Note #1: rank in a Sorted Set is 0-based
Note #2: you'll have to do two queries, one for finding the element and the other for obtaining its rank.
Example using redis-cli:
127.0.0.1:6379> ZADD z -10 a -4 d 0 c 2 b 10 e
(integer) 5
127.0.0.1:6379> ZREVRANGEBYSCORE z (12 -inf LIMIT 0 1
1) "e"
127.0.0.1:6379> ZRANK z e
(integer) 4
127.0.0.1:6379> ZREVRANGEBYSCORE z (1 -inf LIMIT 0 1
1) "c"
127.0.0.1:6379> ZRANK z c
(integer) 2
127.0.0.1:6379> ZREVRANGEBYSCORE z (-11 -inf LIMIT 0 1
(empty list or set)
Naturally, a Lua script would be ideal for this case, i.e:
$ cat script.lua
local r=redis.call('ZREVRANGEBYSCORE', KEYS[1], '('..ARGV[1], '-inf', 'LIMIT', 0, 1)
if #r > 0 then
r=redis.call('ZRANK', KEYS[1], r[1])
end
return r
$ redis-cli --eval script.lua z , 12
(integer) 4
$ redis-cli --eval script.lua z , 1
(integer) 2
$ redis-cli --eval script.lua z , -11
(empty list or set)

How do I delete the last member of a Redis sorted set?

I am using a command line client. Looks like the ZREM doesn't help much. Now I wonder whether that is possible at all.
To remove the element with the highest rank, use zremrangebyrank <key> -1 -1. Here's an example:
127.0.0.1:6379> zadd test 1 one
(integer) 1
127.0.0.1:6379> zadd test 2 two
(integer) 1
127.0.0.1:6379> zadd test 3 three
(integer) 1
127.0.0.1:6379> zrange test 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zremrangebyrank test -1 -1
(integer) 1
127.0.0.1:6379> zrange test 0 -1
1) "one"
2) "two"

Redis lexicographical ordering doesn't work

I'm trying to create a basic autocomplete feature (I created those below manually to test it out first), but somehow I don't get the result I want after adding some keys.
I add every possible version of a word and keep the exact words with * to mark them (For example, if 10 keys are returned and 3 of them have asterisk, they'll be shown as suggestions), so I can query my hash database after that and get hash results.
There are a few duplicate entry attempts, but since it returns integer 0 for them, I presumed that they weren't added for the second time.
I use Redis 3.0.6
127.0.0.1:6379> zadd zset 0 b
(integer) 1
127.0.0.1:6379> zadd zset 0 ba
(integer) 1
127.0.0.1:6379> zadd zset 0 bar
(integer) 1
127.0.0.1:6379> zadd zset 0 bar*
(integer) 1
127.0.0.1:6379> zadd zset 0 f
(integer) 1
127.0.0.1:6379> zadd zset 0 fo
(integer) 1
127.0.0.1:6379> zadd zset 0 foo
(integer) 1
127.0.0.1:6379> zadd zset 0 foo*
(integer) 1
127.0.0.1:6379> zadd zset 0 foob
(integer) 1
127.0.0.1:6379> zadd zset 0 fooba
(integer) 1
127.0.0.1:6379> zadd zset 0 foobar
(integer) 1
127.0.0.1:6379> zadd zset 0 foobar*
(integer) 1
No problem so far.
I want all words that start with fo
127.0.0.1:6379> zrank zset fo
(integer) 5
It gives five, so I increment by one (like shown here if I get it right) and query all keys.
127.0.0.1:6379> zrange zset 6 -1
1) "foo"
2) "foo*"
3) "foob"
4) "fooba"
5) "foobar"
6) "foobar*"
No problem, I get the desired result.
I keep adding keys.
127.0.0.1:6379> zadd zset 0 a
(integer) 1
127.0.0.1:6379> zadd zset 0 b
(integer) 0
127.0.0.1:6379> zadd zset 0 c
(integer) 1
127.0.0.1:6379> zadd zset 0 fi
(integer) 1
127.0.0.1:6379> zadd zset 0 fil
(integer) 1
127.0.0.1:6379> zadd zset 0 filli
(integer) 1
127.0.0.1:6379> zadd zset 0 fillib
(integer) 1
127.0.0.1:6379> zadd zset 0 fillibo
(integer) 1
127.0.0.1:6379> zadd zset 0 filliboy
(integer) 1
127.0.0.1:6379> zadd zset 0 filliboya
(integer) 1
127.0.0.1:6379> zrank zset fo
(integer) 14
I do another search.
127.0.0.1:6379> zrange zset 15 -1
1) "foo"
2) "foo*"
3) "foob"
4) "fooba"
5) "foobar"
6) "foobar*"
Ok again. I keep adding.
127.0.0.1:6379> zadd zset 0 d
(integer) 1
127.0.0.1:6379> zadd zset 0 e
(integer) 1
127.0.0.1:6379> zadd zset 0 x
(integer) 1
127.0.0.1:6379> zadd zset 0 y
(integer) 1
127.0.0.1:6379> zadd zset 0 z
(integer) 1
127.0.0.1:6379> zadd zset 0 filli*
(integer) 1
127.0.0.1:6379> zadd zset 0 filliboya*
(integer) 1
This is the part where things get interesting. I want to get all the words that start with filli, but I can't. Why?
127.0.0.1:6379> zrank zset filli
(integer) 11
127.0.0.1:6379> zrange zset 12 -1
1) "filli*"
2) "fillib"
3) "fillibo"
4) "filliboy"
5) "filliboya"
6) "filliboya*"
7) "fo"
8) "foo"
9) "foo*"
10) "foob"
11) "fooba"
12) "foobar"
13) "foobar*"
14) "x"
15) "y"
16) "z"
127.0.0.1:6379>
The lexicographical ordering is working, but you're asking for the entire range from the member you've retrieved to the end (-1). Since Redis v2.8 you should be using the ZRANGEBYLEX command for that purpose. In your case, it should be as follows:
ZRANGEBYLEX zset [filli [filli\xff