Ruby+Redisでランキングを実装してみる

Redisでランキング機能を実装してみる - (゚∀゚)o彡 sasata299's blog を自分がやったらこんな感じかなと思って書いてみました。実行環境はRuby 2.0.0-p0です。

#!/usr/bin/env ruby

require 'redis'
require 'pp'

class Leaderboard

    class Entry
        attr_reader :user_id, :score, :rank
        def initialize(user_id, score, rank)
            @user_id = user_id
            @score   = score
            @rank    = rank
        end

        def inspect
            "#<#{self.class} user_id:#{user_id}, score:#{score}, rank:#{rank}>"
        end
    end

    def initialize(id, user_id)
        @r = Redis.new
        @id = id
        @user_id = user_id
    end

    def _add(user_id, score)
        @r.zadd @id, score, user_id
    end

    def add(score)
        @r.zadd @id, score, @user_id
    end

    def get_score
        @r.zscore @id, @user_id
    end

    def get_rank
        @r.zrank @id, @user_id
    end

    def get_range(start, count)
        p [start, count]
        @r.zrange(@id, start, start + count, with_scores: true).map.with_index do |pair, i|
            Entry.new *pair, start + i + 1
        end
    end

    def get_current_page_num(num_per_page)
        (get_rank / num_per_page).floor
    end

    def get_page(page_num, num_per_page)
        get_range page_num * num_per_page, num_per_page
    end

    def get_current_page(num_per_page)
        get_page get_current_page_num(num_per_page), num_per_page
    end
end

# example code

l = Leaderboard.new "ranking", "bar"

8.times do |i|
    l._add "foo#{i}", 100 + i
end

8.times do |i|
    l._add "baz#{i}", 300 + i
end

l.add 200

p l.get_score
p l.get_rank
p l.get_current_page_num 5

puts "current_page:" # 自分がいるページ(1ページに付き5エントリ)
pp l.get_current_page 5

# ふつうにページングする場合
puts "page 0, 1, 2:"
pp l.get_page 0, 5
pp l.get_page 1, 5
pp l.get_page 2, 5

実行結果(+コメント):

200.0 # 自分のスコア
8 # 自分の順位
1 # 自分が今いるページ (0 origin)
current_page:
[#<Leaderboard::Entry user_id:foo5, score:105.0, rank:6>,
 #<Leaderboard::Entry user_id:foo6, score:106.0, rank:7>,
 #<Leaderboard::Entry user_id:foo7, score:107.0, rank:8>,
 #<Leaderboard::Entry user_id:bar, score:200.0, rank:9>, # 自分はここ
 #<Leaderboard::Entry user_id:baz0, score:300.0, rank:10>,
 #<Leaderboard::Entry user_id:baz1, score:301.0, rank:11>]
page 0, 1, 2: # ふつうにページング
page 0:
[#<Leaderboard::Entry user_id:foo0, score:100.0, rank:1>,
 #<Leaderboard::Entry user_id:foo1, score:101.0, rank:2>,
 #<Leaderboard::Entry user_id:foo2, score:102.0, rank:3>,
 #<Leaderboard::Entry user_id:foo3, score:103.0, rank:4>,
 #<Leaderboard::Entry user_id:foo4, score:104.0, rank:5>,
 #<Leaderboard::Entry user_id:foo5, score:105.0, rank:6>]
page 1 (current page):
[#<Leaderboard::Entry user_id:foo5, score:105.0, rank:6>,
 #<Leaderboard::Entry user_id:foo6, score:106.0, rank:7>,
 #<Leaderboard::Entry user_id:foo7, score:107.0, rank:8>,
 #<Leaderboard::Entry user_id:bar, score:200.0, rank:9>,
 #<Leaderboard::Entry user_id:baz0, score:300.0, rank:10>,
 #<Leaderboard::Entry user_id:baz1, score:301.0, rank:11>]
page 2:
[#<Leaderboard::Entry user_id:baz1, score:301.0, rank:11>,
 #<Leaderboard::Entry user_id:baz2, score:302.0, rank:12>,
 #<Leaderboard::Entry user_id:baz3, score:303.0, rank:13>,
 #<Leaderboard::Entry user_id:baz4, score:304.0, rank:14>,
 #<Leaderboard::Entry user_id:baz5, score:305.0, rank:15>,
 #<Leaderboard::Entry user_id:baz6, score:306.0, rank:16>]

参考文献

redis.rbのドキュメント: http://rubydoc.info/gems/redis/frames
Redis/Sorted Setのドキュメント:http://redis.io/commands#sorted_set
Redis大活用 - リアルタイムランキングの実装 in WEB+DB PRESS vol.73

WEB+DB PRESS Vol.73

WEB+DB PRESS Vol.73