weightedpicker 0.0.1 → 0.0.2

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/CHANGES ADDED
@@ -0,0 +1,9 @@
1
+ == Master (for 0.0.2)
2
+ Use integer alternative to float.
3
+ Add test for 'pick' method.
4
+
5
+ == Version 0.0.1
6
+ bugfix.
7
+
8
+ == Version 0.0.0
9
+ initiali release.
data/Rakefile CHANGED
@@ -17,8 +17,8 @@ Jeweler::Tasks.new do |gem|
17
17
  gem.name = "weightedpicker"
18
18
  gem.homepage = "http://github.com/ippei94da/weightedpicker"
19
19
  gem.license = "MIT"
20
- gem.summary = %Q{Picking one item from list with weight}
21
- gem.description = %Q{This library enable to pick out items with their weight.
20
+ gem.summary = %Q{Picking one item from list at the rate of its weight.}
21
+ gem.description = %Q{This library enables to pick out items at the rate of their weight.
22
22
  Weight data is storaged as a YAML file.
23
23
  You can use this library for music player, wallpaper changer, language training.
24
24
  }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -1,8 +1,8 @@
1
1
  require 'yaml'
2
2
 
3
3
  # TODO
4
- # initialize.指定したファイル内のデータが WeightedPicker 的に
5
- # 解釈できなければ例外。
4
+ # initialize.指定したファイル内のデータが WeightedPicker 的に
5
+ # 解釈できなければ例外。
6
6
 
7
7
  #= 概要
8
8
  # 要素群の中から、優先度に応じた重み付きランダムで、
@@ -44,12 +44,12 @@ require 'yaml'
44
44
  #
45
45
  #= 評価の上限と下限
46
46
  #== 上限
47
- # 点数の上限は 1.0
47
+ # 点数の上限は 1 million
48
48
  # 上限を越える点数の要素が現れたら、
49
- # それが 1.0 になるように全ての要素の評価を除算して正規化する。
49
+ # それが max valueになるように全ての要素の評価を除算して正規化する。
50
50
  #
51
51
  #== 下限
52
- # 点数の下限は 2.0 ** (-500) としておく。
52
+ # 点数の下限は 1 としておく。
53
53
  # 下限を下回る点数の要素が現れたら、
54
54
  # その要素の点数を下限値に設定する。
55
55
  # これが重要になるのはクイズゲームで間違ったときにその問題以外の
@@ -91,143 +91,127 @@ require 'yaml'
91
91
  #
92
92
  # ヒストリ関係はこのクラスの外に出した方が良いと判断。
93
93
  class WeightedPicker
94
- MAX_WEIGHT = 1.0
95
- MIN_WEIGHT = 2.0 ** (-500)
96
-
97
- class InvalidFilenameError < Exception; end
98
- class NoEntryError < Exception; end
99
- class NotExistKeyError < Exception; end
100
-
101
- # Initialization.
102
- # Argument 'file' indicates a strage file name for data
103
- # which this class manages.
104
- # If the 'file' does not exist, this file is used to data strage.
105
- #
106
- # Argument 'items' is an array of items to be managed.
107
- # Not all the items in the 'file' survive.
108
- # A and B in the file and B and C in items,
109
- # then B and C is the items which this class manage.
110
- # A is discarded from record.
111
- def initialize(file, items)
112
- @save_file = file
113
- unless File.exist? @save_file
114
- File.open(@save_file, "w") {|io| YAML.dump({}, io)}
115
- end
116
- @weights = YAML.load_file(file)
117
-
118
- merge(items)
119
- normalize_write
120
- end
121
-
122
- #乱数を利用して優先度で重み付けして要素を選び、要素を返す。
123
- def pick
124
- raise NoEntryError if @weights.empty?
125
-
126
- ##乱数を 2つ生成し、adopt? を呼ぶ。
127
- ##乱数でどれか1つを選ぶ。
128
- ##その priority に応じて、確率で採用する。
129
- while ( true )
130
- item = @weights.keys[ rand( @weights.size ) ]
131
-
132
- #next if ( ! pick?( item ) )
133
- if adopt?( item, rand )
134
- return item
135
- end
136
- end
137
- end
138
-
139
- #重みを重くする。(優先度が上がる)
140
- def weigh(item)
141
- #pp @weights
142
- raise NotExistKeyError unless @weights.has_key?(item)
143
- @weights[ item ] *= 2.0
144
- normalize_write
145
- end
146
-
147
- #重みを軽くする。(優先度が下がる)
148
- def lighten( item )
149
- raise NotExistKeyError unless @weights.has_key?(item)
150
- @weights[ item ] /= 2.0
151
- normalize_write
152
- end
153
-
154
- ##管理している要素の数を返す。
155
- #def size
156
- # @weights.size
157
- #end
158
-
159
- ##管理している要素と重みでイテレート。
160
- ##e.g. ws0.each { |item, weight| p item, weight }
161
- ##item が管理しているオブジェクト、weight が重み。
162
- #def each
163
- # @weights.each do |item, weight|
164
- # yield( item, weight )
165
- # end
166
- #end
167
-
168
- private
169
-
170
- # 引数 keys で示したものと、
171
- # 内部的に管理しているデータが整合しているかチェックし、
172
- # keys に合わせる。
173
- # 追加されたデータは MAX_WEIGHT の重みとなる。
174
- # データが削除された場合、それが最大値の可能性があるので
175
- # 必ず normalize_write される。
176
- def merge(keys)
177
- keys.each do |key|
178
- @weights[key] = MAX_WEIGHT unless
179
- @weights.keys.include?(key)
180
- end
181
- @weights.each do |key, val|
182
- @weights.delete(key) unless keys.include?(key)
183
- end
184
- normalize_write
185
- end
186
-
187
- # given_val 0.0〜1.0 の間の実数とする。
188
- # 重みと 与えられた given_val が等しい場合は false になる。
189
- # これは重み 1.0 が稀に採用されないことがあっても
190
- # 重み 0.0 が稀にでも採用されることを嫌ってのこと。
191
- def adopt?( item, given_val )
192
- raise NotExistKeyError unless @weights.has_key?(item)
193
-
194
- return given_val < (@weights[item])
195
- end
196
-
197
- ##データを更新し、整合性を確保する。
198
- #def refresh
199
- # TODO
200
- # raise れいがいめい
201
- # "Cannot do refresh because no entry exists." if @weights.size == 0
202
-
203
- # #normalize のために事前設定。
204
- # @max_weight = @weights.max{ |a, b| a[1] <=> b[1] }[1]
205
- # normalize
206
-
207
- # #normalize したあとのデータに従い、total_weight などを算出。
208
- # @total_weight = @weights.values.inject( 0 ){ |sum, i| sum += i }
209
- # @max_weight = @weights.max{ |a, b| a[1] <=> b[1] }[1]
210
- #end
211
-
212
- # 最大値を max とするように規格化する。
213
- # ただし、weight が MIN_WEIGHT 未満となった項目は
214
- # MIN_WEIGHT を新しい weight とする。
215
- def normalize_write
216
- raise NoEntryError if @weights.size == 0
217
-
218
- old_max = @weights.values.max
219
- @weights.each do |key, val|
220
- new_val = (val * (MAX_WEIGHT / old_max ) )
221
- if new_val < MIN_WEIGHT
222
- @weights[key] = MIN_WEIGHT
223
- else
224
- @weights[key] = new_val
225
- end
226
- end
227
-
228
- File.open(@save_file, "w") do |io|
229
- YAML.dump(@weights, io)
230
- end
231
- end
94
+ MAX_WEIGHT = 1_000_000
95
+ MIN_WEIGHT = 1
96
+
97
+ class InvalidFilenameError < Exception; end
98
+ class NoEntryError < Exception; end
99
+ class NotExistKeyError < Exception; end
100
+ class InvalidWeightError < Exception; end
101
+
102
+ # Initialization.
103
+ # Argument 'file' indicates a strage file name for data
104
+ # which this class manages.
105
+ # If the 'file' does not exist, this file is used to data strage.
106
+ #
107
+ # Argument 'items' is an array of items to be managed.
108
+ # Not all the items in the 'file' survive.
109
+ # A and B in the file and B and C in items,
110
+ # then B and C is the items which this class manage.
111
+ # 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)
118
+
119
+ @weights.values.each do |i|
120
+ raise InvalidWeightError, "#{i.inspect}, not integer." unless i.is_a? Integer
121
+ end
122
+
123
+ merge(items)
124
+ normalize_write
125
+ end
126
+
127
+ # 乱数を利用して優先度で重み付けして要素を選び、要素を返す。
128
+ # 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
146
+ end
147
+
148
+ #重みを重くする。(優先度が上がる)
149
+ def weigh(item)
150
+ #pp @weights
151
+ raise NotExistKeyError unless @weights.has_key?(item)
152
+ @weights[ item ] *= 2
153
+ normalize_write
154
+ end
155
+
156
+ #重みを軽くする。(優先度が下がる)
157
+ 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
164
+ end
165
+
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
+ # 引数 keys で示したものと、
183
+ # 内部的に管理しているデータが整合しているかチェックし、
184
+ # keys に合わせる。
185
+ # 追加されたデータは MAX_WEIGHT の重みとなる。
186
+ # データが削除された場合、それが最大値の可能性があるので
187
+ # 必ず normalize_write される。
188
+ def merge(keys)
189
+ keys.each do |key|
190
+ @weights[key] ||= MAX_WEIGHT
191
+ end
192
+ @weights.each do |key, val|
193
+ @weights.delete(key) unless keys.include?(key)
194
+ end
195
+ #pp @weights
196
+ #pp keys
197
+ normalize_write
198
+ end
199
+
200
+ # 最大値を max とするように規格化する。
201
+ # ただし、weight MIN_WEIGHT 未満となった項目は
202
+ # MIN_WEIGHT を新しい weight とする。
203
+ def normalize_write
204
+ raise NoEntryError if @weights.size == 0
205
+
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
211
+
212
+ File.open(@save_file, "w") do |io|
213
+ YAML.dump(@weights, io)
214
+ end
215
+ end
232
216
 
233
217
  end
@@ -4,7 +4,7 @@ require "fileutils"
4
4
 
5
5
  class WeightedPicker
6
6
  attr_accessor :weights
7
- public :normalize_write, :adopt?, :merge
7
+ public :normalize_write, :merge
8
8
  end
9
9
 
10
10
  TMP_FILE = "tmp.yaml"
@@ -14,7 +14,7 @@ NOT_EXIST_FILE = "not_exist_file"
14
14
 
15
15
  describe "Weightedpicker::initialize" do
16
16
  before do
17
- weights = { "A" => 1.0, "B" => 0.5, }
17
+ weights = { "A" => 1_000_000, "B" => 500_000, }
18
18
  items = ["A", "B"]
19
19
  File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
20
20
  @wp01 = WeightedPicker.new(TMP_FILE, items)
@@ -26,8 +26,8 @@ describe "Weightedpicker::initialize" do
26
26
  it "should create new file when the file not exist" do
27
27
  # 指定したファイルが存在しない場合、
28
28
  t = WeightedPicker.new(NOT_EXIST_FILE, ["A","B"])
29
- t.weights.should == { "A" => 1.0, "B" => 1.0}
30
- YAML.load_file(NOT_EXIST_FILE).should == { "A" => 1.0, "B" => 1.0, }
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
31
  end
32
32
 
33
33
  it "should raise exception" do
@@ -37,26 +37,37 @@ describe "Weightedpicker::initialize" do
37
37
 
38
38
  it "should read correctly" do
39
39
  # 正しく取り込めているか?
40
- @wp01.weights.should == { "A" => 1.0, "B" => 0.5, }
40
+ @wp01.weights.should == { "A" => 1_000_000, "B" => 500_000, }
41
41
  end
42
42
 
43
43
  it "should normalize when the maximum in the file not the same as MAX_WEIGHT" do
44
44
  # 指定したファイル内の重みの最大値が MAX_WEIGHT と異なれば、
45
45
  # 全体を normalize して格納。
46
- weights = { "A" => 2.0, "B" => 0.5, }
46
+ weights = { "A" => 2_000_000, "B" => 500_000, }
47
47
  File.open(TMP_FILE, "w") {|io| YAML.dump(weights, io) }
48
- WeightedPicker.new(TMP_FILE, ["A","B"]).weights.should == { "A" => 1.0, "B" => 0.25, }
48
+ WeightedPicker.new(TMP_FILE, ["A","B"]).weights.should == { "A" => 1_000_000, "B" => 250_000, }
49
49
  #
50
- weights = { "A" => 0.25, "B" => 0.5, }
50
+ weights = { "A" => 250_000, "B" => 500_000, }
51
51
  File.open(TMP_FILE, "w"){ |io| YAML.dump(weights, io) }
52
- WeightedPicker.new(TMP_FILE, ["A","B"]).weights.should == { "A" => 0.5, "B" => 1.0, }
52
+ WeightedPicker.new(TMP_FILE, ["A","B"]).weights.should == { "A" => 500_000, "B" => 1_000_000, }
53
53
  end
54
54
 
55
55
  it "should merge when keys between weights and items" do
56
- # weights と items が異なる場合
57
- weights = { "A" => 1.0, "B" => 0.50, }
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, }
58
67
  File.open(TMP_FILE, "w") {|io| YAML.dump(weights, io) }
59
- WeightedPicker.new(TMP_FILE, ["B","C"]).weights.should == { "B" => 0.5, "C" => 1.0, }
68
+ lambda{
69
+ WeightedPicker.new(TMP_FILE, ["A","B"])
70
+ }.should raise_error(WeightedPicker::InvalidWeightError)
60
71
  end
61
72
 
62
73
  after do
@@ -68,7 +79,7 @@ end
68
79
 
69
80
  describe "Weightedpicker::merge" do
70
81
  before do
71
- weights = { "A" => 1.0, "B" => 0.5, }
82
+ weights = { "A" => 1_000_000, "B" => 500_000, }
72
83
  items = ["A", "B"]
73
84
  File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
74
85
  @wp01 = WeightedPicker.new(TMP_FILE, items)
@@ -84,9 +95,9 @@ describe "Weightedpicker::merge" do
84
95
  t = Marshal.load(Marshal.dump(@wp01))
85
96
  keys = ["B"]
86
97
  t.merge(keys)
87
- t.weights. should == { "B" => 1.0 }
98
+ t.weights. should == { "B" => 1_000_000 }
88
99
  # 書き込みチェック
89
- YAML.load_file(TMP_FILE).should == { "B" => 1.0 }
100
+ YAML.load_file(TMP_FILE).should == { "B" => 1_000_000 }
90
101
  end
91
102
 
92
103
  it "when keys given are more than weights file" do
@@ -94,9 +105,9 @@ describe "Weightedpicker::merge" do
94
105
  t = Marshal.load(Marshal.dump(@wp01))
95
106
  keys = ["A", "B", "C"]
96
107
  t.merge(keys)
97
- t.weights.should == { "A" => 1.0, "B" => 0.5, "C" => 1.0, }
108
+ t.weights.should == { "A" => 1_000_000, "B" => 500_000, "C" => 1_000_000, }
98
109
  # 書き込みチェック
99
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 0.5, "C" => 1.0}
110
+ YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 500_000, "C" => 1_000_000}
100
111
  end
101
112
 
102
113
  it "when keys given are the same as weights file" do
@@ -104,9 +115,9 @@ describe "Weightedpicker::merge" do
104
115
  # 同じ場合
105
116
  keys = ["A", "B"]
106
117
  t.merge(keys)
107
- t.weights.should == { "A" => 1.0, "B" => 0.5}
118
+ t.weights.should == { "A" => 1_000_000, "B" => 500_000}
108
119
  # 書き込みチェック
109
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 0.5}
120
+ YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 500_000}
110
121
  end
111
122
 
112
123
  after do
@@ -118,7 +129,7 @@ end
118
129
 
119
130
  describe "Weightedpicker::pick" do
120
131
  before do
121
- weights = { "A" => 1.0, "B" => 0.5, }
132
+ weights = { "A" => 1_000_000, "B" => 500_000, }
122
133
  items = ["A", "B"]
123
134
  File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
124
135
  @wp01 = WeightedPicker.new(TMP_FILE, items)
@@ -130,11 +141,16 @@ describe "Weightedpicker::pick" do
130
141
  end
131
142
 
132
143
  it "should pick" do
133
- ## No test, beccause including randomness.
134
- #assert_raise(WeightedPicker::NoEntryError){@wp02.pick}
135
- #100.times do
136
- # assert_equal(true, ["A","B"].include?(@wp01.pick))
137
- #end
144
+ lambda{@wp02.pick}.should raise_error(WeightedPicker::NoEntryError)
145
+
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
151
+ end
152
+ results["A"].should == tryal / 3 * 2
153
+ results["B"].should == tryal / 3 * 1
138
154
  end
139
155
 
140
156
  after do
@@ -146,7 +162,7 @@ end
146
162
 
147
163
  describe "Weightedpicker::weigh" do
148
164
  before do
149
- weights = { "A" => 1.0, "B" => 0.5, }
165
+ weights = { "A" => 1_000_000, "B" => 500_000, }
150
166
  items = ["A", "B"]
151
167
  File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
152
168
  @wp01 = WeightedPicker.new(TMP_FILE, items)
@@ -159,17 +175,17 @@ describe "Weightedpicker::weigh" do
159
175
  it "should weigh A" do
160
176
  t = Marshal.load(Marshal.dump(@wp01))
161
177
  t.weigh("A")
162
- t.weights.should == { "A" => 1.0, "B" => 0.25 }
178
+ t.weights.should == { "A" => 1_000_000, "B" => 250_000 }
163
179
  # 書き込みチェック
164
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 0.25 }
180
+ YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 250_000 }
165
181
  end
166
182
 
167
183
  it "should weigh B" do
168
184
  t = Marshal.load(Marshal.dump(@wp01))
169
185
  t.weigh("B")
170
- t.weights.should == { "A" => 1.0, "B" => 1.0 }
186
+ t.weights.should == { "A" => 1_000_000, "B" => 1_000_000 }
171
187
  # 書き込みチェック
172
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 1.0 }
188
+ YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 1_000_000 }
173
189
  end
174
190
 
175
191
  it "should raise error" do
@@ -185,7 +201,7 @@ end
185
201
 
186
202
  describe "Weightedpicker::lighten" do
187
203
  before do
188
- weights = { "A" => 1.0, "B" => 0.5, }
204
+ weights = { "A" => 1_000_000, "B" => 500_000, }
189
205
  items = ["A", "B"]
190
206
  File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
191
207
  @wp01 = WeightedPicker.new(TMP_FILE, items)
@@ -197,17 +213,17 @@ describe "Weightedpicker::lighten" do
197
213
  it "should lighten A" do
198
214
  t = Marshal.load(Marshal.dump(@wp01))
199
215
  t.lighten("A")
200
- t.weights.should == { "A" => 1.0, "B" => 1.0 }
216
+ t.weights.should == { "A" => 1_000_000, "B" => 1_000_000 }
201
217
  # 書き込みチェック
202
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 1.0 }
218
+ YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 1_000_000 }
203
219
  end
204
220
 
205
221
  it "should lighten B" do
206
222
  t = Marshal.load(Marshal.dump(@wp01))
207
223
  t.lighten("B")
208
- t.weights.should == { "A" => 1.0, "B" => 0.25 }
224
+ t.weights.should == { "A" => 1_000_000, "B" => 250_000 }
209
225
  # 書き込みチェック
210
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 0.25 }
226
+ YAML.load_file(TMP_FILE).should == { "A" => 1_000_000, "B" => 250_000 }
211
227
  end
212
228
 
213
229
  it "should raise error" do
@@ -222,9 +238,9 @@ describe "Weightedpicker::lighten" do
222
238
 
223
239
  end
224
240
 
225
- describe "Weightedpicker::adopt" do
241
+ describe "Weightedpicker::normalize_write" do
226
242
  before do
227
- weights = { "A" => 1.0, "B" => 0.5, }
243
+ weights = { "A" => 1_000_000, "B" => 500_000, }
228
244
  items = ["A", "B"]
229
245
  File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
230
246
  @wp01 = WeightedPicker.new(TMP_FILE, items)
@@ -233,15 +249,47 @@ describe "Weightedpicker::adopt" do
233
249
  FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
234
250
  end
235
251
 
236
- it do
237
- @wp01.adopt?("A", 0.0 ).should be_true
238
- @wp01.adopt?("A", 0.5 ).should be_true
239
- @wp01.adopt?("A", 0.999).should be_true
240
- @wp01.adopt?("A", 1.0 ).should be_false
241
- @wp01.adopt?("B", 0.0 ).should be_true
242
- @wp01.adopt?("B", 0.499).should be_true
243
- @wp01.adopt?("B", 0.5 ).should be_false
244
- @wp01.adopt?("B", 1.0 ).should be_false
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
260
+
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
267
+
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
274
+
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
+ end
281
+
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
290
+
291
+ it "should raise error" do
292
+ lambda{@wp02.normalize_write}.should raise_error (WeightedPicker::NoEntryError)
245
293
  end
246
294
 
247
295
  after do
@@ -251,37 +299,57 @@ describe "Weightedpicker::adopt" do
251
299
 
252
300
  end
253
301
 
254
- describe "Weightedpicker::normalize_write" do
302
+ describe "include zero weight" do
255
303
  before do
256
- weights = { "A" => 1.0, "B" => 0.5, }
304
+ weights = { "A" => 1_000_000, "B" => 0, }
257
305
  items = ["A", "B"]
258
306
  File.open(TMP_FILE, "w") { |io| YAML.dump(weights, io) }
259
307
  @wp01 = WeightedPicker.new(TMP_FILE, items)
260
- @wp02 = Marshal.load(Marshal.dump(@wp01))
261
- @wp02.weights = {}
262
308
  FileUtils.rm NOT_EXIST_FILE if File.exist? NOT_EXIST_FILE
263
309
  end
264
310
 
265
- it "should shrink write" do
266
- t = Marshal.load(Marshal.dump(@wp01))
267
- t.weights = { "A" => 2.0, "B" => 0.5, }
268
- t.normalize_write
269
- t.weights.should == { "A" => 1.0, "B" => 0.25}
270
- # 書き込みチェック
271
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 0.25}
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 }
272
321
  end
273
322
 
274
- it "should shrink write" do
275
- t = Marshal.load(Marshal.dump(@wp01))
276
- t.weights = { "A" => 0.5, "B" => 0.5, }
277
- t.normalize_write
278
- t.weights.should == { "A" => 1.0, "B" => 1.00}
279
- # 書き込みチェック
280
- YAML.load_file(TMP_FILE).should == { "A" => 1.0, "B" => 1.00}
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
281
326
  end
282
327
 
283
- it "should raise error" do
284
- lambda{@wp02.normalize_write}.should raise_error (WeightedPicker::NoEntryError)
328
+ end
329
+
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
+ end
338
+
339
+ it "should not change zero weight." do
340
+ @wp01.weights.should == { "A" => 1_000_000, "B" => 1 }
341
+
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 }
285
353
  end
286
354
 
287
355
  after do
@@ -290,3 +358,4 @@ describe "Weightedpicker::normalize_write" do
290
358
  end
291
359
 
292
360
  end
361
+
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "weightedpicker"
8
- s.version = "0.0.1"
8
+ s.version = "0.0.2"
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-04-13"
13
- s.description = "This library enable to pick out items with 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 "
12
+ s.date = "2012-07-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 = [
16
16
  "LICENSE.txt",
@@ -19,8 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.files = [
20
20
  ".document",
21
21
  ".rspec",
22
+ "CHANGES",
22
23
  "Gemfile",
23
- "Gemfile.lock",
24
24
  "LICENSE.txt",
25
25
  "README.rdoc",
26
26
  "Rakefile",
@@ -33,8 +33,8 @@ Gem::Specification.new do |s|
33
33
  s.homepage = "http://github.com/ippei94da/weightedpicker"
34
34
  s.licenses = ["MIT"]
35
35
  s.require_paths = ["lib"]
36
- s.rubygems_version = "1.8.21"
37
- s.summary = "Picking one item from list with weight"
36
+ s.rubygems_version = "1.8.11"
37
+ s.summary = "Picking one item from list at the rate of its weight."
38
38
 
39
39
  if s.respond_to? :specification_version then
40
40
  s.specification_version = 3
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.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-13 00:00:00.000000000 Z
12
+ date: 2012-07-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: !ruby/object:Gem::Requirement
16
+ requirement: &71663210 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,15 +21,10 @@ dependencies:
21
21
  version: 2.9.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ~>
28
- - !ruby/object:Gem::Version
29
- version: 2.9.0
24
+ version_requirements: *71663210
30
25
  - !ruby/object:Gem::Dependency
31
26
  name: rdoc
32
- requirement: !ruby/object:Gem::Requirement
27
+ requirement: &71662930 !ruby/object:Gem::Requirement
33
28
  none: false
34
29
  requirements:
35
30
  - - ~>
@@ -37,15 +32,10 @@ dependencies:
37
32
  version: '3.12'
38
33
  type: :development
39
34
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: '3.12'
35
+ version_requirements: *71662930
46
36
  - !ruby/object:Gem::Dependency
47
37
  name: bundler
48
- requirement: !ruby/object:Gem::Requirement
38
+ requirement: &71662620 !ruby/object:Gem::Requirement
49
39
  none: false
50
40
  requirements:
51
41
  - - ~>
@@ -53,15 +43,10 @@ dependencies:
53
43
  version: 1.1.3
54
44
  type: :development
55
45
  prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ~>
60
- - !ruby/object:Gem::Version
61
- version: 1.1.3
46
+ version_requirements: *71662620
62
47
  - !ruby/object:Gem::Dependency
63
48
  name: jeweler
64
- requirement: !ruby/object:Gem::Requirement
49
+ requirement: &71662320 !ruby/object:Gem::Requirement
65
50
  none: false
66
51
  requirements:
67
52
  - - ~>
@@ -69,15 +54,10 @@ dependencies:
69
54
  version: 1.8.3
70
55
  type: :development
71
56
  prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- version: 1.8.3
57
+ version_requirements: *71662320
78
58
  - !ruby/object:Gem::Dependency
79
59
  name: simplecov
80
- requirement: !ruby/object:Gem::Requirement
60
+ requirement: &71662030 !ruby/object:Gem::Requirement
81
61
  none: false
82
62
  requirements:
83
63
  - - ! '>='
@@ -85,15 +65,10 @@ dependencies:
85
65
  version: '0'
86
66
  type: :development
87
67
  prerelease: false
88
- version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ! '>='
92
- - !ruby/object:Gem::Version
93
- version: '0'
94
- description: ! "This library enable to pick out items with their weight.\n Weight
95
- data is storaged as a YAML file.\n You can use this library for music player,
96
- wallpaper changer, language training.\n "
68
+ version_requirements: *71662030
69
+ description: ! "This library enables to pick out items at the rate of their weight.\n
70
+ \ Weight data is storaged as a YAML file.\n You can use this library for music
71
+ player, wallpaper changer, language training.\n "
97
72
  email: ippei94da@gmail.com
98
73
  executables: []
99
74
  extensions: []
@@ -103,8 +78,8 @@ extra_rdoc_files:
103
78
  files:
104
79
  - .document
105
80
  - .rspec
81
+ - CHANGES
106
82
  - Gemfile
107
- - Gemfile.lock
108
83
  - LICENSE.txt
109
84
  - README.rdoc
110
85
  - Rakefile
@@ -128,7 +103,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
103
  version: '0'
129
104
  segments:
130
105
  - 0
131
- hash: -774423471
106
+ hash: -490884429
132
107
  required_rubygems_version: !ruby/object:Gem::Requirement
133
108
  none: false
134
109
  requirements:
@@ -137,8 +112,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
112
  version: '0'
138
113
  requirements: []
139
114
  rubyforge_project:
140
- rubygems_version: 1.8.21
115
+ rubygems_version: 1.8.11
141
116
  signing_key:
142
117
  specification_version: 3
143
- summary: Picking one item from list with weight
118
+ summary: Picking one item from list at the rate of its weight.
144
119
  test_files: []
data/Gemfile.lock DELETED
@@ -1,37 +0,0 @@
1
- GEM
2
- remote: http://rubygems.org/
3
- specs:
4
- diff-lcs (1.1.3)
5
- git (1.2.5)
6
- jeweler (1.8.3)
7
- bundler (~> 1.0)
8
- git (>= 1.2.5)
9
- rake
10
- rdoc
11
- json (1.6.6)
12
- multi_json (1.2.0)
13
- rake (0.9.2.2)
14
- rdoc (3.12)
15
- json (~> 1.4)
16
- rspec (2.9.0)
17
- rspec-core (~> 2.9.0)
18
- rspec-expectations (~> 2.9.0)
19
- rspec-mocks (~> 2.9.0)
20
- rspec-core (2.9.0)
21
- rspec-expectations (2.9.0)
22
- diff-lcs (~> 1.1.3)
23
- rspec-mocks (2.9.0)
24
- simplecov (0.6.1)
25
- multi_json (~> 1.0)
26
- simplecov-html (~> 0.5.3)
27
- simplecov-html (0.5.3)
28
-
29
- PLATFORMS
30
- ruby
31
-
32
- DEPENDENCIES
33
- bundler (~> 1.1.3)
34
- jeweler (~> 1.8.3)
35
- rdoc (~> 3.12)
36
- rspec (~> 2.9.0)
37
- simplecov