weightedpicker 0.0.2 → 0.1.0
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 +12 -1
- data/Gemfile +3 -2
- data/VERSION +1 -1
- data/lib/weightedpicker.rb +56 -77
- data/lib/weightedpicker/tree.rb +139 -0
- data/spec/a-1b256.yaml +3 -0
- data/spec/a256b0.yaml +3 -0
- data/spec/a256b1.yaml +3 -0
- data/spec/a256b128.yaml +3 -0
- data/spec/a512b64.yaml +3 -0
- data/spec/a99999b64.yaml +3 -0
- data/spec/float.yaml +3 -0
- data/spec/tree_spec.rb +123 -0
- data/spec/weightedpicker_spec.rb +136 -305
- data/weightedpicker.gemspec +18 -9
- metadata +50 -16
data/CHANGES
CHANGED
@@ -1,7 +1,18 @@
|
|
1
|
-
|
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
|
+
gem "rspec", "~> 2.13.0"
|
10
10
|
gem "rdoc", "~> 3.12"
|
11
|
-
gem "bundler", "~> 1.
|
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
|
1
|
+
0.1.0
|
data/lib/weightedpicker.rb
CHANGED
@@ -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 =
|
95
|
-
|
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
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
127
|
+
def dump(io)
|
128
|
+
YAML.dump(@tree.names_weights, io)
|
129
|
+
end
|
122
130
|
|
123
|
-
|
124
|
-
|
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
|
130
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
186
|
-
#
|
187
|
-
#
|
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
|
-
|
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
|
-
|
193
|
-
|
173
|
+
|
174
|
+
max = INI_WEIGHT if max < INI_WEIGHT
|
175
|
+
new_keys.each do |key|
|
176
|
+
new_weights[key] = max
|
194
177
|
end
|
195
|
-
|
196
|
-
|
197
|
-
|
178
|
+
|
179
|
+
data = new_weights
|
180
|
+
|
181
|
+
@tree = WeightedPicker::Tree.new(data)
|
198
182
|
end
|
199
183
|
|
200
|
-
|
201
|
-
# ただし、weight が MIN_WEIGHT 未満となった項目は
|
202
|
-
# MIN_WEIGHT を新しい weight とする。
|
203
|
-
def normalize_write
|
204
|
-
raise NoEntryError if @weights.size == 0
|
184
|
+
private
|
205
185
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
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
data/spec/a256b0.yaml
ADDED
data/spec/a256b1.yaml
ADDED
data/spec/a256b128.yaml
ADDED
data/spec/a512b64.yaml
ADDED
data/spec/a99999b64.yaml
ADDED
data/spec/float.yaml
ADDED
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
|
+
|
data/spec/weightedpicker_spec.rb
CHANGED
@@ -4,357 +4,188 @@ require "fileutils"
|
|
4
4
|
|
5
5
|
class WeightedPicker
|
6
6
|
attr_accessor :weights
|
7
|
-
public :
|
7
|
+
public :merge
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
AB_YAML = "spec/a256b128.yaml"
|
11
11
|
NOT_EXIST_FILE = "not_exist_file"
|
12
12
|
|
13
13
|
|
14
14
|
|
15
|
-
describe "Weightedpicker
|
15
|
+
describe "Weightedpicker" do
|
16
16
|
before do
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
144
|
-
|
31
|
+
#it "should raise exception" do
|
32
|
+
# # 作成できないファイル名。
|
33
|
+
# lambda{WeightedPicker.load_file("")}.should raise_error(Errno::ENOENT)
|
34
|
+
#end
|
145
35
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
192
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
end
|
81
|
+
#before do
|
82
|
+
# @wp01 = WeightedPicker.load_file(AB_YAML)
|
83
|
+
# @wp00 = WeightedPicker.new({})
|
84
|
+
#end
|
199
85
|
|
200
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
118
|
+
describe "Weightedpicker::lighten" do
|
119
|
+
#before do
|
120
|
+
# @wp01 = WeightedPicker.load_file(AB_YAML)
|
121
|
+
#end
|
240
122
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
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
|
-
|
296
|
-
|
297
|
-
|
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
|
-
|
162
|
+
wp01.names_weights.should == { "A" => 256, "B" => 1 }
|
301
163
|
|
302
|
-
|
303
|
-
|
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
|
-
|
312
|
-
|
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
|
-
|
324
|
-
|
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
|
-
|
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
|
-
|
340
|
-
|
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
|
-
|
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
|
data/weightedpicker.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "weightedpicker"
|
8
|
-
s.version = "0.0
|
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 = "
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
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:
|
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:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 2.
|
21
|
+
version: 2.13.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
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:
|
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:
|
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:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ~>
|
42
52
|
- !ruby/object:Gem::Version
|
43
|
-
version: 1.
|
53
|
+
version: 1.3.4
|
44
54
|
type: :development
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
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:
|
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:
|
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:
|
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:
|
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: -
|
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.
|
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.
|