如何在一定时期内计算出平均排名

时间:2014-02-07 06:51:28

标签: sql ruby-on-rails ruby

我试图展示虚拟游戏的一些统计数据。

有Team模型,Player模型和Run模型。

我可以在玩家模型中获得特定月份的游戏:

def count_runs(date)
 self.runs.count(:conditions => {:created_at => (date.beginning_of_month..date.end_of_month)})
end

我能够在Team控制器和模型中以正确的顺序获取它们:

@players = @team.players_by_count(Date.today)

def players_by_count(date)
 @date = date
 self.players.all.sort_by{|p| [-p.count_runs(@date)]}
end

我在表格中显示它以显示其位置:

<table>
  <% @players.each_with_index do |player, index| %>
  <tr>
    <td><%= (index+1).ordinalize %></td>
    <td><%= player.name %></td>
    <td><%= player.count_runs(Date.today) %></td>
  </tr>
  <% end %>
</table>

我的架构如下:

create_table "teams", :force => true do |t|
    t.string   "name"
    t.datetime "created_at",    :null => false
    t.datetime "updated_at",    :null => false
  end

  create_table "players", :force => true do |t|
    t.string   "name"
    t.integer  "team_id"
    t.datetime "created_at",   :null => false
    t.datetime "updated_at",   :null => false
  end

  create_table "runs", :force => true do |t|
    t.integer  "player_id"
    t.datetime "created_at",               :null => false
    t.datetime "updated_at",               :null => false
  end

我希望能够弄清楚他们的月末平均排名是什么。因此,在每个月末,他们都处于职位(第1,第3,第1,第5/4个月)=平均排名= 2.5

我也想弄清楚我每个月如何获得胜利者(顶级球员)。

有什么想法吗?

4 个答案:

答案 0 :(得分:2)

我建议创建一个只存储这些月度聚合的模型:

create_table "placements", :force => true do |t|
  t.integer  "player_id"
  t.date "month"
  t.integer "runs_count", :default => 0
end

因此,在创建每个运行之后,可能会添加一个回调,如下所示:(如果删除运行,也需要执行某些操作)

def aggregate!
  # Many different ways to accomplish this, this is just the first I thought of.
  Placements.where(player_id: self.player_id, month: self.created_at.beginning_of_month).first_or_create do |placement|
    placement.runs_count += 1
  end
end

这种方法可以让您执行以下操作:

class Player < ActiveRecord::Base
  has_many :placements
  belongs_to :team
end

class Team < ActiveRecord::Base
  has_many :players
  has_many :placements, through: game
end

class Placement < ActiveRecord::Base
  belongs_to :player
end

这将允许您执行以下操作:

@players = @team.players_by_count(Date.today)

变为

@placements = @team.placements.includes(:player).where(month: Date.today.beginning_of_month).order('runs_count DESC')

这样你就得到了玩家,他们的runs_count和他们的位置。

因此,在您的视图中,您可以像这样显示它们:

<table>
  <% @placements.each_with_index do |placement, index| %>
  <tr>
    <td><%= (index+1).ordinalize %></td>
    <td><%= placement.player.name %></td>
    <td><%= placement.runs_count %></td>
  </tr>
  <% end %>
</table>

如果您想获得他们的平均展示位置,我建议您在展示位置添加一个新列,该列将存储其在给定月份的位置,以便您可以执行以下操作:

positions = player.placements.pluck(:position)
average = positions.sum / positions.size.to_f

我建议这条路径的原因是因为我看到了当前实现的查询密集程度。如果团队中有10名玩家,则@team.players_by_count(Date.today)将创建11个查询(1 + players_count)。其中@team.placements.includes(:player).where(month: Date.today.beginning_of_month).order('runs_count DESC')是2个查询,无论玩家数量如何。

希望这有帮助!

答案 1 :(得分:1)

如果您想在表中添加另一列,我将创建一个实例方法来处理计算:

def avg
    positions = runs.map(&:position)
    avg = positions.inject(:+) / positions.size
    return avg
end

您还可以执行SQL query to calculate the average

   def avg
      runs.average(:positions)
   end

答案 2 :(得分:1)

到目前为止,我采取的方法与答案不同,以避免从实施的角度解决问题。

<强>评论

恕我直言,阻止你实现该功能的主要问题是你在数据库或实现层面上的想法太多了。你在谈论你的例子中的模型,所以我假设你在做OOP:)

考虑到这一点,为什么不回顾问题领域(你的建模和试图解决的现实世界的“切片”)来检查是否存在未被建模的概念。

我会解决这个问题,因为你的问题背景不足,但是我可以想象一下登上领奖台存在玩家在评估他们的表现后被放置的地方。

从这个角度来看,通过将责任分配给控制器(按运行次数对球员进行排序),这就是你想要达到的目的。

可能的解决方案

在问题领域,您可以通过查看获胜者和每位玩家最终获得的位置来判断。

同样,鉴于我没有太多关于你的问题的上下文,我将使用两个子类建模Podium层次结构。

class Podium

    def self.monthly players, date
        MonthlyPodium.new players, date
    end

    def self.average podia
        AveragePodium.new podia
    end

    def players
        fail 'subclass responsibility'
    end

    def place_of a_player
        fail 'subclass responsibility'
    end

    def winner
        fail 'subclass responsibility'
    end

end

第一个是MonthlyPodium,它将从玩家的集合中计算出胜利者和每个玩家的位置:

class MonthlyPodium < Podium

    attr_reader :players    

    def initialize players, a_date
        @players = players
        @time_period = a_date.beginning_of_month..a_date.end_of_month
    end

    def place_of a_player
        players_by_descending_runs.index(a_player) + 1
    end

    def winner
        players_by_descending_runs.first
    end

    def players_by_descending_runs
        @players.
            sort_by { |player| player.runs_in @time_period }.
            reverse
    end

end

为了能够重复使用每月获胜者/地点的计算来计算平均值,因缺少更好的名称而命名为AveragePodium的第二个子类,创建了一系列领奖台以计算平均值职位和获胜者:

class AveragePodium < Podium

    def initialize podia
        @podia = podia
    end

    def players
        @podia.
            collect(&:players).
            flatten.
            uniq
    end     

    def place_of a_player
        places = @podia.collect { |podium| podium.place_of a_player }
        places.inject(:+) / places.size
    end

    def winner
        players.max_by { |player| place_of player }
    end

end

备注

通过 abstract Podium创建两个子类的实例。
因为层次结构是多态的,所以您不需要知道对象是否是特定类的实例。

此外,您的代码中只应包含对抽象类的引用。

我根本没有使用数据库,即使我使用:runs_in方法将解决方案保留在您的问题的上下文中。 我尝试用纯OO设计表达解决方案,尽可能地匹配您尝试解决的域。

视图怎么样

嗯,我认为使用多态对象的视图会更乐意。你可以在不知道他们的实例的情况下向球员,地点和胜利者询问任何Podium实例。

DB / Performance

怎么样? 那是另一首歌。换句话说,如何从数据库中检索对象以构建模型不是Team / Player / Run的责任。它们应尽可能与持久性无关(我说因为AR总是会阻碍它)。

这就是为什么到目前为止我不同意数据库驱动的解决方案。其中大多数都围绕性能优化(即保持运行计数,因此您不必一直计算它)。

你知道你需要计算得分的玩家,你可以一次性获取它们并构建模型。从那一刻开始,你将只使用对象。忘了DB。

有趣的事情

请注意Ruby缺乏代表月份的良好模型。您必须通过传递约会并要求beginning_of_month / end_of_month来处理它。 如果在MonthlyPodium类中实现该对象,那么建模该对象会更好/更好。

创建的所有Podium个对象都是完整且有效的。也就是说,他们通过构造函数获得他们完成工作所需的所有对象。

此外,通过使用Podium对象,视图的工作量更少。它应该限于迭代球员(从领奖台本身获得)并以你想要的方式呈现胜利者/位置。

最终评论

我专注于从概念/设计的角度展示我的观点,添加方法的实现只是为了展示一种可行的方法。

确定可以根据需要对它们进行改进/优化/修复:)

答案 3 :(得分:1)

您需要在select子句中使用grouphavingorder avg()来获取查询中生成的正确SQL。

ActiveRecord文档的example非常类似于您的要求。