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 CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ *.swp
data/README.rdoc CHANGED
@@ -1,7 +1,8 @@
1
1
  = Weighted Random
2
+
2
3
  Weighted randomization extension for ActiveRecord.
3
4
 
4
- Loads records with weight value for randomization and gives ability of weighted randomization of them.
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
- === Load data
34
+ === Importing data
34
35
 
35
- Load records with <code>weight</code> attribute for randomization, here is an example of loading last names and their frequencies from CSV file:
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
- last_names.csv:
38
- Smith,10
39
- Johnson,8
40
- Williams,7
41
- Jones,6
42
- Brown,6
43
- Davis,5
44
- Miller,4
45
- Wilson,3
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
- seeds.rb:
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
- === Demonstration
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
- ActiveRecord::Base.send(:extend, WeightedRandom::Loader)
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
@@ -1,3 +1,3 @@
1
1
  module WeightedRandom
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -10,14 +10,16 @@ module WeightedRandom
10
10
 
11
11
  module ClassMethods
12
12
  def weighted_rand
13
- self.where("cumulative_weight >= #{rand(self.maximum('cumulative_weight'))}").order('cumulative_weight').first
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! self.compute_and_insert_cumulative_weight(collection)
17
+ self.create WeightedRandom.set_cumulative_weight(collection)
18
18
  end
19
+ end
19
20
 
20
- def compute_and_insert_cumulative_weight(collection)
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
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
@@ -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
@@ -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.3
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 00:00:00.000000000 +02:00
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