2015-08-16 2 views
3

У меня есть Django модели:Как эффективно хранить ранжированный список объектов в Django?

from django.db import models 

class Player(models.Model): 
    name = models.CharField(max_length=254, null=True, blank=True,) 
    score = models.IntegerField() 

A = Player.create(name="A", score=99) 
B = Player.create(name="B", score=66) 
C = Player.create(name="C", score=66) 
D = Player.create(name="D", score=55) 
E = Player.create(name="E", score=44) 

Я хочу, чтобы сохранить рейтинг всех моих игроков по счету. Если у игроков одинаковый балл, у них будет тот же ранг. Так что в этом случае рейтинг будет выглядеть следующим образом:

Name  Score  Rank 
A   99  1 
B   66  2 
C   66  2 
D   55  4 
E   44  5 

Я хочу сохранить этот рейтинг в базе данных, так что я не постоянно приходится выполнять ресурсоемкие запросы сортировки. Мне нужно, чтобы эффективно выполнять все следующие операции:

  1. Данный игрок, LookUp их ранг
  2. Учитывая звание, выяснить, какой игрок (ы) есть, что ранг
  3. Данный плеер, вставить их в этот рейтинг
  4. Учитывая игрок, удалить их из этого рейтинга
  5. Учитывая игрок и счет, обновить свою позицию в этом рейтинге

Мне нужно будет вставить и удалить игроков из этого списка. Всякий раз, когда я делаю # 3, # 4 или # 5, мне нужно будет обновить ряды других игроков, чтобы сохранить целостность рейтинга.

Каков наиболее эффективный способ сделать это в Django? Как мне создать мои модели, чтобы это работало эффективно, а мои операции с базой данных были минимальными? Пожалуйста, покажите мне, как выглядят новые модели.

+0

* «Когда я делаю # 3, № 4 или № 5, мне нужно будет обновить ряды других игроков ...» * - это еще дороже, чем вычисление рангов на лету. Хранение рангов в базе данных кажется плохой идеей, если вам придется обновлять их каждый раз, когда вы вставляете/удаляете игроков. – xyres

+0

Кроме того, запрос может быть намного быстрее, если все в памяти. См. [Memcached] (http://memcached.org) или [Redis] (http://redis.io) для кэширования базы данных в памяти. – xyres

+0

Xyres, я не могу легко вычислить эти ряды «на лету», используя только предложение базы данных «order by», потому что, как вы можете видеть, моя функция ранжирования немного сложнее (т. Е. Могут быть два игрока, занявших первое место). Также вставка/удаление игроков будет намного реже, чем поиск. –

ответ

1

Вы можете материализовать ranks и реорганизовать на экономии игрока, уведомление, чем не все время нужно прибегать ряды, когда новый счет будет сохранен:

Новая модель:

class ScoreRank(models.Model): 
    score = models.IntegerField(primary_key=True) 
    rank = models.IntegerField() 
    player_count = models.IntegerField() 

Сохраняя ранг отсортирован:

from django.db.models.signals import pre_save 
@receiver(pre_save, sender=Player) 
def update_score_rank(sender, instance, **kwargs): 
    #delete from previous rank 
    if instance.pk: 
     previous_score = (Player.objects 
         .filter(id=instance.id) 
         .values_list('score', flat=True).first()) 
     sc = ScoreRank.objects.get(score = previous_score) 
     if sc.player_count == 1: 
      #new hole in ranks, add -1 to other ranks to remove it: 
      _ = (ScoreRank.objects 
       .filter(score__gt = previous_score) 
       .update(rank=F('rank') - 1)) 
     sc.delete() 
    #insert in new rank 
    sc, is_new = (ScoreRank.objects 
        .get_or_create(score=instance.score, 
           defaults={'rank': -1,'player_count': 1,})) 
    if not is_new: 
     #this score is not new: add one to player_count 
     _ = (ScoreRank.objects 
      .filter(score = instance.score) 
      .update(rank=F('player_count') + 1)) 
    else: 
     #this score is not new: make hole for it 
     rank = (ScoreRank.objects 
       .filter(score__gt = instance.score) 
       .annotate(m=Min("score"))) 
     new_rank = rank["m"] if rank["m"] else 1 
     _ = (ScoreRank.objects 
      .filter(score__lte = new_rank) 
      .update(rank=F('rank') + 1)) 
     _ = (ScoreRank.objects 
      .filter(sc = sc.score) 
      .update(rank=new_rank)) 

не забудьте приложить операции с базами данных в одной транзакции (сериализации транзакций)

Disclainer: не проверено.

0

Технически каждый объект в наборе запросов является объектом Python. Это означает, что вы можете назначать им атрибуты «на лету». В нашем случае мы назначим каждому игроку атрибут rank.

Так что я написал небольшой алгоритм для расчета рангов на лету. Этот алгоритм работает в предположении, что набор запросов сортируется в порядке убывания.

players = Player.objects.order_by('-score') 

current_rank = 1 
counter = 0 

for player in players: 
    if counter < 1: # for first player 
     player.rank = current_rank 
    else: # for other players 
     if player.score == players[counter - 1].score: 
      # if player and previous player have same score, 
      # give them the same rank 
      player.rank = current_rank 
     else: 
      # first update the rank 
      current_rank += 1 
      # then assign new rank to player 
      player.rank = current_rank 
    counter += 1 

Сейчас в ваших шаблонах, вы можете получить доступ к рангу с помощью {{ player.rank }}.

Ограничения:

  1. Вы не можете поиска игрока, если ранг дается. Хотя вы все еще можете найти, например, топ 10 игроков и т. Д.
  2. Расчет отдельных рангов может быть медленным. Например, чтобы узнать ранг 1000-го игрока, вам понадобятся ряды предыдущих 999 игроков.
Смежные вопросы