Working with Sorted Sets

Exploring Sorted Sets in Redis

Welcome back! Building on our previous experience with Redis sets, today we are diving into sorted sets. Redis sorted sets combine the power of sets and lists, allowing us to handle collections in which every member is unique and has an associated score. These scores ensure that the elements are kept in a specific, sorted order.

What You'll Learn

In this lesson, you will learn how to use sorted sets in Redis with C++ and Boost.Redis. Specifically, we will focus on:

  1. Adding members and scores to a sorted set using asynchronous operations.
  2. Retrieving top members based on their scores.
  3. Removing members from a sorted set.

Sorted sets in Redis are remarkable due to their efficiency and flexibility. You might find them particularly useful for scenarios such as maintaining leaderboards, scheduling tasks, or storing time-series data.

Adding Members and Retrieving Top Scores

Let's start by connecting to your Redis server and adding some members to a sorted set:

C++
#include <boost/redis.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <iostream>
#include <vector>
#include <string>

#include <boost/redis/src.hpp>

namespace net = boost::asio;
using boost::redis::connection;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::request;
using boost::redis::response;

int main() {
    try {
        net::io_context ioc;

        auto conn = std::make_shared<connection>(ioc.get_executor());

        config cfg;
        conn->async_run(cfg, logger{logger::level::disabled},
                        net::consign(net::detached, conn));

        request req;
        req.push("ZADD", "leaderboard", "100", "Alice", "400", "Bob", "300", "Charlie", "350", "Alice");
        req.push("ZREVRANGE", "leaderboard", "0", "1", "WITHSCORES");

        response<std::int64_t, std::vector<std::string>> resp;

        conn->async_exec(req, resp,
            [conn, &resp](boost::system::error_code ec, std::size_t) {
                if (!ec) {
                    const auto& zadd_res = std::get<0>(resp);
                    if (zadd_res) {
                        std::cout << "ZADD Response (elements added): " << zadd_res.value() << "\n";
                    }

                    const auto& zrevrange_res = std::get<1>(resp);
                    if (zrevrange_res) {
                        const auto& players = zrevrange_res.value();
                        std::cout << "Top 2 players: ";
                        for (std::size_t i = 0; i + 1 < players.size(); i += 2) {
                            std::cout << players[i] << " (" << players[i + 1] << ") ";
                        }
                        std::cout << "\n";
                    }
                } else {
                    std::cerr << "Error executing Redis commands: " << ec.message() << "\n";
                }
                conn->cancel();
            });

        ioc.run();
    } catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
        return 1;
    }
    return 0;
}

This code works by using the ZADD command to add members with their scores and the ZREVRANGE command to get members in descending order of their scores.

Notice how we batch both commands in a single request object. The ZADD command takes alternating score and member arguments: "100", "Alice", "400", "Bob", etc. When we add Alice twice with different scores (100 and 350), Redis keeps only the last score (350).

The WITHSCORES parameter in the ZREVRANGE command tells Redis to include scores in the result. The response comes back as a flat std::vector<std::string> where members and scores alternate: [member1, score1, member2, score2, ...]. We parse this by iterating with a step of 2, accessing players[i] for the member name and players[i + 1] for the score.

We use std::get<0>(resp) to access the ZADD response (number of elements added) and std::get<1>(resp) to access the ZREVRANGE response (the vector of members and scores).

For this example, the output will be:

text
ZADD Response (elements added): 3
Top 2 players: Bob (400) Alice (350)

Notice that Alice's score is 350, not 100, because the last score is the one that is kept when adding the same member multiple times.

Modern Redis note (ZRANGE REV vs ZREVRANGE):

  • Since Redis 6.2, ZRANGE was extended with a REV option and is the modern alternative to ZREVRANGE. Both forms are supported.
  • Many newer docs and examples prefer ZRANGE ... REV. If you want to use that style, replace the ZREVRANGE line with: req.push("ZRANGE", "leaderboard", "0", "1", "REV", "WITHSCORES");
  • The response type and parsing remain exactly the same.
Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal