pickup 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +45 -17
- data/benchmarks/pickup.rb +0 -2
- data/lib/pickup.rb +58 -13
- data/lib/pickup/version.rb +1 -1
- data/spec/pickup/pickup_spec.rb +51 -4
- metadata +5 -7
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NjM5ZjczZmRhZjIzYzBkY2NjNmY5ZGNmNzY1NzczZDg0NTQ4NTMyYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDZmNzcxNjM4MTFkMDU1OTBhNDA5MjI2YmIyNDdiM2UyMDc4MGE4Zg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MmM4NGU2YzY2NDkyODdkZWY1ZDdhNWM5ZWFmN2VlNGFmMzVhMGQ0M2Q1ZGMy
|
10
|
+
MDc1MGU3MDI0NWNhNjRiYmI4YzA1YThjNDc0NTE3YTIyZmM5ODNjMzM0Mjk3
|
11
|
+
NWU0NjBhNDE0OTBlMzY0MTRlMThiMDcwOTg3ZTM5YzkwNTg3YTY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
N2JiZWU0N2JmOWFkZDExMTA1NjY2MDIyZDdkN2U5NTZjZTc0NDE0MTQxMmU4
|
14
|
+
NmVjOWQxYWNlZWQwOTJiYmUzZDU5M2JhODNjYzU3ODkxZDBiYzQ3MjNkMWFh
|
15
|
+
ZmYzNDk1ODYwODJkZWM2MzY2YTkxYWY2NDljMGQ5M2IzZTIxNTk=
|
data/README.md
CHANGED
@@ -31,7 +31,7 @@ pond = {
|
|
31
31
|
"minnow" => 20
|
32
32
|
}
|
33
33
|
```
|
34
|
-
Values are a
|
34
|
+
Values are a chance (probability) to get this fish.
|
35
35
|
|
36
36
|
So we should create our pickup.
|
37
37
|
|
@@ -42,6 +42,8 @@ pickup.pick(3)
|
|
42
42
|
```
|
43
43
|
Look, we've just catched few minnows! To get selmon we need some more tries ;)
|
44
44
|
|
45
|
+
### Custom distribution function
|
46
|
+
|
45
47
|
Ok. What if our probability is not a linear function. We can create our pickup with a function:
|
46
48
|
|
47
49
|
```ruby
|
@@ -51,7 +53,7 @@ pickup.pick(3)
|
|
51
53
|
```
|
52
54
|
Wow, good catch!
|
53
55
|
|
54
|
-
Also you can change our
|
56
|
+
Also you can change our "function" on the fly. Let's make square function:
|
55
57
|
|
56
58
|
```ruby
|
57
59
|
pickup = Pickup.new(pond)
|
@@ -67,6 +69,22 @@ pickup.pick
|
|
67
69
|
#=> "minnow"
|
68
70
|
```
|
69
71
|
|
72
|
+
In case of `f(weight)=weight^10` most possible result will be "minnow", because `20^10` is `2^10` more possible then "gudgeon"
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
pickup = Pickup.new(pond)
|
76
|
+
pickup.pick(10){ |v| v**10 }
|
77
|
+
#=> ["minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow"]
|
78
|
+
```
|
79
|
+
|
80
|
+
Or you can use reverse probability:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
pickup = Pickup.new(pond)
|
84
|
+
pickup.pick(10){ |v| v**(-10) }
|
85
|
+
#=> ["selmon", "selmon", "selmon", "selmon", "crucian", "selmon", "selmon", "selmon", "selmon", "selmon"]
|
86
|
+
```
|
87
|
+
|
70
88
|
### Random uniq pick
|
71
89
|
|
72
90
|
Also we can pick random uniq items from the list
|
@@ -83,24 +101,34 @@ pickup.pick
|
|
83
101
|
#=> "sturgeon"
|
84
102
|
```
|
85
103
|
|
86
|
-
### Custom
|
87
|
-
|
88
|
-
You can define your own function. So in case of `f(weight)=weight^10` most possible result will be "minnow", because `20^10` is `2^10` more possible then "gudgeon"
|
89
|
-
```ruby
|
90
|
-
pickup = Pickup.new(pond)
|
91
|
-
pickup.pick(10){ |v| v**10 }
|
92
|
-
#=> ["minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow", "minnow"]
|
93
|
-
```
|
104
|
+
### Custom key and weight selection functions
|
94
105
|
|
95
|
-
|
106
|
+
We can use more complex collections by defining our own key and weight selectors:
|
96
107
|
|
97
108
|
```ruby
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
109
|
+
require "ostruct"
|
110
|
+
|
111
|
+
pond_ostruct = [
|
112
|
+
OpenStruct.new(key: "sel", name: "selmon", weight: 1),
|
113
|
+
OpenStruct.new(key: "car", name: "carp", weight: 4),
|
114
|
+
OpenStruct.new(key: "cru", name: "crucian", weight: 3),
|
115
|
+
OpenStruct.new(key: "her", name: "herring", weight: 6),
|
116
|
+
OpenStruct.new(key: "stu", name: "sturgeon", weight: 8),
|
117
|
+
OpenStruct.new(key: "gud", name: "gudgeon", weight: 10),
|
118
|
+
OpenStruct.new(key: "min", name: "minnow", weight: 20)
|
119
|
+
]
|
120
|
+
|
121
|
+
key_func = Proc.new{ |item| item.key }
|
122
|
+
weight_func = Proc.new{ |item| item.weight }
|
123
|
+
|
124
|
+
pickup = Pickup.new(pond_ostruct, key_func: key_func, weight_func: weight_func)
|
125
|
+
pickup.pick
|
126
|
+
#=> "gud"
|
102
127
|
|
103
|
-
|
128
|
+
name_func = Proc.new{ |item| item.name }
|
129
|
+
pickup.pick(1, key_func: name_func)
|
130
|
+
#=> "gudgeon"
|
131
|
+
```
|
104
132
|
|
105
133
|
## Contributing
|
106
134
|
|
@@ -108,4 +136,4 @@ Reverse
|
|
108
136
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
109
137
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
110
138
|
4. Push to the branch (`git push origin my-new-feature`)
|
111
|
-
5. Create new Pull Request
|
139
|
+
5. Create new Pull Request
|
data/benchmarks/pickup.rb
CHANGED
data/lib/pickup.rb
CHANGED
@@ -2,17 +2,21 @@ require "pickup/version"
|
|
2
2
|
|
3
3
|
class Pickup
|
4
4
|
attr_reader :list, :uniq
|
5
|
-
attr_writer :pick_func
|
5
|
+
attr_writer :pick_func, :key_func, :weight_func
|
6
6
|
|
7
7
|
def initialize(list, opts={}, &block)
|
8
8
|
@list = list
|
9
9
|
@uniq = opts[:uniq] || false
|
10
10
|
@pick_func = block if block_given?
|
11
|
+
@key_func = opts[:key_func]
|
12
|
+
@weight_func = opts[:weight_func]
|
11
13
|
end
|
12
14
|
|
13
|
-
def pick(count=1, &block)
|
15
|
+
def pick(count=1, opts={}, &block)
|
14
16
|
func = block || pick_func
|
15
|
-
|
17
|
+
key_func = opts[:key_func] || @key_func
|
18
|
+
weight_func = opts[:weight_func] || @weight_func
|
19
|
+
mlist = MappedList.new(list, func, uniq: uniq, key_func: key_func, weight_func: weight_func)
|
16
20
|
result = mlist.random(count)
|
17
21
|
count == 1 ? result.first : result
|
18
22
|
end
|
@@ -26,22 +30,43 @@ class Pickup
|
|
26
30
|
end
|
27
31
|
|
28
32
|
class CircleIterator
|
29
|
-
attr_reader :func, :obj, :max
|
33
|
+
attr_reader :func, :obj, :max, :key_func, :weight_func
|
30
34
|
|
31
|
-
def initialize(obj, func, max)
|
35
|
+
def initialize(obj, func, max, opts={})
|
32
36
|
@obj = obj.dup
|
33
37
|
@func = func
|
34
38
|
@max = max
|
39
|
+
@key_func = opts[:key_func] || key_func
|
40
|
+
@weight_func = opts[:weight_func] || weight_func
|
41
|
+
end
|
42
|
+
|
43
|
+
def key_func
|
44
|
+
@key_func ||= begin
|
45
|
+
Proc.new do |item|
|
46
|
+
item[0]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def weight_func
|
52
|
+
@weight_func ||= begin
|
53
|
+
Proc.new do |item|
|
54
|
+
item[1]
|
55
|
+
end
|
56
|
+
end
|
35
57
|
end
|
36
58
|
|
37
59
|
def each
|
38
60
|
until obj.empty?
|
39
61
|
start = 0
|
40
|
-
obj.each do |item
|
62
|
+
obj.each do |item|
|
63
|
+
key = key_func.call(item)
|
64
|
+
weight = weight_func.call(item)
|
65
|
+
|
41
66
|
val = func.call(weight)
|
42
67
|
start += val
|
43
|
-
if yield([
|
44
|
-
obj.delete
|
68
|
+
if yield([key, start, max])
|
69
|
+
obj.delete key
|
45
70
|
@max -= val
|
46
71
|
end
|
47
72
|
end
|
@@ -50,17 +75,37 @@ class Pickup
|
|
50
75
|
end
|
51
76
|
|
52
77
|
class MappedList
|
53
|
-
attr_reader :list, :func, :uniq
|
78
|
+
attr_reader :list, :func, :uniq, :key_func, :weight_func
|
79
|
+
|
80
|
+
def initialize(list, func, opts=nil)
|
81
|
+
if Hash === opts
|
82
|
+
@key_func = opts[:key_func]
|
83
|
+
@weight_func = opts[:weight_func] || weight_func
|
84
|
+
@uniq = opts[:uniq] || false
|
85
|
+
else
|
86
|
+
if !!opts == opts
|
87
|
+
# If opts is explicitly provided as a boolean, show the deprecated warning.
|
88
|
+
warn "[DEPRECATED] Passing uniq as a boolean to MappedList's initialize method is deprecated. Please use the opts hash instead."
|
89
|
+
end
|
90
|
+
|
91
|
+
@uniq = opts || false
|
92
|
+
end
|
54
93
|
|
55
|
-
def initialize(list, func, uniq=false)
|
56
94
|
@func = func
|
57
|
-
@uniq = uniq
|
58
95
|
@list = list
|
59
96
|
@current_state = 0
|
60
97
|
end
|
61
98
|
|
99
|
+
def weight_func
|
100
|
+
@weight_func ||= begin
|
101
|
+
Proc.new do |item|
|
102
|
+
item[1]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
62
107
|
def each(&blk)
|
63
|
-
CircleIterator.new(@list, func, max).each do |item|
|
108
|
+
CircleIterator.new(@list, func, max, key_func: @key_func, weight_func: weight_func).each do |item|
|
64
109
|
if uniq
|
65
110
|
true if yield item
|
66
111
|
else
|
@@ -92,7 +137,7 @@ class Pickup
|
|
92
137
|
def max
|
93
138
|
@max ||= begin
|
94
139
|
max = 0
|
95
|
-
list.each{ |item| max += func.call(item
|
140
|
+
list.each{ |item| max += func.call(weight_func.call(item)) }
|
96
141
|
max
|
97
142
|
end
|
98
143
|
end
|
data/lib/pickup/version.rb
CHANGED
data/spec/pickup/pickup_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'spec_helper'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'stringio'
|
3
5
|
|
4
6
|
describe Pickup do
|
5
7
|
before do
|
@@ -12,20 +14,44 @@ describe Pickup do
|
|
12
14
|
"gudgeon" => 10, # 32
|
13
15
|
"minnow" => 20 # 52
|
14
16
|
}
|
17
|
+
|
18
|
+
@struct_list = @list.map{ |key, weight| OpenStruct.new(key: key, weight: weight) }
|
19
|
+
|
15
20
|
@func = Proc.new{ |a| a }
|
16
21
|
@pickup = Pickup.new(@list)
|
17
22
|
@pickup2 = Pickup.new(@list, uniq: true)
|
23
|
+
|
24
|
+
@key_func = Proc.new{ |item| item.key }
|
25
|
+
@weight_func = Proc.new{ |item| item.weight }
|
26
|
+
@pickup3 = Pickup.new(@struct_list, key_func: @key_func, weight_func: @weight_func)
|
18
27
|
end
|
19
28
|
|
20
|
-
it "should pick correct
|
29
|
+
it "should pick correct amount of items" do
|
21
30
|
@pickup.pick(2).size.must_equal 2
|
22
31
|
@pickup.pick(10).size.must_equal 10
|
23
32
|
end
|
24
33
|
|
25
34
|
describe Pickup::MappedList do
|
26
35
|
before do
|
27
|
-
@ml = Pickup::MappedList.new(@list, @func, true)
|
36
|
+
@ml = Pickup::MappedList.new(@list, @func, uniq: true)
|
28
37
|
@ml2 = Pickup::MappedList.new(@list, @func)
|
38
|
+
@ml3 = Pickup::MappedList.new(@struct_list, @func, key_func: @key_func, weight_func: @weight_func)
|
39
|
+
@ml4 = Pickup::MappedList.new(@struct_list, @func, uniq: true, key_func: @key_func, weight_func: @weight_func)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "deprecated warning on initialization if uniq is passed directly as a boolean" do
|
43
|
+
# Swap in a fake IO object for $stderr.
|
44
|
+
orig_stderr = $stderr
|
45
|
+
$stderr = StringIO.new
|
46
|
+
|
47
|
+
Pickup::MappedList.new(@list, @func, true)
|
48
|
+
|
49
|
+
# Inspect the fake IO object for the deprecated warning.
|
50
|
+
$stderr.rewind
|
51
|
+
$stderr.string.chomp.must_equal("[DEPRECATED] Passing uniq as a boolean to MappedList's initialize method is deprecated. Please use the opts hash instead.")
|
52
|
+
|
53
|
+
# Restore the original stderr.
|
54
|
+
$stderr = orig_stderr
|
29
55
|
end
|
30
56
|
|
31
57
|
it "should return selmon and then carp and then crucian for uniq pickup" do
|
@@ -49,6 +75,18 @@ describe Pickup do
|
|
49
75
|
it "should return right max" do
|
50
76
|
@ml.max.must_equal 52
|
51
77
|
end
|
78
|
+
|
79
|
+
it "should return selmon 4 times for non-uniq pickup (using custom weight function)" do
|
80
|
+
4.times{ @ml3.get_random_items([0]).first.must_equal "selmon" }
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should return right max (using custom weight function)" do
|
84
|
+
@ml3.max.must_equal 52
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return selmon and then carp and then crucian for uniq pickup (using custom weight function)" do
|
88
|
+
@ml4.get_random_items([0, 0, 0]).must_equal ["selmon", "carp", "crucian"]
|
89
|
+
end
|
52
90
|
end
|
53
91
|
|
54
92
|
it "should take 7 different fish" do
|
@@ -60,13 +98,22 @@ describe Pickup do
|
|
60
98
|
proc{ items = @pickup2.pick(8) }.must_raise RuntimeError
|
61
99
|
end
|
62
100
|
|
63
|
-
it "should return
|
101
|
+
it "should return a list of items including the heaviest item (but not always - sometimes it will fail)" do
|
64
102
|
items = @pickup2.pick(2){ |v| v**20 }
|
65
103
|
(items.include? "minnow").must_equal true
|
66
104
|
end
|
67
105
|
|
68
|
-
it "should return
|
106
|
+
it "should return a list of items including the lightest item (but not always - sometimes it will fail)" do
|
69
107
|
items = @pickup2.pick(2){ |v| v**(-20) }
|
70
108
|
(items.include? "selmon").must_equal true
|
71
109
|
end
|
110
|
+
|
111
|
+
it "should pick correct amount of items (using custom weight function)" do
|
112
|
+
@pickup3.pick(4).size.must_equal 4
|
113
|
+
@pickup3.pick(12).size.must_equal 12
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should take 5 fish (using custom weight function)" do
|
117
|
+
@pickup3.pick(5, key_func: @key_func, weight_func: @weight_func).size.must_equal 5
|
118
|
+
end
|
72
119
|
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pickup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.9
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- fl00r
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-05-20 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: Pickup helps you to pick item from collection by it's weight/probability
|
15
14
|
email:
|
@@ -31,27 +30,26 @@ files:
|
|
31
30
|
- spec/spec_helper.rb
|
32
31
|
homepage: ''
|
33
32
|
licenses: []
|
33
|
+
metadata: {}
|
34
34
|
post_install_message:
|
35
35
|
rdoc_options: []
|
36
36
|
require_paths:
|
37
37
|
- lib
|
38
38
|
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
-
none: false
|
40
39
|
requirements:
|
41
40
|
- - ! '>='
|
42
41
|
- !ruby/object:Gem::Version
|
43
42
|
version: '0'
|
44
43
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
-
none: false
|
46
44
|
requirements:
|
47
45
|
- - ! '>='
|
48
46
|
- !ruby/object:Gem::Version
|
49
47
|
version: '0'
|
50
48
|
requirements: []
|
51
49
|
rubyforge_project:
|
52
|
-
rubygems_version:
|
50
|
+
rubygems_version: 2.2.1
|
53
51
|
signing_key:
|
54
|
-
specification_version:
|
52
|
+
specification_version: 4
|
55
53
|
summary: Pickup helps you to pick item from collection by it's weight/probability
|
56
54
|
test_files:
|
57
55
|
- spec/pickup/pickup_spec.rb
|