weighted_random 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/README.rdoc +37 -22
- data/lib/weighted_random/railtie.rb +7 -1
- data/lib/weighted_random/version.rb +1 -1
- data/lib/weighted_random.rb +5 -3
- data/spec/auto_management_of_cumulative_weight_spec.rb +30 -0
- data/spec/config/database.yml +3 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/weighted_randomization_spec.rb +88 -0
- data/weighted_random.gemspec +2 -0
- metadata +18 -3
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
= Weighted Random
|
2
|
+
|
2
3
|
Weighted randomization extension for ActiveRecord.
|
3
4
|
|
4
|
-
|
5
|
+
Gives ability to get weighted random record from particular database relation.
|
5
6
|
|
6
7
|
== Installation
|
7
8
|
|
@@ -30,29 +31,23 @@ Load weighted randomization stuff into desired model and set accessibillity of p
|
|
30
31
|
|
31
32
|
== Usage
|
32
33
|
|
33
|
-
===
|
34
|
+
=== Importing data
|
34
35
|
|
35
|
-
|
36
|
+
To import data for randomization you have to provide an array of hashes with <code>weight</code> attributes to
|
37
|
+
<code>create_with_cumulative_weight</code> method, for example:
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
FirstName.create_with_cumulative_weight([
|
40
|
+
{:name => "Smith", :weight => 10},
|
41
|
+
{:name => "Johnson", :weight => 8},
|
42
|
+
{:name => "Williams", :weight => 7},
|
43
|
+
{:name => "Jones", :weight => 6},
|
44
|
+
{:name => "Brown", :weight => 6},
|
45
|
+
{:name => "Davis", :weight => 5},
|
46
|
+
{:name => "Miller", :weight => 4},
|
47
|
+
{:name => "Wilson", :weight => 3}
|
48
|
+
])
|
46
49
|
|
47
|
-
|
48
|
-
csv = CSV.open(File.expand_path("seeds/first_names.csv", File.dirname(__FILE__)))
|
49
|
-
names = csv.collect { |name, weight| {:name => name, :weight => to_i} }
|
50
|
-
csv.close
|
51
|
-
LastName.create_with_cumulative_weight(names)
|
52
|
-
|
53
|
-
Simply you have to supply collection of hashes, each with <code>weight</code> integer property, to
|
54
|
-
<code>create_with_cumulative_weight</code> method of your desired model class, so it will load them all
|
55
|
-
into database with additional <code>cumulative_weight</code> attribute, which is reqired for faster weighted randomization.
|
50
|
+
This method will save all this hashes in database with additional <code>cumulative_weight</code> attribute.
|
56
51
|
|
57
52
|
=== Weighted randomization
|
58
53
|
|
@@ -60,7 +55,7 @@ To get weighed random record simply run:
|
|
60
55
|
|
61
56
|
LastName.weighted_rand
|
62
57
|
|
63
|
-
|
58
|
+
Demonstration:
|
64
59
|
|
65
60
|
10.times { puts LastName.weighted_rand.name }
|
66
61
|
Johnson
|
@@ -74,6 +69,26 @@ To get weighed random record simply run:
|
|
74
69
|
Miller
|
75
70
|
Jones
|
76
71
|
|
72
|
+
== Import data from CSV
|
73
|
+
|
74
|
+
Here is an example of importing last names and their frequencies from CSV file:
|
75
|
+
|
76
|
+
db/seeds/last_names.csv:
|
77
|
+
name,weight
|
78
|
+
Smith,10
|
79
|
+
Johnson,8
|
80
|
+
Williams,7
|
81
|
+
Jones,6
|
82
|
+
Brown,6
|
83
|
+
Davis,5
|
84
|
+
Miller,4
|
85
|
+
Wilson,3
|
86
|
+
|
87
|
+
db/seeds.rb:
|
88
|
+
LastName.create_with_cumulative_weight(
|
89
|
+
CSV.table(File.expand_path("seeds/last_names.csv", File.dirname(__FILE__))).collect(&:to_hash)
|
90
|
+
)
|
91
|
+
|
77
92
|
== Author
|
78
93
|
|
79
94
|
Szymon Przybył (http://github.com/apocalyptiq)
|
@@ -4,9 +4,15 @@ module WeightedRandom
|
|
4
4
|
class Railtie < Rails::Railtie
|
5
5
|
initializer 'weighted_random.insert_into_active_record' do
|
6
6
|
ActiveSupport.on_load :active_record do
|
7
|
-
|
7
|
+
WeightedRandom::Railtie.insert
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
class Railtie
|
14
|
+
def self.insert
|
15
|
+
ActiveRecord::Base.send(:extend, WeightedRandom::Loader)
|
16
|
+
end
|
17
|
+
end
|
12
18
|
end
|
data/lib/weighted_random.rb
CHANGED
@@ -10,14 +10,16 @@ module WeightedRandom
|
|
10
10
|
|
11
11
|
module ClassMethods
|
12
12
|
def weighted_rand
|
13
|
-
self.where("cumulative_weight
|
13
|
+
self.where("cumulative_weight > #{Kernel.rand(self.maximum('cumulative_weight'))}").order('cumulative_weight').limit(1).first
|
14
14
|
end
|
15
15
|
|
16
16
|
def create_with_cumulative_weight(collection)
|
17
|
-
self.create
|
17
|
+
self.create WeightedRandom.set_cumulative_weight(collection)
|
18
18
|
end
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
+
class << self
|
22
|
+
def set_cumulative_weight(collection)
|
21
23
|
weight_sum = 0
|
22
24
|
collection.collect do |item|
|
23
25
|
weight_sum += item[:weight]
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WeightedRandom, "auto management of cumulative weight" do
|
4
|
+
|
5
|
+
def cumulative_weight_of(name)
|
6
|
+
TestModel.find_by_name(name).cumulative_weight
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#create_with_cumulative_weight sets cumulative_weight to" do
|
10
|
+
before(:all) do
|
11
|
+
TestModel.create_with_cumulative_weight [
|
12
|
+
{:name => 'first-50', :weight => 50},
|
13
|
+
{:name => 'second-1', :weight => 1},
|
14
|
+
{:name => 'last-10', :weight => 10}
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "50 for first record with weight 50" do
|
19
|
+
cumulative_weight_of('first-50').should be(50)
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "51 for second record with weight 1" do
|
23
|
+
cumulative_weight_of('second-1').should be(51)
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "61 for last record with weight 10" do
|
27
|
+
cumulative_weight_of('last-10').should be(61)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_support'
|
5
|
+
|
6
|
+
DIR = File.dirname(__FILE__)
|
7
|
+
ROOT = File.join(DIR, '..')
|
8
|
+
|
9
|
+
# Load WeightedRandom module!
|
10
|
+
require File.join(ROOT, 'lib/weighted_random')
|
11
|
+
|
12
|
+
# Load extension inserter
|
13
|
+
WeightedRandom::Railtie.insert
|
14
|
+
|
15
|
+
# Database configuration and connection
|
16
|
+
config = YAML::load(IO.read(DIR + '/config/database.yml'))
|
17
|
+
ActiveRecord::Base.establish_connection(config['test'])
|
18
|
+
|
19
|
+
# Test model
|
20
|
+
class TestModel < ActiveRecord::Base
|
21
|
+
weighted_randomizable
|
22
|
+
attr_accessible :name, :weight, :cumulative_weight
|
23
|
+
|
24
|
+
connection.create_table self.table_name, :force => true do |t|
|
25
|
+
t.string :name
|
26
|
+
t.integer :weight
|
27
|
+
t.integer :cumulative_weight
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WeightedRandom, "weighted randomization" do
|
4
|
+
|
5
|
+
def times_drawed(name)
|
6
|
+
counter = 0
|
7
|
+
rand_times.times { counter += 1 if TestModel.weighted_rand.name == name }
|
8
|
+
counter
|
9
|
+
end
|
10
|
+
|
11
|
+
def reload_with_data(data)
|
12
|
+
TestModel.destroy_all
|
13
|
+
TestModel.create_with_cumulative_weight(data)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "in 1000 rands" do
|
17
|
+
let(:rand_times) { 1000 }
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
# Set random number generator seed, so every test will receive the same sequence of numbers
|
21
|
+
Kernel.srand(77)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "deviation tolerance" do
|
25
|
+
before(:all) do
|
26
|
+
reload_with_data [
|
27
|
+
{:name => 'first-50', :weight => 50},
|
28
|
+
{:name => 'second-25', :weight => 25},
|
29
|
+
{:name => 'third-10', :weight => 10},
|
30
|
+
{:name => 'fourth-5', :weight => 5},
|
31
|
+
{:name => 'fifth-3', :weight => 3},
|
32
|
+
{:name => 'sixth-2', :weight => 2},
|
33
|
+
{:name => 'seventh-1', :weight => 1},
|
34
|
+
{:name => 'last-4', :weight => 4}
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "draws record with weight 50% of overall within 50 of 500 times (5% of 50%)" do
|
39
|
+
times_drawed('first-50').should be_within(50).of(500)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "draws record with weight 25% of overall within 40 of 250 times (4% of 25%)" do
|
43
|
+
times_drawed('second-25').should be_within(40).of(250)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "draws record with weight 10% of overall within 25 of 100 times (2.5% of 10%)" do
|
47
|
+
times_drawed('third-10').should be_within(25).of(100)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "draws record with weight 5% of overall within 20 of 50 times (2% of 5%)" do
|
51
|
+
times_drawed('fourth-5').should be_within(20).of(50)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "draws record with weight 3% of overall within 15 of 30 times (1.5% of 3%)" do
|
55
|
+
times_drawed('fifth-3').should be_within(15).of(30)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "draws record with weight 2% of overall within 10 of 20 times (1% of 2%)" do
|
59
|
+
times_drawed('sixth-2').should be_within(10).of(20)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "draws record with weight 1% of overall within 7 of 10 times (0.7% of 1%)" do
|
63
|
+
times_drawed('seventh-1').should be_within(7).of(10)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "boundary records with weight 1 (10% of overall)" do
|
68
|
+
before(:all) do
|
69
|
+
reload_with_data [
|
70
|
+
{:name => 'first-1', :weight => 1},
|
71
|
+
{:name => 'second-8', :weight => 8},
|
72
|
+
{:name => 'last-1', :weight => 1}
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
context "draws within 25 of 100 times (2.5% of 10%)" do
|
77
|
+
it "first" do
|
78
|
+
times_drawed('first-1').should be_within(25).of(100)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "last" do
|
82
|
+
times_drawed('last-1').should be_within(25).of(100)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
data/weighted_random.gemspec
CHANGED
@@ -14,6 +14,8 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.rubyforge_project = "weighted_random"
|
16
16
|
|
17
|
+
s.add_development_dependency('rspec')
|
18
|
+
|
17
19
|
s.files = `git ls-files`.split("\n")
|
18
20
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
21
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: weighted_random
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,20 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-04-
|
12
|
+
date: 2011-04-13 00:00:00.000000000 +02:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: &72406200 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *72406200
|
15
26
|
description: ActiveRecord extension for weighted randomization which supplies loading
|
16
27
|
records with weight for randomize into database and weighted randomization of them
|
17
28
|
email:
|
@@ -27,6 +38,10 @@ files:
|
|
27
38
|
- lib/weighted_random.rb
|
28
39
|
- lib/weighted_random/railtie.rb
|
29
40
|
- lib/weighted_random/version.rb
|
41
|
+
- spec/auto_management_of_cumulative_weight_spec.rb
|
42
|
+
- spec/config/database.yml
|
43
|
+
- spec/spec_helper.rb
|
44
|
+
- spec/weighted_randomization_spec.rb
|
30
45
|
- weighted_random.gemspec
|
31
46
|
has_rdoc: true
|
32
47
|
homepage: https://github.com/apocalyptiq/weighted_random
|