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 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