Howto Rank Items by Balance in Ruby on Rails

HOWTO rank items by balance in Ruby on rails

To get the rank of a user,

Users.all(:order => "balance").index(a_particular_user)

This should give you the index (rank) of a particular user within the array of all users (sorted by balance).

(will_paginate) Find the page a record is in

HOWTO rank items by balance in Ruby on rails

Suggest Ranking algorithm for Multi User Sortable Lists

So I think I have a working solution. By combining the approach I mentioned in the question comment, with the lower bound of Wilson's score confidence interval for a Bernoulli parameter the score seems to align to my expectations.

So to rehash the approach from my comment: user item score = count of items + 1 - rank in the list / count of items. (1 of 3 = 1, 2 of 3 = .667, 2 of 5 = .8).

to give an overall item score I plug into the Wilson formula:
(phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)

Where phat = average of scores, n is number of rankings, z=1.96 (for a 95% confidence ranking).

I mocked up some data in Excel and played around with different scenarios and liked the results. Will move to implementation. Thanks for the help

How to Return index position of hash


how to return the index of a the hash based on the key

You can use each_with_index. A quick example:

hash = {:h=>1, :k=>2, :v=>3}
hash.each_with_index.detect { |(key,value),index| key == :k }
# => [[:k, 2], 1]
hash.each_with_index.detect { |(key,value),index| key == :k }.last
# => 1

find page for given record using kaminari

There is a similar question and answer for Java JPA/Hibernate Finding out the page containing a given record using JPA (Hibernate) that shows the sql needed to get the offset of the record in the index view. Something like

SELECT COUNT(*)
FROM Model r
WHERE r.column < ?
ORDER BY r.id

The page number will be offset/records_per_page + 1.

The question then is how to get the records_per_page from Kaminari? Kaminari lets you set the number of records per page in configuration, and for a specific model through Model.paginates_per. It provides the Model.default_per_page method to yield number of records per page for the model.

The best place to decide the page will be in the controller method that displays the index. That method already picks up the page param, if present, to display the correct page. Another way would be to compute the page before redirect and send the page param. Let's try the former.

After saving the edit we redirect to the index with a param identifying the record we want included in the index page redirect with params. The index sorts by Model.column.

redirect_to model_index_url(:for_column => @model_record.column), 
:notice => 'Record saved'

Then in the index method of the controller we let the page param govern, or compute the page from the for_column param if present.

page = 1 
if (params[:page])
page = params[:page]
elsif (params[:for_column])
offset = Model.select('count(*) as count').where('? < column',
params[:for_column]).order(id).first
page = offset.count/Model.default_per_page + 1
end
@records = Model.all.order('column desc').page(page)

And of course, Model and Model.column stand for the model and column name for your application.

Ranking optimization

TL;DR - download a program that does exactly what you need from here.

This can be modeled as a cost flow problem. In a cost flow problem, items conceptually flow between nodes along directional arcs. Each arc has a cost that is incurred each time an item flows between its nodes. Arcs also have a capacity that limits how many items can flow across the arc. And finally, each node has a starting inventory of items. A node can only send an item along one of its attached arcs if it has an item in inventory. The item is transferred from the sending end of the arc to the receiving end of the arc. The receiving node can then send the item along one of its attached arcs to yet another node. The cost flow solver searches for the set of transfers that sends as many items as it can at the lowest cost.

So what does this have to do with assigning people to workshops? Well, you can think of each person as having one token that represents their ability to attend one workshop. They send their token to the workshop they will attend, and the cost (or penalty) of sending that token is a reflection of where that workshop is in the person's prioritized list of workshops. The workshop a person most wants to attend is very cheap, while the least preferred workshop is very expensive. Once a workshop gets tokens from attendees, it sends its tokens to a sink node. The arc between the workshop and the sink node has no cost, but it does have a capacity that indicates the maximum number of people that can attend that workshop. The optimization will try to get all of the tokens to the sink node, and at the lowest cost. The only way for every token to get to the sink is if every person goes to one workshop, and if the number of people going to each workshop doesn't exceed the workshop's capacity. The solver finds a way to do that at the lowest cost.

Here's a diagram showing the layout:

Assignment flow network

There's a node for each person, a node for each workshop, and a sink node. Numbers in square braces show a node's starting inventory. Numbers in parentheses show an arc's capacity. In this example, the maximum number of people that can attend each workshop is 3, 2, and 4, respectively. Nodes without a starting inventory displayed have a starting inventory of 0, and arcs without a capacity displayed have a capacity of 1.

This can be implemented using Google's open source cost flow solver. I've done just that, and wrapped it into a program that you can download from gitlab. It's written it in such a way that you only have to replace a little bit of C# code to solve your specific problem. Just replace the workshop and person information shown below with your own data.

    // Identifies all workshops, with the maximum number of people that can enroll in each.
Dictionary<string, int> workshopCapacities = new Dictionary<string, int>()
{
{"A", 2 },
{"B", 3 },
{"C", 1 }
};

// Identifies each person by name, which maps to a list of workshops they want to attend in the order of preference
Dictionary<string, string[]> peoplePreferences = new Dictionary<string, string[]>()
{
{ "Betsy", new[]{ "A", "B", "C" } },
{ "Joe", new[]{ "C", "B", "A" } },
{ "Maria", new[]{ "C", "A", "B" } },
{ "Kuang", new[]{ "C", "A", "B" } },
{ "Paula", new[]{ "B", "C", "A" } },
};

Position of object in database

Assuming the order is made by ID desc:

class Team < ActiveRecord::Base
def position
self.class.count(:conditions => ['id <= ?', self.id])
end
end

Calculation Logic In Rails App

You could try the following:

class SomeController < ApplicationController

def some_action
@some_variable = Shot.calculate(params)

render :show_action
end
end

@some_variable, which is the return value of the Shot.calculate(params), is available in the view.



Related Topics



Leave a reply



Submit