weightedpicker 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,7 +1,18 @@
1
- == Master (for 0.0.2)
1
+ = weightedpicker changelog
2
+
3
+ == Master (for 0.1.1)
4
+
5
+ == Version 0.1.0
2
6
  Use integer alternative to float.
3
7
  Add test for 'pick' method.
4
8
 
9
+ WeightedPicker::Tree is added.
10
+ WeightedPicker::Tree::Node is added.
11
+
12
+ Change treatment of values over MAX and under MIN.
13
+ Change user interface around initialize and merge.
14
+ Change main weight range to between 2^0 and 2^16.
15
+
5
16
  == Version 0.0.1
6
17
  bugfix.
7
18
 
data/Gemfile CHANGED
@@ -6,9 +6,10 @@ source "http://rubygems.org"
6
6
  # Add dependencies to develop your gem here.
7
7
  # Include everything needed to run rake, tests, features, etc.
8
8
  group :development do
9
- gem "rspec", "~> 2.9.0"
9
+ gem "rspec", "~> 2.13.0"
10
10
  gem "rdoc", "~> 3.12"
11
- gem "bundler", "~> 1.1.3"
11
+ gem "bundler", "~> 1.3.4"
12
12
  gem "jeweler", "~> 1.8.3"
13
13
  gem "simplecov", ">= 0"
14
+ #gem "psych", ">= 0"
14
15
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.0
@@ -1,5 +1,9 @@
1
1
  require 'yaml'
2
2
 
3
+ class WeightedPicker; end
4
+
5
+ require 'weightedpicker/tree.rb'
6
+
3
7
  # TODO
4
8
  # initialize.指定したファイル内のデータが WeightedPicker 的に
5
9
  # 解釈できなければ例外。
@@ -91,8 +95,9 @@ require 'yaml'
91
95
  #
92
96
  # ヒストリ関係はこのクラスの外に出した方が良いと判断。
93
97
  class WeightedPicker
94
- MAX_WEIGHT = 1_000_000
95
- MIN_WEIGHT = 1
98
+ MAX_WEIGHT = 2**16
99
+ INI_WEIGHT = 2** 8
100
+ MIN_WEIGHT = 2** 0
96
101
 
97
102
  class InvalidFilenameError < Exception; end
98
103
  class NoEntryError < Exception; end
@@ -100,6 +105,11 @@ class WeightedPicker
100
105
  class InvalidWeightError < Exception; end
101
106
 
102
107
  # Initialization.
108
+ def initialize(data)
109
+ data = sanity_data(data)
110
+ @tree = WeightedPicker::Tree.new(data)
111
+ end
112
+
103
113
  # Argument 'file' indicates a strage file name for data
104
114
  # which this class manages.
105
115
  # If the 'file' does not exist, this file is used to data strage.
@@ -109,109 +119,78 @@ class WeightedPicker
109
119
  # A and B in the file and B and C in items,
110
120
  # then B and C is the items which this class manage.
111
121
  # A is discarded from record.
112
- def initialize(file, items)
113
- @save_file = file
114
- unless File.exist? @save_file
115
- File.open(@save_file, "w") {|io| YAML.dump({}, io)}
116
- end
117
- @weights = YAML.load_file(file)
122
+ def self.load_file(filename)
123
+ weights = YAML.load_file(filename)
124
+ self.new(weights)
125
+ end
118
126
 
119
- @weights.values.each do |i|
120
- raise InvalidWeightError, "#{i.inspect}, not integer." unless i.is_a? Integer
121
- end
127
+ def dump(io)
128
+ YAML.dump(@tree.names_weights, io)
129
+ end
122
130
 
123
- merge(items)
124
- normalize_write
131
+ def names_weights
132
+ @tree.names_weights
125
133
  end
126
134
 
127
135
  # 乱数を利用して優先度で重み付けして要素を選び、要素を返す。
128
136
  # num is only for test. User should not use this argument.
129
- def pick(num = nil)
130
- raise NoEntryError if @weights.empty?
131
-
132
- sums = []
133
- keys = []
134
- sum = 0
135
- @weights.each do |key, weight|
136
- keys << key
137
- sum += weight
138
- sums << sum
139
- end
140
-
141
- num ||= rand(sum)
142
- # find index of first excess a number
143
- sums.each_with_index do |item, index|
144
- return keys[index] if num < item
145
- end
137
+ def pick
138
+ @tree.pick
146
139
  end
147
140
 
148
141
  #重みを重くする。(優先度が上がる)
149
142
  def weigh(item)
150
- #pp @weights
151
- raise NotExistKeyError unless @weights.has_key?(item)
152
- @weights[ item ] *= 2
153
- normalize_write
143
+ @tree.weigh(item)
154
144
  end
155
145
 
156
146
  #重みを軽くする。(優先度が下がる)
157
147
  def lighten(item)
158
- raise NotExistKeyError unless @weights.has_key?(item)
159
- #@weights[ item ] /= 2 unless @weights[ item ] == 0
160
- @weights.each do |key, val|
161
- weigh(key) unless key == item
162
- end
163
- normalize_write
148
+ @tree.lighten(item)
164
149
  end
165
150
 
166
- ##管理している要素の数を返す。
167
- #def size
168
- # @weights.size
169
- #end
170
-
171
- ##管理している要素と重みでイテレート。
172
- ##e.g. ws0.each { |item, weight| p item, weight }
173
- ##item が管理しているオブジェクト、weight が重み。
174
- #def each
175
- # @weights.each do |item, weight|
176
- # yield(item, weight)
177
- # end
178
- #end
179
-
180
- private
181
-
182
151
  # 引数 keys で示したものと、
183
152
  # 内部的に管理しているデータが整合しているかチェックし、
184
153
  # keys に合わせる。
185
- # 追加されたデータは MAX_WEIGHT の重みとなる。
186
- # データが削除された場合、それが最大値の可能性があるので
187
- # 必ず normalize_write される。
154
+ # 追加されたデータの重みは、データ内に存在する最大値と
155
+ # 同じになる。
156
+ # This affects destructively.
188
157
  def merge(keys)
158
+ new_weights = {}
159
+ new_keys = []
160
+ max = 0
161
+ data = @tree.names_weights
189
162
  keys.each do |key|
190
- @weights[key] ||= MAX_WEIGHT
163
+ new_weights[key] = data[key]
164
+
165
+ if data[key] == nil
166
+ #substitute max among exist values afterward
167
+ new_keys << key unless data[key]
168
+ next
169
+ end
170
+
171
+ max = data[key] if max < data[key]
191
172
  end
192
- @weights.each do |key, val|
193
- @weights.delete(key) unless keys.include?(key)
173
+
174
+ max = INI_WEIGHT if max < INI_WEIGHT
175
+ new_keys.each do |key|
176
+ new_weights[key] = max
194
177
  end
195
- #pp @weights
196
- #pp keys
197
- normalize_write
178
+
179
+ data = new_weights
180
+
181
+ @tree = WeightedPicker::Tree.new(data)
198
182
  end
199
183
 
200
- # 最大値を max とするように規格化する。
201
- # ただし、weight が MIN_WEIGHT 未満となった項目は
202
- # MIN_WEIGHT を新しい weight とする。
203
- def normalize_write
204
- raise NoEntryError if @weights.size == 0
184
+ private
205
185
 
206
- old_max = @weights.values.max
207
- @weights.each do |key, val|
208
- new_val = (val.to_f * (MAX_WEIGHT.to_f / old_max.to_f)).to_i
209
- @weights[key] = new_val if MIN_WEIGHT <= new_val
210
- end
186
+ def sanity_data(data)
187
+ data.each do |key, val|
188
+ data[key] = 0 if val < MIN_WEIGHT
189
+ data[key] = MAX_WEIGHT if MAX_WEIGHT < val
211
190
 
212
- File.open(@save_file, "w") do |io|
213
- YAML.dump(@weights, io)
191
+ raise InvalidWeightError, "#{val.inspect}, not integer." unless val.is_a? Integer
214
192
  end
193
+ data
215
194
  end
216
195
 
217
196
  end
@@ -0,0 +1,139 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ #
6
+ #
7
+ class WeightedPicker::Tree
8
+
9
+ attr_reader :size
10
+
11
+ class NoEntryError < Exception; end
12
+
13
+ #
14
+ def initialize(data)
15
+ @size = data.size #for return hash.
16
+
17
+ @names = data.keys
18
+ @weights = []
19
+ @weights[0] = data.values
20
+
21
+ #Fill 0 to 2**n
22
+ @size.upto ((2 ** depth) - 1) do |i|
23
+ @weights[0][i] = 0
24
+ end
25
+
26
+ depth.times do |i|
27
+ @weights[i+1] = []
28
+ num = @weights[i].size
29
+ (num - num / 2).times do |j|
30
+ @weights[i+1] << @weights[i][2*j] + @weights[i][2*j + 1]
31
+ end
32
+ end
33
+
34
+ @weights.reverse!
35
+ end
36
+
37
+ # Return internal data as a Hash.
38
+ def names_weights
39
+ results = {}
40
+ @size.times do |i|
41
+ results[@names[i]] = @weights[-1][i]
42
+ end
43
+ results
44
+ end
45
+
46
+ def pick
47
+ raise NoEntryError if @weights[0][0] == 0
48
+
49
+ current_index = 0
50
+ depth.times do |i|
51
+ next_id0 = 2 * current_index
52
+ next_id1 = 2 * current_index + 1
53
+ #puts
54
+ choise = choose( @weights[i+1][next_id0], @weights[i+1][next_id1])
55
+ current_index = 2 * current_index + choise
56
+ end
57
+ return @names[current_index]
58
+
59
+
60
+ #sums = []
61
+ #keys = []
62
+ #sum = 0
63
+ #@weights.each do |key, weight|
64
+ # keys << key
65
+ # sum += weight
66
+ # sums << sum
67
+ #end
68
+
69
+ #num ||= rand(sum)
70
+ ## find index of first excess a number
71
+ #sums.each_with_index do |item, index|
72
+ # return keys[index] if num < item
73
+ #end
74
+ end
75
+
76
+ def weigh(item)
77
+ raise NoEntryError unless @names.include?(item)
78
+ id = index(item)
79
+ old_weight = @weights[-1][id]
80
+ if (WeightedPicker::MAX_WEIGHT < old_weight * 2)
81
+ add_weight = WeightedPicker::MAX_WEIGHT - old_weight
82
+ else
83
+ add_weight = old_weight
84
+ end
85
+ return if add_weight == 0
86
+ add_ancestors(id, add_weight)
87
+ end
88
+
89
+ def lighten(item)
90
+ raise NoEntryError unless @names.include?(item)
91
+ id = index(item)
92
+ old_weight = @weights[-1][id]
93
+ if (old_weight / 2 < WeightedPicker::MIN_WEIGHT)
94
+ add_weight = 0
95
+ else
96
+ add_weight = - old_weight / 2
97
+ end
98
+ return if add_weight == 0
99
+ add_ancestors(id, add_weight)
100
+ end
101
+
102
+ private
103
+
104
+ def add_ancestors(id, val)
105
+ (depth+1).times do |d|
106
+ divisor = 2 ** (depth - d)
107
+ x = id / divisor
108
+ @weights[d][x] += val
109
+ end
110
+ end
111
+
112
+ def log2_ceil(num)
113
+ result = 0
114
+ while (num > 1)
115
+ result += 1
116
+ num -= num/2
117
+ end
118
+ result
119
+ end
120
+
121
+ def depth
122
+ log2_ceil(@size)
123
+ end
124
+
125
+ def choose(num0, num1)
126
+ sum = num0 + num1
127
+
128
+ # 0, 1, 2
129
+ return 0 if rand(sum) < num0
130
+ return 1
131
+ end
132
+
133
+ def index(item)
134
+ return @names.index(item)
135
+ #raise WeightedPicker::Tree::NoEntryError
136
+ end
137
+
138
+ end
139
+
data/spec/a-1b256.yaml ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ A: -1
3
+ B: 256
data/spec/a256b0.yaml ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ A: 256
3
+ B: 0
data/spec/a256b1.yaml ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ A: 256
3
+ B: 1
@@ -0,0 +1,3 @@
1
+ ---
2
+ A: 256
3
+ B: 128
data/spec/a512b64.yaml ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ A: 512
3
+ B: 64
@@ -0,0 +1,3 @@
1
+ ---
2
+ A: 99999
3
+ B: 64
data/spec/float.yaml ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ A: 1.0
3
+ B: 0.5
data/spec/tree_spec.rb ADDED
@@ -0,0 +1,123 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require "stringio"
3
+ require "fileutils"
4
+
5
+ class WeightedPicker::Tree
6
+ public :log2_ceil
7
+ public :depth
8
+ public :choose
9
+ public :index
10
+ public :add_ancestors
11
+ attr_reader :weights
12
+ end
13
+
14
+ describe "Weightedpicker::Tree" do
15
+ before do
16
+ @tree00 = WeightedPicker::Tree.new({"A" => 2, "B" => 1, "C" => 1})
17
+ @tree01 = WeightedPicker::Tree.new({"A" => 0})
18
+ @tree02 = WeightedPicker::Tree.new({})
19
+ end
20
+
21
+ it "should do in initialize" do
22
+ #@tree00
23
+ end
24
+
25
+ it "should return a hash of names_weights" do
26
+ #@tree00 = WeightedPicker::Tree.new({"A" => 2, "B" => 1, "C" => 1})
27
+ @tree00.names_weights.should == {"A" => 2, "B" => 1, "C" => 1}
28
+ end
29
+
30
+ it "should pick" do
31
+ results = {"A" => 0, "B" => 0, "C" => 0}
32
+ srand(0)
33
+ 300.times do |i|
34
+ results[@tree00.pick] += 1
35
+ end
36
+ #pp results #=> {"A"=> 52, "B"=>76, "C"=>72}
37
+ results["A"].should be_within(15).of(150)
38
+ results["B"].should be_within( 8).of( 75)
39
+ results["C"].should be_within( 8).of( 75)
40
+
41
+ lambda{ @tree01.pick}.should raise_error(WeightedPicker::Tree::NoEntryError)
42
+ lambda{ @tree02.pick}.should raise_error(WeightedPicker::Tree::NoEntryError)
43
+ end
44
+
45
+ it "should weigh item" do
46
+ @tree00.weigh "A"
47
+ @tree00.weights.should == [
48
+ [6],
49
+ [5,1],
50
+ [4,1,1,0],
51
+ ]
52
+ @tree00.names_weights.should == {"A" => 4, "B" => 1, "C" => 1}
53
+ lambda{ @tree00.weigh("D")}.should raise_error(WeightedPicker::Tree::NoEntryError)
54
+ end
55
+
56
+ it "should weigh item, but be limited by MAX" do
57
+ tree10 = WeightedPicker::Tree.new({"A" => 60000, "B" => 1, "C" => 1})
58
+
59
+ tree10.weigh "A"
60
+ tree10.weights.should == [
61
+ [65538],
62
+ [65537,1],
63
+ [65536,1,1,0],
64
+ ]
65
+ tree10.names_weights.should == {"A" => 65536, "B" => 1, "C" => 1}
66
+ end
67
+
68
+ it "should lighten item" do
69
+ @tree00.lighten "A"
70
+ @tree00.weights.should == [
71
+ [3],
72
+ [2,1],
73
+ [1,1,1,0],
74
+ ]
75
+ @tree00.names_weights.should == {"A" => 1, "B" => 1, "C" => 1}
76
+ lambda{ @tree00.lighten("D")}.should raise_error(WeightedPicker::Tree::NoEntryError)
77
+ end
78
+
79
+ it "should lighten item, but be limited by MIN" do
80
+ @tree00.lighten "B"
81
+ @tree00.weights.should == [
82
+ [4],
83
+ [3,1],
84
+ [2,1,1,0],
85
+ ]
86
+ @tree00.names_weights.should == {"A" => 2, "B" => 1, "C" => 1}
87
+ end
88
+
89
+
90
+ it "should add_ancestors" do
91
+ @tree00.add_ancestors(1,10)
92
+ @tree00.weights.should == [
93
+ [14],
94
+ [13,1],
95
+ [2,11,1,0],
96
+ ]
97
+ end
98
+
99
+ it "should log2_ceil" do
100
+ #lambda{ @tree00.log2_ceil(0)}.should raise_error(WeightedPicker::Tree::NoEntryError)
101
+ @tree00.log2_ceil( 1).should == 0
102
+ @tree00.log2_ceil( 2).should == 1
103
+ @tree00.log2_ceil( 3).should == 2
104
+ @tree00.log2_ceil( 4).should == 2
105
+ @tree00.log2_ceil( 8).should == 3
106
+ @tree00.log2_ceil(16).should == 4
107
+ @tree00.log2_ceil(20).should == 5
108
+ end
109
+
110
+ #it "should choose" do
111
+ # @tree00.choose(1,2).should == 0
112
+ # @tree00.choose(1,2).should == 1
113
+ # @tree00.choose(1,2).should == 1
114
+ #end
115
+
116
+ it "should get index" do
117
+ @tree00.index("A").should == 0
118
+ @tree00.index("B").should == 1
119
+ #lambda{ @tree00.find("C")}.should raise_error(WeightedPicker::Tree::NoEntryError)
120
+ end
121
+
122
+ end
123
+
@@ -4,357 +4,188 @@ require "fileutils"
4
4
 
5
5
  class WeightedPicker
6
6
  attr_accessor :weights
7
- public :normalize_write, :merge
7
+ public :merge
8
8
  end
9
9
 
10
- TMP_FILE = "tmp.yaml"
10
+ AB_YAML = "spec/a256b128.yaml"
11
11
  NOT_EXIST_FILE = "not_exist_file"
12
12
 
13
13
 
14
14
 
15
- describe "Weightedpicker::initialize" do
15
+ describe "Weightedpicker" do
16
16
  before do
17
- weights = { "A" => 1_000_000, "B" => 500_000, }
18
- items = ["A", "B"]
19
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
20
- @wp01 = WeightedPicker.new(TMP_FILE, items)
21
- @wp02 = Marshal.load(Marshal.dump(@wp01))
22
- @wp02.weights = {}
23
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
24
- end
25
-
26
- it "should create new file when the file not exist" do
27
- # 指定したファイルが存在しない場合、
28
- t = WeightedPicker.new(NOT_EXIST_FILE, ["A","B"])
29
- t.weights.should == { "A" => 1_000_000, "B" => 1_000_000}
30
- YAML.load_file(NOT_EXIST_FILE).should == { "A" => 1_000_000, "B" => 1_000_000, }
31
- end
32
-
33
- it "should raise exception" do
34
- # 作成できないファイル名。
35
- lambda{WeightedPicker.new("", ["A","B"])}.should raise_error(Errno::ENOENT)
36
- end
37
-
38
- it "should read correctly" do
39
- # 正しく取り込めているか?
40
- @wp01.weights.should == { "A" => 1_000_000, "B" => 500_000, }
41
- end
42
-
43
- it "should normalize when the maximum in the file not the same as MAX_WEIGHT" do
44
- # 指定したファイル内の重みの最大値が MAX_WEIGHT と異なれば、
45
- # 全体を normalize して格納。
46
- weights = { "A" => 2_000_000, "B" => 500_000, }
47
- File.open(TMP_FILE, "w") {|io| YAML.dump(weights, io) }
48
- WeightedPicker.new(TMP_FILE, ["A","B"]).weights.should == { "A" => 1_000_000, "B" => 250_000, }
49
- #
50
- weights = { "A" => 250_000, "B" => 500_000, }
51
- File.open(TMP_FILE, "w"){ |io| YAML.dump(weights, io) }
52
- WeightedPicker.new(TMP_FILE, ["A","B"]).weights.should == { "A" => 500_000, "B" => 1_000_000, }
53
- end
54
-
55
- it "should merge when keys between weights and items" do
56
- # weights と items が異なる場合、
57
- # まず新しいキーを重み MAX_WEIGHT で追加して、
58
- # それから実際にはないキーを削除。
59
- weights = { "A" => 1_000_000, "B" => 500_000, }
60
- File.open(TMP_FILE, "w") {|io| YAML.dump(weights, io) }
61
- #puts "HERE ###################"
62
- WeightedPicker.new(TMP_FILE, ["B","C"]).weights.should == { "B" => 500_000, "C" => 1_000_000, }
63
- end
64
-
65
- it "should raise exception if include not integer weight." do
66
- weights = { "A" => 1.0, "B" => 0.5, }
67
- File.open(TMP_FILE, "w") {|io| YAML.dump(weights, io) }
68
- lambda{
69
- WeightedPicker.new(TMP_FILE, ["A","B"])
70
- }.should raise_error(WeightedPicker::InvalidWeightError)
71
- end
72
-
73
- after do
74
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
75
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
76
- end
77
-
78
- end
79
-
80
- describe "Weightedpicker::merge" do
81
- before do
82
- weights = { "A" => 1_000_000, "B" => 500_000, }
83
- items = ["A", "B"]
84
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
85
- @wp01 = WeightedPicker.new(TMP_FILE, items)
86
-
87
- @wp02 = Marshal.load(Marshal.dump(@wp01))
88
- @wp02.weights = {}
89
-
90
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
91
- end
92
-
93
- it "when keys given are less than weights file" do
94
- # 少ない場合
95
- t = Marshal.load(Marshal.dump(@wp01))
96
- keys = ["B"]
97
- t.merge(keys)
98
- t.weights. should == { "B" => 1_000_000 }
99
- # 書き込みチェック
100
- YAML.load_file(TMP_FILE).should == { "B" => 1_000_000 }
101
- end
102
-
103
- it "when keys given are more than weights file" do
104
- # 多い場合
105
- t = Marshal.load(Marshal.dump(@wp01))
106
- keys = ["A", "B", "C"]
107
- t.merge(keys)
108
- t.weights.should == { "A" => 1_000_000, "B" => 500_000, "C" => 1_000_000, }
109
- # 書き込みチェック
110
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 500_000, "C" => 1_000_000}
111
- end
112
-
113
- it "when keys given are the same as weights file" do
114
- t = Marshal.load(Marshal.dump(@wp01))
115
- # 同じ場合
116
- keys = ["A", "B"]
117
- t.merge(keys)
118
- t.weights.should == { "A" => 1_000_000, "B" => 500_000}
119
- # 書き込みチェック
120
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 500_000}
121
- end
122
-
123
- after do
124
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
125
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
126
- end
127
-
128
- end
129
-
130
- describe "Weightedpicker::pick" do
131
- before do
132
- weights = { "A" => 1_000_000, "B" => 500_000, }
133
- items = ["A", "B"]
134
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
135
- @wp01 = WeightedPicker.new(TMP_FILE, items)
136
-
137
- @wp02 = Marshal.load(Marshal.dump(@wp01))
138
- @wp02.weights = {}
139
-
140
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
141
- end
17
+ @wp00 = WeightedPicker.new({})
18
+ @wp01 = WeightedPicker.load_file "spec/a256b128.yaml"
19
+ @wp02 = WeightedPicker.load_file "spec/a512b64.yaml"
20
+ end
21
+
22
+ describe "initialize" do
23
+ it "should create new file with data of 256 when the file not exist" do
24
+ FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
25
+ lambda{
26
+ WeightedPicker.load_file(NOT_EXIST_FILE)
27
+ }.should raise_error(Errno::ENOENT)
28
+ FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
29
+ end
142
30
 
143
- it "should pick" do
144
- lambda{@wp02.pick}.should raise_error(WeightedPicker::NoEntryError)
31
+ #it "should raise exception" do
32
+ # # 作成できないファイル名。
33
+ # lambda{WeightedPicker.load_file("")}.should raise_error(Errno::ENOENT)
34
+ #end
145
35
 
146
- sum = 1_500_000
147
- tryal = 300
148
- results = {"A" => 0, "B" => 0}
149
- tryal.times do |i|
150
- results[@wp01.pick(i * sum / tryal)] += 1
36
+ it "should read correctly" do
37
+ # 正しく取り込めているか?
38
+ @wp01.names_weights.should == { "A" => 256, "B" => 128, }
39
+ @wp02.names_weights.should == { "A" => 512, "B" => 64, }
151
40
  end
152
- results["A"].should == tryal / 3 * 2
153
- results["B"].should == tryal / 3 * 1
154
- end
155
41
 
156
- after do
157
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
158
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
159
- end
42
+ it "should treat as MAX_WEIGHT when the values are over the MAX_WEIGHT" do
43
+ #Not write
44
+ WeightedPicker.load_file("spec/a99999b64.yaml").names_weights.should == { "A" => 65536, "B" => 64, }
45
+ end
160
46
 
161
- end
47
+ it "should treat as 0 when the values are negative" do
48
+ #Not write
49
+ WeightedPicker.load_file("spec/a-1b256.yaml").names_weights.should == { "A" => 0, "B" => 256, }
50
+ end
162
51
 
163
- describe "Weightedpicker::weigh" do
164
- before do
165
- weights = { "A" => 1_000_000, "B" => 500_000, }
166
- items = ["A", "B"]
167
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
168
- @wp01 = WeightedPicker.new(TMP_FILE, items)
169
- @wp02 = Marshal.load(Marshal.dump(@wp01))
170
- @wp02.weights = {}
171
-
172
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
173
- end
52
+ #New item is set by max values in alive entries.
53
+ it "should merge when keys between weights and items" do
54
+ @wp01.merge(["B","C"])
55
+ @wp01.names_weights.should == { "B" => 128, "C" => 256, }
56
+ @wp02.merge(["B","C"])
57
+ @wp02.names_weights.should == { "B" => 64, "C" => 256, }
58
+ end
174
59
 
175
- it "should weigh A" do
176
- t = Marshal.load(Marshal.dump(@wp01))
177
- t.weigh("A")
178
- t.weights.should == { "A" => 1_000_000, "B" => 250_000 }
179
- # 書き込みチェック
180
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 250_000 }
181
- end
60
+ #New item is set by max values in alive entries.
61
+ it "should merge when keys between weights and items" do
62
+ @wp01.merge(["A","C"])
63
+ @wp01.names_weights.should == { "A" => 256, "C" => 256, }
64
+ @wp02.merge(["A","C"])
65
+ @wp02.names_weights.should == { "A" => 512, "C" => 512, }
66
+ end
182
67
 
183
- it "should weigh B" do
184
- t = Marshal.load(Marshal.dump(@wp01))
185
- t.weigh("B")
186
- t.weights.should == { "A" => 1_000_000, "B" => 1_000_000 }
187
- # 書き込みチェック
188
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 1_000_000 }
189
- end
68
+ it "should raise exception if include not integer weight." do
69
+ weights = { "A" => 1.0, "B" => 0.5, }
70
+ lambda{
71
+ WeightedPicker.load_file("spec/float.yaml")
72
+ }.should raise_error(WeightedPicker::InvalidWeightError)
73
+ end
190
74
 
191
- it "should raise error" do
192
- lambda{Marshal.load(Marshal.dump(@wp01)).weigh("C")}.should raise_error(WeightedPicker::NotExistKeyError)
75
+ after do
76
+ #FileUtils.rm AB_YAML if File.exist? AB_YAML
77
+ FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
78
+ end
193
79
  end
194
80
 
195
- after do
196
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
197
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
198
- end
81
+ #before do
82
+ # @wp01 = WeightedPicker.load_file(AB_YAML)
83
+ # @wp00 = WeightedPicker.new({})
84
+ #end
199
85
 
200
- end
86
+ describe "pick" do
87
+ srand(0)
88
+ it "should pick" do
89
+ lambda{@wp00.pick}.should raise_error(WeightedPicker::Tree::NoEntryError)
201
90
 
202
- describe "Weightedpicker::lighten" do
203
- before do
204
- weights = { "A" => 1_000_000, "B" => 500_000, }
205
- items = ["A", "B"]
206
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
207
- @wp01 = WeightedPicker.new(TMP_FILE, items)
208
- @wp02 = Marshal.load(Marshal.dump(@wp01))
209
- @wp02.weights = {}
210
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
91
+ results = {"A" => 0, "B" => 0}
92
+ 300.times do |i|
93
+ results[@wp01.pick] += 1
94
+ end
95
+ #pp @wp01.names_weights #=> {"A"=>256, "B"=>128}
96
+ results["A"].should be_within(20).of(200)
97
+ results["B"].should be_within(10).of(100)
98
+ end
211
99
  end
212
100
 
213
- it "should lighten A" do
214
- t = Marshal.load(Marshal.dump(@wp01))
215
- t.lighten("A")
216
- t.weights.should == { "A" => 1_000_000, "B" => 1_000_000 }
217
- # 書き込みチェック
218
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 1_000_000 }
219
- end
101
+ describe "weigh" do
102
+ it "should weigh A" do
103
+ @wp01.weigh("A")
104
+ @wp01.names_weights.should == { "A" => 512, "B" => 128 }
105
+ end
220
106
 
221
- it "should lighten B" do
222
- t = Marshal.load(Marshal.dump(@wp01))
223
- t.lighten("B")
224
- t.weights.should == { "A" => 1_000_000, "B" => 250_000 }
225
- # 書き込みチェック
226
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 250_000 }
227
- end
107
+ it "should weigh B" do
108
+ @wp01.weigh("B")
109
+ @wp01.names_weights.should == { "A" => 256, "B" => 256 }
110
+ end
228
111
 
229
- it "should raise error" do
230
- t = Marshal.load(Marshal.dump(@wp01))
231
- lambda{t.lighten("C")}.should raise_error(WeightedPicker::NotExistKeyError)
232
- end
112
+ it "should raise error" do
113
+ lambda{Marshal.load(Marshal.dump(@wp01)).weigh("C")}.should raise_error(WeightedPicker::Tree::NoEntryError)
114
+ end
233
115
 
234
- after do
235
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
236
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
237
116
  end
238
117
 
239
- end
118
+ describe "Weightedpicker::lighten" do
119
+ #before do
120
+ # @wp01 = WeightedPicker.load_file(AB_YAML)
121
+ #end
240
122
 
241
- describe "Weightedpicker::normalize_write" do
242
- before do
243
- weights = { "A" => 1_000_000, "B" => 500_000, }
244
- items = ["A", "B"]
245
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
246
- @wp01 = WeightedPicker.new(TMP_FILE, items)
247
- @wp02 = Marshal.load(Marshal.dump(@wp01))
248
- @wp02.weights = {}
249
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
250
- end
251
-
252
- it "should shrink write" do
253
- t = Marshal.load(Marshal.dump(@wp01))
254
- t.weights = { "A" => 2_000_000, "B" => 500_000, }
255
- t.normalize_write
256
- t.weights.should == { "A" => 1_000_000, "B" => 250_000}
257
- # 書き込みチェック
258
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 250_000}
259
- end
123
+ it "should lighten A" do
124
+ t = Marshal.load(Marshal.dump(@wp01))
125
+ t.lighten("A")
126
+ t.names_weights.should == { "A" => 128, "B" => 128 }
127
+ end
260
128
 
261
- it "should shrink write with 2" do
262
- t = Marshal.load(Marshal.dump(@wp01))
263
- t.weights = { "A" => 2_000_000, "B" => 2, }
264
- t.normalize_write
265
- t.weights.should == { "A" => 1_000_000, "B" => 1}
266
- end
129
+ it "should lighten B" do
130
+ t = Marshal.load(Marshal.dump(@wp01))
131
+ t.lighten("B")
132
+ t.names_weights.should == { "A" => 256, "B" => 64 }
133
+ end
267
134
 
268
- it "should shrink write with 1" do
269
- t = Marshal.load(Marshal.dump(@wp01))
270
- t.weights = { "A" => 2_000_000, "B" => 1, }
271
- t.normalize_write
272
- t.weights.should == { "A" => 1_000_000, "B" => 1}
273
- end
135
+ it "should raise error" do
136
+ t = Marshal.load(Marshal.dump(@wp01))
137
+ lambda{t.lighten("C")}.should raise_error(WeightedPicker::Tree::NoEntryError)
138
+ end
274
139
 
275
- it "should shrink write with 0" do
276
- t = Marshal.load(Marshal.dump(@wp01))
277
- t.weights = { "A" => 2_000_000, "B" => 0, }
278
- t.normalize_write
279
- t.weights.should == { "A" => 1_000_000, "B" => 0}
280
140
  end
281
141
 
282
- it "should expand write" do
283
- t = Marshal.load(Marshal.dump(@wp01))
284
- t.weights = { "A" => 500_000, "B" => 500_000, }
285
- t.normalize_write
286
- t.weights.should == { "A" => 1_000_000, "B" => 1_000_000}
287
- # 書き込みチェック
288
- YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 1_000_000}
289
- end
142
+ describe "include zero weight" do
143
+ it "should not change zero weight." do
144
+ wp01 = WeightedPicker.load_file("spec/a256b0.yaml")
145
+ wp01.names_weights.should == { "A" => 256, "B" => 0 }
146
+ wp01.lighten("A")
147
+ wp01.names_weights.should == { "A" => 128, "B" => 0 }
148
+ wp01.weigh("A")
149
+ wp01.names_weights.should == { "A" => 256, "B" => 0 }
150
+ wp01.lighten("B")
151
+ wp01.names_weights.should == { "A" => 256, "B" => 0 }
152
+ wp01.weigh("B")
153
+ wp01.names_weights.should == { "A" => 256, "B" => 0 }
154
+ end
290
155
 
291
- it "should raise error" do
292
- lambda{@wp02.normalize_write}.should raise_error (WeightedPicker::NoEntryError)
293
156
  end
294
157
 
295
- after do
296
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
297
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
298
- end
158
+ describe "include one weight" do
159
+ it "should not change zero weight." do
160
+ wp01 = WeightedPicker.load_file("spec/a256b1.yaml")
299
161
 
300
- end
162
+ wp01.names_weights.should == { "A" => 256, "B" => 1 }
301
163
 
302
- describe "include zero weight" do
303
- before do
304
- weights = { "A" => 1_000_000, "B" => 0, }
305
- items = ["A", "B"]
306
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
307
- @wp01 = WeightedPicker.new(TMP_FILE, items)
308
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
309
- end
164
+ wp01.lighten("A")
165
+ wp01.names_weights.should == { "A" => 128, "B" => 1 }
310
166
 
311
- it "should not change zero weight." do
312
- @wp01.weights.should == { "A" => 1_000_000, "B" => 0 }
313
- @wp01.lighten("A")
314
- @wp01.weights.should == { "A" => 1_000_000, "B" => 0 }
315
- @wp01.weigh("A")
316
- @wp01.weights.should == { "A" => 1_000_000, "B" => 0 }
317
- @wp01.lighten("B")
318
- @wp01.weights.should == { "A" => 1_000_000, "B" => 0 }
319
- @wp01.weigh("B")
320
- @wp01.weights.should == { "A" => 1_000_000, "B" => 0 }
321
- end
167
+ wp01.weigh("A")
168
+ wp01.names_weights.should == { "A" => 256, "B" => 1 }
322
169
 
323
- after do
324
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
325
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
326
- end
170
+ wp01.lighten("B")
171
+ wp01.names_weights.should == { "A" => 256, "B" => 1 }
327
172
 
328
- end
173
+ wp01.weigh("B")
174
+ wp01.names_weights.should == { "A" => 256, "B" => 2 }
175
+ end
329
176
 
330
- describe "include one weight" do
331
- before do
332
- weights = { "A" => 1_000_000, "B" => 1, }
333
- items = ["A", "B"]
334
- File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
335
- @wp01 = WeightedPicker.new(TMP_FILE, items)
336
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
337
177
  end
338
178
 
339
- it "should not change zero weight." do
340
- @wp01.weights.should == { "A" => 1_000_000, "B" => 1 }
179
+ describe "Weightedpicker::dump" do
180
+ it "should dump yaml." do
181
+ io = StringIO.new
182
+ @wp01.dump(io)
183
+ io.rewind
184
+ results = YAML.load(io)
185
+ results.should == { "A" => 256, "B" => 128, }
341
186
 
342
- @wp01.lighten("A")
343
- @wp01.weights.should == { "A" => 1_000_000, "B" => 2 }
344
-
345
- @wp01.weigh("A")
346
- @wp01.weights.should == { "A" => 1_000_000, "B" => 1 }
347
-
348
- @wp01.lighten("B")
349
- @wp01.weights.should == { "A" => 1_000_000, "B" => 1 }
350
-
351
- @wp01.weigh("B")
352
- @wp01.weights.should == { "A" => 1_000_000, "B" => 2 }
353
- end
187
+ end
354
188
 
355
- after do
356
- FileUtils.rm TMP_FILE if File.exist? TMP_FILE
357
- FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
358
189
  end
359
190
 
360
191
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "weightedpicker"
8
- s.version = "0.0.2"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["ippei94da"]
12
- s.date = "2012-07-13"
12
+ s.date = "2013-04-10"
13
13
  s.description = "This library enables to pick out items at the rate of their weight.\n Weight data is storaged as a YAML file.\n You can use this library for music player, wallpaper changer, language training.\n "
14
14
  s.email = "ippei94da@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -26,36 +26,45 @@ Gem::Specification.new do |s|
26
26
  "Rakefile",
27
27
  "VERSION",
28
28
  "lib/weightedpicker.rb",
29
+ "lib/weightedpicker/tree.rb",
30
+ "spec/a-1b256.yaml",
31
+ "spec/a256b0.yaml",
32
+ "spec/a256b1.yaml",
33
+ "spec/a256b128.yaml",
34
+ "spec/a512b64.yaml",
35
+ "spec/a99999b64.yaml",
36
+ "spec/float.yaml",
29
37
  "spec/spec_helper.rb",
38
+ "spec/tree_spec.rb",
30
39
  "spec/weightedpicker_spec.rb",
31
40
  "weightedpicker.gemspec"
32
41
  ]
33
42
  s.homepage = "http://github.com/ippei94da/weightedpicker"
34
43
  s.licenses = ["MIT"]
35
44
  s.require_paths = ["lib"]
36
- s.rubygems_version = "1.8.11"
45
+ s.rubygems_version = "1.8.23"
37
46
  s.summary = "Picking one item from list at the rate of its weight."
38
47
 
39
48
  if s.respond_to? :specification_version then
40
49
  s.specification_version = 3
41
50
 
42
51
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
- s.add_development_dependency(%q<rspec>, ["~> 2.9.0"])
52
+ s.add_development_dependency(%q<rspec>, ["~> 2.13.0"])
44
53
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
45
- s.add_development_dependency(%q<bundler>, ["~> 1.1.3"])
54
+ s.add_development_dependency(%q<bundler>, ["~> 1.3.4"])
46
55
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
47
56
  s.add_development_dependency(%q<simplecov>, [">= 0"])
48
57
  else
49
- s.add_dependency(%q<rspec>, ["~> 2.9.0"])
58
+ s.add_dependency(%q<rspec>, ["~> 2.13.0"])
50
59
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
51
- s.add_dependency(%q<bundler>, ["~> 1.1.3"])
60
+ s.add_dependency(%q<bundler>, ["~> 1.3.4"])
52
61
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
53
62
  s.add_dependency(%q<simplecov>, [">= 0"])
54
63
  end
55
64
  else
56
- s.add_dependency(%q<rspec>, ["~> 2.9.0"])
65
+ s.add_dependency(%q<rspec>, ["~> 2.13.0"])
57
66
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
58
- s.add_dependency(%q<bundler>, ["~> 1.1.3"])
67
+ s.add_dependency(%q<bundler>, ["~> 1.3.4"])
59
68
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
60
69
  s.add_dependency(%q<simplecov>, [">= 0"])
61
70
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: weightedpicker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,27 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-13 00:00:00.000000000 Z
12
+ date: 2013-04-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &71663210 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 2.9.0
21
+ version: 2.13.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *71663210
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.13.0
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rdoc
27
- requirement: &71662930 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,21 +37,31 @@ dependencies:
32
37
  version: '3.12'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *71662930
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.12'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: bundler
38
- requirement: &71662620 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ~>
42
52
  - !ruby/object:Gem::Version
43
- version: 1.1.3
53
+ version: 1.3.4
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *71662620
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.3.4
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: jeweler
49
- requirement: &71662320 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ~>
@@ -54,10 +69,15 @@ dependencies:
54
69
  version: 1.8.3
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *71662320
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.3
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: simplecov
60
- requirement: &71662030 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ! '>='
@@ -65,7 +85,12 @@ dependencies:
65
85
  version: '0'
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *71662030
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
69
94
  description: ! "This library enables to pick out items at the rate of their weight.\n
70
95
  \ Weight data is storaged as a YAML file.\n You can use this library for music
71
96
  player, wallpaper changer, language training.\n "
@@ -85,7 +110,16 @@ files:
85
110
  - Rakefile
86
111
  - VERSION
87
112
  - lib/weightedpicker.rb
113
+ - lib/weightedpicker/tree.rb
114
+ - spec/a-1b256.yaml
115
+ - spec/a256b0.yaml
116
+ - spec/a256b1.yaml
117
+ - spec/a256b128.yaml
118
+ - spec/a512b64.yaml
119
+ - spec/a99999b64.yaml
120
+ - spec/float.yaml
88
121
  - spec/spec_helper.rb
122
+ - spec/tree_spec.rb
89
123
  - spec/weightedpicker_spec.rb
90
124
  - weightedpicker.gemspec
91
125
  homepage: http://github.com/ippei94da/weightedpicker
@@ -103,7 +137,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
137
  version: '0'
104
138
  segments:
105
139
  - 0
106
- hash: -490884429
140
+ hash: -243347693
107
141
  required_rubygems_version: !ruby/object:Gem::Requirement
108
142
  none: false
109
143
  requirements:
@@ -112,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
146
  version: '0'
113
147
  requirements: []
114
148
  rubyforge_project:
115
- rubygems_version: 1.8.11
149
+ rubygems_version: 1.8.23
116
150
  signing_key:
117
151
  specification_version: 3
118
152
  summary: Picking one item from list at the rate of its weight.