Ruby on Rails - 计算排名

时间:2014-05-12 19:36:44

标签: ruby-on-rails activeadmin

On Rails 4.我正在构建比赛应用程序。我有三个与此用例相关的表:

提交 - 属性包括:

  • :contest_year - 创建提交的年份
  • :category_id - 提交内容的比赛类别
  • :division_id - 提交的比赛部门(分为五个)

得分 - 属性包括:

  • :user_id - 给出分数的法官的身份证明
  • :submission_id - 分数与
  • 相关联的提交ID
  • :total_score - 法官提供的整数,作为条目的分数;通常十分之一

评委 - 属性包括:

  • :user_id - 法官的用户ID
  • :category_id - 法官分配的分类
  • :division_id - 法官被分配的分数

一个提交可以有很多分数(因为有多个评委分配到其类别/部门)。

应用程序必须做的第一件事就是在各个评委给出的所有孩子分数中找出提交的综合最终分数。我使用submission.rb中的计算执行此操作:

  def calculate_final_score
    self.scores.average(:total_score)
  end

所以基本上,它会找到提交的所有孩子分数,并取平均分数来找到最终分数。最终得分为提交表的非更新属性,它是方法计算结果。

现在,我的问题就在这里。我需要找到一种方法来计算提交的排名与其他提交的SAME :contest_year,{{1通过比较上面的最终得分计算,和:category_id。当提交具有相同的最终得分(平局)时,排名系统必须给出相同的排名。

tl; dr,我需要这样的排名行为(样本提交表):

:division_id

此排名信息将放在我的Active Admin提交内容中。索引表页面(也是CSV表输出的一部分)。

2 个答案:

答案 0 :(得分:2)

我对Rails(或Ruby)并不熟悉。但是在我们的Django应用程序中,我们遇到了类似的情况,我们需要计算某些计算数据的排名。由于我们使用PostgreSQL作为数据库后端,我们使用Postges的窗口函数(http://www.postgresql.org/docs/9.1/static/functions-window.htmlhttp://www.postgresql.org/docs/9.1/static/functions-window.htmlhttp://www.postgresql.org/docs/9.1/static/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS)进行排名计算。以下是PostgresSQL查询示例,它可以生成您的问题所需的结果。

sql_query = %Q{SELECT 
   *, dense_rank() OVER (
       PARTITION BY contest_year, category_id, division_id
       ORDER BY final_score DESC
   )
FROM (
    SELECT 
        Submissions.id, 
        Submission.contest_year AS contest_year, 
        Submission.category_id AS category_id, 
        Submission.division_id AS division_id, 
        AVG(Scores.total_score) AS final_score
    FROM Submissions 
        INNER JOIN Scores ON (Submissions.id = Scores.submission_id)
    GROUP BY 
        Submissions.id, 
        Submission.contest_year, 
        Submission.category_id, 
        Submission.division_id
) AS FinalScores}

submissions_by_rank = Submission.find_by_sql(sql_query)

注意:如果要以特定方式对结果进行排序,则需要在查询中添加ORDER BY子句。

答案 1 :(得分:1)

在Ruby中,假设您已经设置了#calculate_final_score方法:

class Submission < ActiveRecord::Base
  def self.rank_all_submissions_by_group
    keys = [ :contest_year, :category_id, :division_id ]
    submission_groups = Submission.all.group_by do |sub|
      values = keys.map { |k| sub.send(key) }
      Hash[key.zip(values)]
    end

    return ranked_submissions_by_group = submission_groups.map do |group, submissions|
      group[:ranked_submissions] = submissions.map do |s|
        [s, s.calculate_final_score]
      end.sort_by(&:last)
    end
  end
end

将返回的数据如下所示:

[
  {
    :contest_year => 2013,
    :division_id => 1,
    :category_id => 1,
    :ranked_submissions => [
      [ <Submission>, 10 ],
      [ <Submission>, 10 ],
      [ <Submission>, 8  ],
      [ <Submission>, 6  ],
      [ <Submission>, 5  ],
    ],
  },
  {
    :contest_year => 2013,
    :division_id => 2,
    :category_id => 1,
    :ranked_submissions => [
      [ <Submission>, 8 ],
    ],
  },
  {
    :contest_year => 2014,
    :division_id => 1,
    :category_id => 2,
    :ranked_submissions => [
      [ <Submission>, 9 ],
      [ <Submission>, 7 ],
    ],
  },
]

虽然它不会非常高效,因为它只是经历了所有时间的提交,制作了很多副本(我认为?),而且我没有使用正确的排序算法。

如果您有任何问题/疑虑,请告诉我,我可以更新我的答案。

其他帮助

如果您正在寻找一种在Ruby中运行原始查询的方法,请考虑这个简单的示例:

full_submissions_table_as_hash = ActiveRecord::Base.connection.select_all(<<-mysql)
  SELECT *
  FROM #{Submissions.table_name}
mysql

另一个显示如何从控制器呈现数据的简单示例:

# assuming routes are properly set up to actions in HomeController
class HomeController < ActionController::Base
  def ranked_submissions_text
    render :text => Submission.rank_all_submissions_by_group
  end
  def ranked_submissions_json
    render :json => Submission.rank_all_submissions_by_group
  end
end