forked from denalove/votersim
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpickup.rb
144 lines (126 loc) · 2.91 KB
/
pickup.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
class Pickup
attr_reader :list, :uniq
attr_writer :pick_func, :key_func, :weight_func
def initialize(list, opts={}, &block)
@list = list
@uniq = opts[:uniq] || false
@pick_func = block if block_given?
@key_func = opts[:key_func]
@weight_func = opts[:weight_func]
end
def pick(count=1, opts={}, &block)
func = block || pick_func
key_func = opts[:key_func] || @key_func
weight_func = opts[:weight_func] || @weight_func
mlist = MappedList.new(list, func, uniq: uniq, key_func: key_func, weight_func: weight_func)
result = mlist.random(count)
count == 1 ? result.first : result
end
def pick_func
@pick_func ||= begin
Proc.new do |val|
val
end
end
end
class CircleIterator
attr_reader :func, :obj, :max, :key_func, :weight_func
def initialize(obj, func, max, opts={})
@obj = obj.dup
@func = func
@max = max
@key_func = opts[:key_func] || key_func
@weight_func = opts[:weight_func] || weight_func
end
def key_func
@key_func ||= begin
Proc.new do |item|
item[0]
end
end
end
def weight_func
@weight_func ||= begin
Proc.new do |item|
item[1]
end
end
end
def each
until obj.empty?
start = 0
obj.each do |item|
key = key_func.call(item)
weight = weight_func.call(item)
val = func.call(weight)
start += val
if yield([key, start, max])
obj.delete key
@max -= val
end
end
end
end
end
class MappedList
attr_reader :list, :func, :uniq, :key_func, :weight_func
def initialize(list, func, opts=nil)
if Hash === opts
@key_func = opts[:key_func]
@weight_func = opts[:weight_func] || weight_func
@uniq = opts[:uniq] || false
else
if !!opts == opts
# If opts is explicitly provided as a boolean, show the deprecated warning.
warn "[DEPRECATED] Passing uniq as a boolean to MappedList's initialize method is deprecated. Please use the opts hash instead."
end
@uniq = opts || false
end
@func = func
@list = list
@current_state = 0
end
def weight_func
@weight_func ||= begin
Proc.new do |item|
item[1]
end
end
end
def each(&blk)
CircleIterator.new(@list, func, max, key_func: @key_func, weight_func: weight_func).each do |item|
if uniq
true if yield item
else
nil while yield(item)
end
end
end
def random(count)
raise "List is shorter then count of items you want to get" if uniq && list.size < count
nums = count.times.map{ rand(max) }.sort
return [] if max == 0
get_random_items(nums)
end
def get_random_items(nums)
current_num = nums.shift
items = []
each do |item, counter, mx|
break unless current_num
if counter%(mx+1) > current_num%mx
items << item
current_num = nums.shift
true
end
end
items
end
def max
@max ||= begin
max = 0
list.each{ |item| max += func.call(weight_func.call(item)) }
max
end
end
end
end