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 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 chence to get this fish.
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 `function` on the fly. Let's make square function:
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 probability function
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
- Or you can use reverse probability:
106
+ We can use more complex collections by defining our own key and weight selectors:
96
107
 
97
108
  ```ruby
98
- pickup = Pickup.new(pond)
99
- pickup.pick(10){ |v| v**(-10) }
100
- #=> ["selmon", "selmon", "selmon", "selmon", "crucian", "selmon", "selmon", "selmon", "selmon", "selmon"]
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
- Reverse
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
@@ -46,8 +46,6 @@ def big_weights(uniq=false)
46
46
  pickup.pick(100)
47
47
  end
48
48
 
49
-
50
-
51
49
  n = 500
52
50
 
53
51
  Benchmark.bm do |x|
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
- mlist = MappedList.new(list, func, uniq)
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, weight|
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([item, start, max])
44
- obj.delete item
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[1]) }
140
+ list.each{ |item| max += func.call(weight_func.call(item)) }
96
141
  max
97
142
  end
98
143
  end
@@ -1,3 +1,3 @@
1
1
  class Pickup
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -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 ammount of items" do
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 include most weigtfull item (but not always - sometimes it will fail)" do
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 include less weigtfull item (but not always - sometimes it will fail)" do
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.8
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: 2012-06-04 00:00:00.000000000 Z
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: 1.8.23
50
+ rubygems_version: 2.2.1
53
51
  signing_key:
54
- specification_version: 3
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