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 +9 -0
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/weightedpicker.rb +127 -143
- data/spec/weightedpicker_spec.rb +135 -66
- data/weightedpicker.gemspec +6 -6
- metadata +19 -44
- data/Gemfile.lock +0 -37
data/CHANGES
ADDED
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
|
21
|
-
gem.description = %Q{This library
|
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
|
+
0.0.2
|
data/lib/weightedpicker.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
# TODO
|
4
|
-
#
|
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
|
47
|
+
# 点数の上限は 1 million。
|
48
48
|
# 上限を越える点数の要素が現れたら、
|
49
|
-
# それが
|
49
|
+
# それが max valueになるように全ての要素の評価を除算して正規化する。
|
50
50
|
#
|
51
51
|
#== 下限
|
52
|
-
# 点数の下限は
|
52
|
+
# 点数の下限は 1 としておく。
|
53
53
|
# 下限を下回る点数の要素が現れたら、
|
54
54
|
# その要素の点数を下限値に設定する。
|
55
55
|
# これが重要になるのはクイズゲームで間違ったときにその問題以外の
|
@@ -91,143 +91,127 @@ require 'yaml'
|
|
91
91
|
#
|
92
92
|
# ヒストリ関係はこのクラスの外に出した方が良いと判断。
|
93
93
|
class WeightedPicker
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
data/spec/weightedpicker_spec.rb
CHANGED
@@ -4,7 +4,7 @@ require "fileutils"
|
|
4
4
|
|
5
5
|
class WeightedPicker
|
6
6
|
attr_accessor :weights
|
7
|
-
public :normalize_write, :
|
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" =>
|
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" =>
|
30
|
-
YAML.load_file(NOT_EXIST_FILE).should == { "A" =>
|
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" =>
|
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" =>
|
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" =>
|
48
|
+
WeightedPicker.new(TMP_FILE, ["A","B"]).weights.should == { "A" => 1_000_000, "B" => 250_000, }
|
49
49
|
#
|
50
|
-
weights = { "A" =>
|
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" =>
|
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
|
-
|
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
|
-
|
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" =>
|
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" =>
|
98
|
+
t.weights. should == { "B" => 1_000_000 }
|
88
99
|
# 書き込みチェック
|
89
|
-
YAML.load_file(TMP_FILE).should == { "B" =>
|
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" =>
|
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" =>
|
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" =>
|
118
|
+
t.weights.should == { "A" => 1_000_000, "B" => 500_000}
|
108
119
|
# 書き込みチェック
|
109
|
-
YAML.load_file(TMP_FILE).should == { "A" =>
|
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" =>
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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" =>
|
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" =>
|
178
|
+
t.weights.should == { "A" => 1_000_000, "B" => 250_000 }
|
163
179
|
# 書き込みチェック
|
164
|
-
YAML.load_file(TMP_FILE).should == { "A" =>
|
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" =>
|
186
|
+
t.weights.should == { "A" => 1_000_000, "B" => 1_000_000 }
|
171
187
|
# 書き込みチェック
|
172
|
-
YAML.load_file(TMP_FILE).should == { "A" =>
|
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" =>
|
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" =>
|
216
|
+
t.weights.should == { "A" => 1_000_000, "B" => 1_000_000 }
|
201
217
|
# 書き込みチェック
|
202
|
-
YAML.load_file(TMP_FILE).should == { "A" =>
|
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" =>
|
224
|
+
t.weights.should == { "A" => 1_000_000, "B" => 250_000 }
|
209
225
|
# 書き込みチェック
|
210
|
-
YAML.load_file(TMP_FILE).should == { "A" =>
|
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::
|
241
|
+
describe "Weightedpicker::normalize_write" do
|
226
242
|
before do
|
227
|
-
weights = { "A" =>
|
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
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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 "
|
302
|
+
describe "include zero weight" do
|
255
303
|
before do
|
256
|
-
weights = { "A" =>
|
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
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
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
|
-
|
284
|
-
|
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
|
+
|
data/weightedpicker.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "weightedpicker"
|
8
|
-
s.version = "0.0.
|
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-
|
13
|
-
s.description = "This library
|
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.
|
37
|
-
s.summary = "Picking one item from list
|
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.
|
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-
|
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:
|
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:
|
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:
|
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:
|
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:
|
89
|
-
|
90
|
-
|
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: -
|
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.
|
115
|
+
rubygems_version: 1.8.11
|
141
116
|
signing_key:
|
142
117
|
specification_version: 3
|
143
|
-
summary: Picking one item from list
|
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
|