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