pickup 0.0.8 → 0.0.9
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.
- 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
|